Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ typedef uint16_t _Py_CODEUNIT;
PyObject *co_exceptiontable; /* Byte string encoding exception handling \
table */ \
int co_flags; /* CO_..., see below */ \
int co_warmup; /* Warmup counter for quickening */ \
short co_warmup; /* Warmup counter for quickening */ \
short _co_linearray_entry_size; /* Size of each entry in _co_linearray */ \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal since you were able to squeeze this in here, but we might want to consider making this the first byte of the line array (or a flag on the code object) if we end up getting rid of co_warmup later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally made it a bit in co_flags but I'm worried that might be considered an API change by some.

\
/* The rest are not so impactful on performance. */ \
int co_argcount; /* #arguments, except *args */ \
Expand Down Expand Up @@ -90,6 +91,7 @@ typedef uint16_t _Py_CODEUNIT;
PyObject *co_weakreflist; /* to support weakrefs to code objects */ \
void *_co_code; /* cached co_code object/attribute */ \
int _co_firsttraceable; /* index of first traceable instruction */ \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed when this was added, but maybe we could move this member up with the other ints to plug the hole in the struct?

Copy link
Member Author

@markshannon markshannon Jun 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not without breaking code that accesses co_name and other nearby fields.
In 3.12, yes we can. Although it looks like we might just drop _co_firsttraceable #93818 (comment)

char *_co_linearray; /* array of line offsets */ \
/* Scratch space for extra data relating to the code object. \
Type is a void* to keep the format private in codeobject.c to force \
people to go through the proper APIs. */ \
Expand Down
28 changes: 28 additions & 0 deletions Include/internal/pycore_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,34 @@ adaptive_counter_backoff(uint16_t counter) {
}


/* Line array cache for tracing */

extern int _PyCode_CreateLineArray(PyCodeObject *co);

static inline int
_PyCode_InitLineArray(PyCodeObject *co)
{
if (co->_co_linearray) {
return 0;
}
return _PyCode_CreateLineArray(co);
}

static inline int
_PyCode_LineNumberFromArray(PyCodeObject *co, int index)
{
assert(co->_co_linearray != NULL);
assert(index >= 0);
if (co->_co_linearray_entry_size == 2) {
return ((int16_t *)co->_co_linearray)[index];
}
else {
assert(co->_co_linearray_entry_size == 4);
return ((int32_t *)co->_co_linearray)[index];
}
}


#ifdef __cplusplus
}
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Lazily create a table mapping bytecode offsets to line numbers to speed up
calculation of line numbers when tracing.
64 changes: 64 additions & 0 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
co->_co_code = NULL;

co->co_warmup = QUICKENING_INITIAL_WARMUP_VALUE;
co->_co_linearray_entry_size = 0;
co->_co_linearray = NULL;
memcpy(_PyCode_CODE(co), PyBytes_AS_STRING(con->code),
PyBytes_GET_SIZE(con->code));
int entry_point = 0;
Expand Down Expand Up @@ -701,13 +703,68 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
lnotab_notes.txt for the details of the lnotab representation.
*/


int
_PyCode_CreateLineArray(PyCodeObject *co)
{
assert(co->_co_linearray == NULL);
PyCodeAddressRange bounds;
int size;
int max_line = 0;
_PyCode_InitAddressRange(co, &bounds);
while (1) {
if (!_PyLineTable_NextAddressRange(&bounds)) {
break;
}
if (bounds.ar_line > max_line) {
max_line = bounds.ar_line;
}
}
if (max_line < (1 << 15)) {
size = 2;
}
else {
size = 4;
}
co->_co_linearray = PyMem_Malloc(Py_SIZE(co)*size);
if (co->_co_linearray == NULL) {
PyErr_NoMemory();
return -1;
}
co->_co_linearray_entry_size = size;
_PyCode_InitAddressRange(co, &bounds);
while (1) {
if (!_PyLineTable_NextAddressRange(&bounds)) {
break;
}
int addr = bounds.ar_start;
while (addr < bounds.ar_end) {
assert(addr < (int)(Py_SIZE(co) * sizeof(_Py_CODEUNIT)));
int index = addr / sizeof(_Py_CODEUNIT);
if (size == 2) {
assert(((int16_t)bounds.ar_line) == bounds.ar_line);
((int16_t *)co->_co_linearray)[index] = bounds.ar_line;
}
else {
assert(size == 4);
((int32_t *)co->_co_linearray)[index] = bounds.ar_line;
}
addr += sizeof(_Py_CODEUNIT);
}
}
return 0;
}

int
PyCode_Addr2Line(PyCodeObject *co, int addrq)
{
if (addrq < 0) {
return co->co_firstlineno;
}
assert(addrq >= 0 && addrq < _PyCode_NBYTES(co));
if (co->_co_linearray) {
return _PyCode_LineNumberFromArray(co, addrq / sizeof(_Py_CODEUNIT));
}
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(co, &bounds);
return _PyCode_CheckLineNumber(addrq, &bounds);
Expand Down Expand Up @@ -1547,6 +1604,9 @@ code_dealloc(PyCodeObject *co)
if (co->co_weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject*)co);
}
if (co->_co_linearray) {
PyMem_Free(co->_co_linearray);
}
if (co->co_warmup == 0) {
_Py_QuickenedCount--;
}
Expand Down Expand Up @@ -2104,6 +2164,10 @@ _PyStaticCode_Dealloc(PyCodeObject *co)
PyObject_ClearWeakRefs((PyObject *)co);
co->co_weakreflist = NULL;
}
if (co->_co_linearray) {
PyMem_Free(co->_co_linearray);
co->_co_linearray = NULL;
}
}

int
Expand Down
16 changes: 9 additions & 7 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -6805,9 +6805,10 @@ call_trace(Py_tracefunc func, PyObject *obj,
tstate->tracing_what = what;
PyThreadState_EnterTracing(tstate);
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
initialize_trace_info(&tstate->trace_info, frame);
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
f->f_lineno = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
if (_PyCode_InitLineArray(frame->f_code)) {
return -1;
}
f->f_lineno = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
result = func(obj, f, what, arg);
f->f_lineno = 0;
PyThreadState_LeaveTracing(tstate);
Expand Down Expand Up @@ -6844,16 +6845,17 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
represents a jump backwards, update the frame's line number and
then call the trace function if we're tracing source lines.
*/
initialize_trace_info(&tstate->trace_info, frame);
if (_PyCode_InitLineArray(frame->f_code)) {
return -1;
}
int lastline;
if (instr_prev <= frame->f_code->_co_firsttraceable) {
lastline = -1;
}
else {
lastline = _PyCode_CheckLineNumber(instr_prev*sizeof(_Py_CODEUNIT), &tstate->trace_info.bounds);
lastline = _PyCode_LineNumberFromArray(frame->f_code, instr_prev);
}
int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT);
int line = _PyCode_CheckLineNumber(addr, &tstate->trace_info.bounds);
int line = _PyCode_LineNumberFromArray(frame->f_code, _PyInterpreterFrame_LASTI(frame));
PyFrameObject *f = _PyFrame_GetFrameObject(frame);
if (f == NULL) {
return -1;
Expand Down
2 changes: 2 additions & 0 deletions Tools/scripts/deepfreeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
self.write(f".co_exceptiontable = {co_exceptiontable},")
self.field(code, "co_flags")
self.write(".co_warmup = QUICKENING_INITIAL_WARMUP_VALUE,")
self.write("._co_linearray_entry_size = 0,")
self.field(code, "co_argcount")
self.field(code, "co_posonlyargcount")
self.field(code, "co_kwonlyargcount")
Expand All @@ -269,6 +270,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
self.write(f".co_name = {co_name},")
self.write(f".co_qualname = {co_qualname},")
self.write(f".co_linetable = {co_linetable},")
self.write("._co_linearray = NULL,")
self.write(f".co_code_adaptive = {co_code_adaptive},")
for i, op in enumerate(code.co_code[::2]):
if op == RESUME:
Expand Down