Skip to content

Commit 8b50da8

Browse files
committed
fix (and test) bisecting into code object cache
1 parent ad18e02 commit 8b50da8

File tree

3 files changed

+113
-13
lines changed

3 files changed

+113
-13
lines changed

Cython/Compiler/Nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8711,12 +8711,12 @@ def generate_execution_code(self, code):
87118711
PyObject *py_globals = 0;
87128712
PyFrameObject *py_frame = 0;
87138713
8714-
py_code = %(FINDCODEOBJECT)s(%(CLINENO)s);
8714+
py_code = %(FINDCODEOBJECT)s(%(CLINENO)s ? %(CLINENO)s : %(LINENO)s);
87158715
if (!py_code) {
87168716
py_code = __Pyx_CreateCodeObjectForTraceback(
87178717
funcname, %(CLINENO)s, %(LINENO)s, %(FILENAME)s);
87188718
if (!py_code) goto bad;
8719-
%(INSERTCODEOBJECT)s(%(CLINENO)s, py_code);
8719+
%(INSERTCODEOBJECT)s(%(CLINENO)s ? %(CLINENO)s : %(LINENO)s, py_code);
87208720
}
87218721
py_globals = PyModule_GetDict(%(GLOBALS)s);
87228722
if (!py_globals) goto bad;

Cython/Utility/ModuleSetupCode.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ struct __Pyx_CodeObjectCache {
234234

235235
static struct __Pyx_CodeObjectCache __pyx_code_cache = {0,0,NULL};
236236

237+
static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line);
237238
static void __pyx_clear_code_object_cache(void);
238239
static PyCodeObject *__pyx_find_code_object(int code_line);
239240
static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object);
@@ -258,25 +259,25 @@ static void __pyx_clear_code_object_cache(void) {
258259
PyMem_Free(entries);
259260
}
260261

261-
static int __pyx_bisect_code_objects(int code_line) {
262-
int start, end, mid;
263-
__Pyx_CodeObjectCacheEntry* entries = __pyx_code_cache.entries;
264-
start = 0;
265-
end = __pyx_code_cache.count - 1;
262+
static int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line) {
263+
int start = 0, mid = 0, end = count - 1;
264+
if (end >= 0 && code_line > entries[end].code_line) {
265+
return count;
266+
}
266267
while (start < end) {
267268
mid = (start + end) / 2;
268269
if (code_line < entries[mid].code_line) {
269-
end = mid - 1;
270+
end = mid;
270271
} else if (code_line > entries[mid].code_line) {
271272
start = mid + 1;
272273
} else {
273274
return mid;
274275
}
275276
}
276-
if (start == end || code_line <= entries[start].code_line) {
277-
return start;
277+
if (code_line <= entries[mid].code_line) {
278+
return mid;
278279
} else {
279-
return end;
280+
return mid + 1;
280281
}
281282
}
282283

@@ -286,7 +287,7 @@ static PyCodeObject *__pyx_find_code_object(int code_line) {
286287
if (unlikely(!code_line) || unlikely(!__pyx_code_cache.entries)) {
287288
return NULL;
288289
}
289-
pos = __pyx_bisect_code_objects(code_line);
290+
pos = __pyx_bisect_code_objects(__pyx_code_cache.entries, __pyx_code_cache.count, code_line);
290291
if (unlikely(pos >= __pyx_code_cache.count) || unlikely(__pyx_code_cache.entries[pos].code_line != code_line)) {
291292
return NULL;
292293
}
@@ -313,7 +314,7 @@ static void __pyx_insert_code_object(int code_line, PyCodeObject* code_object) {
313314
}
314315
return;
315316
}
316-
pos = __pyx_bisect_code_objects(code_line);
317+
pos = __pyx_bisect_code_objects(__pyx_code_cache.entries, __pyx_code_cache.count, code_line);
317318
if ((pos < __pyx_code_cache.count) && unlikely(__pyx_code_cache.entries[pos].code_line == code_line)) {
318319
PyCodeObject* tmp = entries[pos].code_object;
319320
entries[pos].code_object = code_object;

tests/run/code_object_cache.pyx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# mode: run
2+
# tag: except
3+
4+
# test the code object cache that is being used in exception raising
5+
6+
### low level tests
7+
8+
cdef extern from *:
9+
# evil hack to access the internal utility function
10+
ctypedef struct PyCodeObject
11+
ctypedef struct __Pyx_CodeObjectCacheEntry:
12+
int code_line
13+
PyCodeObject* code_object
14+
int __pyx_bisect_code_objects(__Pyx_CodeObjectCacheEntry* entries, int count, int code_line)
15+
16+
def test_lowlevel_bisect2(*indices):
17+
"""
18+
>>> test_lowlevel_bisect2(1, 2, 3, 4, 5, 6)
19+
[0, 0, 1, 1, 2, 2]
20+
"""
21+
cdef __Pyx_CodeObjectCacheEntry* cache = [
22+
__Pyx_CodeObjectCacheEntry(2, NULL),
23+
__Pyx_CodeObjectCacheEntry(4, NULL),
24+
]
25+
return [ __pyx_bisect_code_objects(cache, 2, i)
26+
for i in indices ]
27+
28+
def test_lowlevel_bisect5(*indices):
29+
"""
30+
>>> test_lowlevel_bisect5(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
31+
[0, 1, 2, 2, 2, 3, 3, 3, 4, 5, 5]
32+
"""
33+
cdef __Pyx_CodeObjectCacheEntry* cache = [
34+
__Pyx_CodeObjectCacheEntry(1, NULL),
35+
__Pyx_CodeObjectCacheEntry(2, NULL),
36+
__Pyx_CodeObjectCacheEntry(5, NULL),
37+
__Pyx_CodeObjectCacheEntry(8, NULL),
38+
__Pyx_CodeObjectCacheEntry(9, NULL),
39+
]
40+
return [ __pyx_bisect_code_objects(cache, 5, i)
41+
for i in indices ]
42+
43+
def test_lowlevel_bisect6(*indices):
44+
"""
45+
>>> test_lowlevel_bisect6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
46+
[0, 0, 1, 2, 2, 2, 3, 3, 4, 5, 5, 5, 6]
47+
"""
48+
cdef __Pyx_CodeObjectCacheEntry* cache = [
49+
__Pyx_CodeObjectCacheEntry(2, NULL),
50+
__Pyx_CodeObjectCacheEntry(3, NULL),
51+
__Pyx_CodeObjectCacheEntry(6, NULL),
52+
__Pyx_CodeObjectCacheEntry(8, NULL),
53+
__Pyx_CodeObjectCacheEntry(9, NULL),
54+
__Pyx_CodeObjectCacheEntry(12, NULL),
55+
]
56+
return [ __pyx_bisect_code_objects(cache, 6, i)
57+
for i in indices ]
58+
59+
### Python level tests
60+
61+
import sys
62+
63+
def tb():
64+
return sys.exc_info()[-1]
65+
66+
def raise_keyerror():
67+
raise KeyError
68+
69+
def check_code_object_identity_recursively(tb1, tb2):
70+
if tb1 is None or tb2 is None:
71+
return
72+
code1, code2 = tb1.tb_frame.f_code, tb2.tb_frame.f_code
73+
if code1 is not code2:
74+
print('%s != %s' % (code1, code2))
75+
check_code_object_identity_recursively(tb1.tb_next, tb2.tb_next)
76+
77+
def assert_simple_code_object_reuse():
78+
"""
79+
>>> try: assert_simple_code_object_reuse()
80+
... except KeyError: t1 = tb()
81+
>>> try: assert_simple_code_object_reuse()
82+
... except KeyError: t2 = tb()
83+
>>> check_code_object_identity_recursively(t1.tb_next, t2.tb_next)
84+
"""
85+
raise KeyError
86+
87+
def assert_multi_step_code_object_reuse(recursions=0):
88+
"""
89+
>>> for depth in range(5):
90+
... try: assert_multi_step_code_object_reuse(depth)
91+
... except KeyError: t1 = tb()
92+
... try: assert_multi_step_code_object_reuse(depth)
93+
... except KeyError: t2 = tb()
94+
... check_code_object_identity_recursively(t1.tb_next, t2.tb_next)
95+
"""
96+
if recursions:
97+
assert_multi_step_code_object_reuse(recursions-1)
98+
else:
99+
raise_keyerror()

0 commit comments

Comments
 (0)