bpo-42246: Partial implementation of PEP 626. (GH-23113)

* Implement new line number table format, as defined in PEP 626.
This commit is contained in:
Mark Shannon 2020-11-12 09:43:29 +00:00 committed by GitHub
parent cda99b4022
commit 877df851c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 5364 additions and 5000 deletions

View File

@ -38,7 +38,7 @@ struct PyCodeObject {
Py_ssize_t *co_cell2arg; /* Maps cell vars which are arguments. */ Py_ssize_t *co_cell2arg; /* Maps cell vars which are arguments. */
PyObject *co_filename; /* unicode (where it was loaded from) */ PyObject *co_filename; /* unicode (where it was loaded from) */
PyObject *co_name; /* unicode (name, for reference) */ PyObject *co_name; /* unicode (name, for reference) */
PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See PyObject *co_linetable; /* string (encoding addr<->lineno mapping) See
Objects/lnotab_notes.txt for details. */ Objects/lnotab_notes.txt for details. */
void *co_zombieframe; /* for optimization only (see frameobject.c) */ void *co_zombieframe; /* for optimization only (see frameobject.c) */
PyObject *co_weakreflist; /* to support weakrefs to code objects */ PyObject *co_weakreflist; /* to support weakrefs to code objects */
@ -135,16 +135,18 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno);
PyAPI_FUNC(int) PyCode_Addr2Line(PyCodeObject *, int); PyAPI_FUNC(int) PyCode_Addr2Line(PyCodeObject *, int);
/* for internal use only */ /* for internal use only */
typedef struct _addr_pair { typedef struct _line_offsets {
int ap_lower; int ar_start;
int ap_upper; int ar_end;
} PyAddrPair; int ar_line;
int ar_computed_line;
char *lo_next;
} PyCodeAddressRange;
/* Update *bounds to describe the first and one-past-the-last instructions in the /* Update *bounds to describe the first and one-past-the-last instructions in the
same line as lasti. Return the number of that line. same line as lasti. Return the number of that line.
*/ */
PyAPI_FUNC(int) _PyCode_CheckLineNumber(PyCodeObject* co, PyAPI_FUNC(int) _PyCode_CheckLineNumber(int lasti, PyCodeAddressRange *bounds);
int lasti, PyAddrPair *bounds);
/* Create a comparable key used to compare constants taking in account the /* Create a comparable key used to compare constants taking in account the
* object type. It is used to make sure types are not coerced (e.g., float and * object type. It is used to make sure types are not coerced (e.g., float and
@ -163,3 +165,15 @@ PyAPI_FUNC(int) _PyCode_GetExtra(PyObject *code, Py_ssize_t index,
void **extra); void **extra);
PyAPI_FUNC(int) _PyCode_SetExtra(PyObject *code, Py_ssize_t index, PyAPI_FUNC(int) _PyCode_SetExtra(PyObject *code, Py_ssize_t index,
void *extra); void *extra);
/** API for initializing the line number table. */
int _PyCode_InitAddressRange(PyCodeObject* co, PyCodeAddressRange *bounds);
/** Out of process API for initializing the line number table. */
void PyLineTable_InitAddressRange(char *linetable, int firstlineno, PyCodeAddressRange *range);
/** API for traversing the line number table. */
int PyLineTable_NextAddressRange(PyCodeAddressRange *range);
int PyLineTable_PreviousAddressRange(PyCodeAddressRange *range);

View File

@ -449,32 +449,15 @@ def findlabels(code):
def findlinestarts(code): def findlinestarts(code):
"""Find the offsets in a byte code which are start of lines in the source. """Find the offsets in a byte code which are start of lines in the source.
Generate pairs (offset, lineno) as described in Python/compile.c. Generate pairs (offset, lineno)
""" """
byte_increments = code.co_lnotab[0::2] lastline = None
line_increments = code.co_lnotab[1::2] for start, end, line in code.co_lines():
bytecode_len = len(code.co_code) if line is not None and line != lastline:
lastline = line
yield start, line
return
lastlineno = None
lineno = code.co_firstlineno
addr = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
if byte_incr:
if lineno != lastlineno:
yield (addr, lineno)
lastlineno = lineno
addr += byte_incr
if addr >= bytecode_len:
# The rest of the lnotab byte offsets are past the end of
# the bytecode, so the lines were optimized away.
return
if line_incr >= 0x80:
# line_increments is an array of 8-bit signed integers
line_incr -= 0x100
lineno += line_incr
if lineno != lastlineno:
yield (addr, lineno)
class Bytecode: class Bytecode:
"""The bytecode operations of a piece of code """The bytecode operations of a piece of code

View File

@ -258,7 +258,7 @@ class CodeTest(unittest.TestCase):
("co_cellvars", ("cellvar",)), ("co_cellvars", ("cellvar",)),
("co_filename", "newfilename"), ("co_filename", "newfilename"),
("co_name", "newname"), ("co_name", "newname"),
("co_lnotab", code2.co_lnotab), ("co_linetable", code2.co_linetable),
): ):
with self.subTest(attr=attr, value=value): with self.subTest(attr=attr, value=value):
new_code = code.replace(**{attr: value}) new_code = code.replace(**{attr: value})

View File

@ -155,8 +155,8 @@ if 1:
def test_leading_newlines(self): def test_leading_newlines(self):
s256 = "".join(["\n"] * 256 + ["spam"]) s256 = "".join(["\n"] * 256 + ["spam"])
co = compile(s256, 'fn', 'exec') co = compile(s256, 'fn', 'exec')
self.assertEqual(co.co_firstlineno, 257) self.assertEqual(co.co_firstlineno, 1)
self.assertEqual(co.co_lnotab, bytes()) self.assertEqual(list(co.co_lines()), [(0, 4, 257), (4, 8, None)])
def test_literals_with_leading_zeroes(self): def test_literals_with_leading_zeroes(self):
for arg in ["077787", "0xj", "0x.", "0e", "090000000000000", for arg in ["077787", "0xj", "0x.", "0e", "090000000000000",

View File

@ -27,7 +27,7 @@ class OpcodeTest(unittest.TestCase):
with open(ann_module.__file__) as f: with open(ann_module.__file__) as f:
txt = f.read() txt = f.read()
co = compile(txt, ann_module.__file__, 'exec') co = compile(txt, ann_module.__file__, 'exec')
self.assertEqual(co.co_firstlineno, 3) self.assertEqual(co.co_firstlineno, 1)
except OSError: except OSError:
pass pass

View File

@ -1645,9 +1645,10 @@ def bœr():
'debug doesnotexist', 'debug doesnotexist',
'c', 'c',
]) ])
stdout, _ = self.run_pdb_script('', commands + '\n') stdout, _ = self.run_pdb_script('pass', commands + '\n')
self.assertEqual(stdout.splitlines()[1:], [ self.assertEqual(stdout.splitlines()[1:], [
'-> pass',
'(Pdb) *** SyntaxError: unexpected EOF while parsing', '(Pdb) *** SyntaxError: unexpected EOF while parsing',
'(Pdb) ENTERING RECURSIVE DEBUGGER', '(Pdb) ENTERING RECURSIVE DEBUGGER',

View File

@ -220,8 +220,7 @@ ireturn_example.events = [(0, 'call'),
(2, 'line'), (2, 'line'),
(3, 'line'), (3, 'line'),
(4, 'line'), (4, 'line'),
(6, 'line'), (4, 'return')]
(6, 'return')]
# Tight loop with while(1) example (SF #765624) # Tight loop with while(1) example (SF #765624)
def tightloop_example(): def tightloop_example():
@ -602,6 +601,17 @@ class TraceTestCase(unittest.TestCase):
self.compare_events(doit_async.__code__.co_firstlineno, self.compare_events(doit_async.__code__.co_firstlineno,
tracer.events, events) tracer.events, events)
def test_21_repeated_pass(self):
def func():
pass
pass
self.run_and_compare(func,
[(0, 'call'),
(1, 'line'),
(2, 'line'),
(2, 'return')])
def test_loop_in_try_except(self): def test_loop_in_try_except(self):
# https://bugs.python.org/issue41670 # https://bugs.python.org/issue41670
@ -766,7 +776,7 @@ class JumpTracer:
if (self.firstLine is None and frame.f_code == self.code and if (self.firstLine is None and frame.f_code == self.code and
event == 'line'): event == 'line'):
self.firstLine = frame.f_lineno - 1 self.firstLine = frame.f_lineno - 1
if (event == self.event and self.firstLine and if (event == self.event and self.firstLine is not None and
frame.f_lineno == self.firstLine + self.jumpFrom): frame.f_lineno == self.firstLine + self.jumpFrom):
f = frame f = frame
while f is not None and f.f_code != self.code: while f is not None and f.f_code != self.code:
@ -1540,7 +1550,7 @@ output.append(4)
""", "<fake module>", "exec") """, "<fake module>", "exec")
class fake_function: class fake_function:
__code__ = code __code__ = code
tracer = JumpTracer(fake_function, 2, 0) tracer = JumpTracer(fake_function, 4, 1)
sys.settrace(tracer.trace) sys.settrace(tracer.trace)
namespace = {"output": []} namespace = {"output": []}
exec(code, namespace) exec(code, namespace)

View File

@ -0,0 +1,2 @@
Improved accuracy of line tracing events and f_lineno attribute of Frame
objects. See PEP 626 for details.

View File

@ -5,7 +5,7 @@ preserve
PyDoc_STRVAR(code_new__doc__, PyDoc_STRVAR(code_new__doc__,
"code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize,\n" "code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize,\n"
" flags, codestring, constants, names, varnames, filename, name,\n" " flags, codestring, constants, names, varnames, filename, name,\n"
" firstlineno, lnotab, freevars=(), cellvars=(), /)\n" " firstlineno, linetable, freevars=(), cellvars=(), /)\n"
"--\n" "--\n"
"\n" "\n"
"Create a code object. Not for the faint of heart."); "Create a code object. Not for the faint of heart.");
@ -15,7 +15,7 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
int kwonlyargcount, int nlocals, int stacksize, int flags, int kwonlyargcount, int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names, PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *filename, PyObject *name, PyObject *varnames, PyObject *filename, PyObject *name,
int firstlineno, PyObject *lnotab, PyObject *freevars, int firstlineno, PyObject *linetable, PyObject *freevars,
PyObject *cellvars); PyObject *cellvars);
static PyObject * static PyObject *
@ -35,7 +35,7 @@ code_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
PyObject *filename; PyObject *filename;
PyObject *name; PyObject *name;
int firstlineno; int firstlineno;
PyObject *lnotab; PyObject *linetable;
PyObject *freevars = NULL; PyObject *freevars = NULL;
PyObject *cellvars = NULL; PyObject *cellvars = NULL;
@ -114,7 +114,7 @@ code_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
_PyArg_BadArgument("code", "argument 14", "bytes", PyTuple_GET_ITEM(args, 13)); _PyArg_BadArgument("code", "argument 14", "bytes", PyTuple_GET_ITEM(args, 13));
goto exit; goto exit;
} }
lnotab = PyTuple_GET_ITEM(args, 13); linetable = PyTuple_GET_ITEM(args, 13);
if (PyTuple_GET_SIZE(args) < 15) { if (PyTuple_GET_SIZE(args) < 15) {
goto skip_optional; goto skip_optional;
} }
@ -132,7 +132,7 @@ code_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
} }
cellvars = PyTuple_GET_ITEM(args, 15); cellvars = PyTuple_GET_ITEM(args, 15);
skip_optional: skip_optional:
return_value = code_new_impl(type, argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, lnotab, freevars, cellvars); return_value = code_new_impl(type, argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, flags, code, consts, names, varnames, filename, name, firstlineno, linetable, freevars, cellvars);
exit: exit:
return return_value; return return_value;
@ -144,7 +144,7 @@ PyDoc_STRVAR(code_replace__doc__,
" co_flags=-1, co_firstlineno=-1, co_code=None, co_consts=None,\n" " co_flags=-1, co_firstlineno=-1, co_code=None, co_consts=None,\n"
" co_names=None, co_varnames=None, co_freevars=None,\n" " co_names=None, co_varnames=None, co_freevars=None,\n"
" co_cellvars=None, co_filename=None, co_name=None,\n" " co_cellvars=None, co_filename=None, co_name=None,\n"
" co_lnotab=None)\n" " co_linetable=None)\n"
"--\n" "--\n"
"\n" "\n"
"Return a copy of the code object with new values for the specified fields."); "Return a copy of the code object with new values for the specified fields.");
@ -160,13 +160,13 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
PyObject *co_consts, PyObject *co_names, PyObject *co_consts, PyObject *co_names,
PyObject *co_varnames, PyObject *co_freevars, PyObject *co_varnames, PyObject *co_freevars,
PyObject *co_cellvars, PyObject *co_filename, PyObject *co_cellvars, PyObject *co_filename,
PyObject *co_name, PyBytesObject *co_lnotab); PyObject *co_name, PyBytesObject *co_linetable);
static PyObject * static PyObject *
code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{ {
PyObject *return_value = NULL; PyObject *return_value = NULL;
static const char * const _keywords[] = {"co_argcount", "co_posonlyargcount", "co_kwonlyargcount", "co_nlocals", "co_stacksize", "co_flags", "co_firstlineno", "co_code", "co_consts", "co_names", "co_varnames", "co_freevars", "co_cellvars", "co_filename", "co_name", "co_lnotab", NULL}; static const char * const _keywords[] = {"co_argcount", "co_posonlyargcount", "co_kwonlyargcount", "co_nlocals", "co_stacksize", "co_flags", "co_firstlineno", "co_code", "co_consts", "co_names", "co_varnames", "co_freevars", "co_cellvars", "co_filename", "co_name", "co_linetable", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "replace", 0}; static _PyArg_Parser _parser = {NULL, _keywords, "replace", 0};
PyObject *argsbuf[16]; PyObject *argsbuf[16];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
@ -185,7 +185,7 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
PyObject *co_cellvars = self->co_cellvars; PyObject *co_cellvars = self->co_cellvars;
PyObject *co_filename = self->co_filename; PyObject *co_filename = self->co_filename;
PyObject *co_name = self->co_name; PyObject *co_name = self->co_name;
PyBytesObject *co_lnotab = (PyBytesObject *)self->co_lnotab; PyBytesObject *co_linetable = (PyBytesObject *)self->co_linetable;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf); args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 0, 0, argsbuf);
if (!args) { if (!args) {
@ -344,14 +344,14 @@ code_replace(PyCodeObject *self, PyObject *const *args, Py_ssize_t nargs, PyObje
} }
} }
if (!PyBytes_Check(args[15])) { if (!PyBytes_Check(args[15])) {
_PyArg_BadArgument("replace", "argument 'co_lnotab'", "bytes", args[15]); _PyArg_BadArgument("replace", "argument 'co_linetable'", "bytes", args[15]);
goto exit; goto exit;
} }
co_lnotab = (PyBytesObject *)args[15]; co_linetable = (PyBytesObject *)args[15];
skip_optional_kwonly: skip_optional_kwonly:
return_value = code_replace_impl(self, co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals, co_stacksize, co_flags, co_firstlineno, co_code, co_consts, co_names, co_varnames, co_freevars, co_cellvars, co_filename, co_name, co_lnotab); return_value = code_replace_impl(self, co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals, co_stacksize, co_flags, co_firstlineno, co_code, co_consts, co_names, co_varnames, co_freevars, co_cellvars, co_filename, co_name, co_linetable);
exit: exit:
return return_value; return return_value;
} }
/*[clinic end generated code: output=18c31941ec09e9ca input=a9049054013a1b77]*/ /*[clinic end generated code: output=e3091c7baaaaa420 input=a9049054013a1b77]*/

View File

@ -119,7 +119,7 @@ PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,
PyObject *code, PyObject *consts, PyObject *names, PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *varnames, PyObject *freevars, PyObject *cellvars,
PyObject *filename, PyObject *name, int firstlineno, PyObject *filename, PyObject *name, int firstlineno,
PyObject *lnotab) PyObject *linetable)
{ {
PyCodeObject *co; PyCodeObject *co;
Py_ssize_t *cell2arg = NULL; Py_ssize_t *cell2arg = NULL;
@ -137,7 +137,7 @@ PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,
cellvars == NULL || !PyTuple_Check(cellvars) || cellvars == NULL || !PyTuple_Check(cellvars) ||
name == NULL || !PyUnicode_Check(name) || name == NULL || !PyUnicode_Check(name) ||
filename == NULL || !PyUnicode_Check(filename) || filename == NULL || !PyUnicode_Check(filename) ||
lnotab == NULL || !PyBytes_Check(lnotab)) { linetable == NULL || !PyBytes_Check(linetable)) {
PyErr_BadInternalCall(); PyErr_BadInternalCall();
return NULL; return NULL;
} }
@ -258,8 +258,8 @@ PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,
Py_INCREF(name); Py_INCREF(name);
co->co_name = name; co->co_name = name;
co->co_firstlineno = firstlineno; co->co_firstlineno = firstlineno;
Py_INCREF(lnotab); Py_INCREF(linetable);
co->co_lnotab = lnotab; co->co_linetable = linetable;
co->co_zombieframe = NULL; co->co_zombieframe = NULL;
co->co_weakreflist = NULL; co->co_weakreflist = NULL;
co->co_extra = NULL; co->co_extra = NULL;
@ -277,12 +277,12 @@ PyCode_New(int argcount, int kwonlyargcount,
PyObject *code, PyObject *consts, PyObject *names, PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *varnames, PyObject *freevars, PyObject *cellvars,
PyObject *filename, PyObject *name, int firstlineno, PyObject *filename, PyObject *name, int firstlineno,
PyObject *lnotab) PyObject *linetable)
{ {
return PyCode_NewWithPosOnlyArgs(argcount, 0, kwonlyargcount, nlocals, return PyCode_NewWithPosOnlyArgs(argcount, 0, kwonlyargcount, nlocals,
stacksize, flags, code, consts, names, stacksize, flags, code, consts, names,
varnames, freevars, cellvars, filename, varnames, freevars, cellvars, filename,
name, firstlineno, lnotab); name, firstlineno, linetable);
} }
int int
@ -369,7 +369,7 @@ PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno)
filename_ob, /* filename */ filename_ob, /* filename */
funcname_ob, /* name */ funcname_ob, /* name */
firstlineno, /* firstlineno */ firstlineno, /* firstlineno */
emptystring /* lnotab */ emptystring /* linetable */
); );
failed: failed:
@ -395,11 +395,89 @@ static PyMemberDef code_memberlist[] = {
{"co_cellvars", T_OBJECT, OFF(co_cellvars), READONLY}, {"co_cellvars", T_OBJECT, OFF(co_cellvars), READONLY},
{"co_filename", T_OBJECT, OFF(co_filename), READONLY}, {"co_filename", T_OBJECT, OFF(co_filename), READONLY},
{"co_name", T_OBJECT, OFF(co_name), READONLY}, {"co_name", T_OBJECT, OFF(co_name), READONLY},
{"co_firstlineno", T_INT, OFF(co_firstlineno), READONLY}, {"co_firstlineno", T_INT, OFF(co_firstlineno), READONLY},
{"co_lnotab", T_OBJECT, OFF(co_lnotab), READONLY}, {"co_linetable", T_OBJECT, OFF(co_linetable), READONLY},
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
static int
emit_pair(PyObject **bytes, int *offset, int a, int b)
{
Py_ssize_t len = PyBytes_GET_SIZE(*bytes);
if (*offset + 2 >= len) {
if (_PyBytes_Resize(bytes, len * 2) < 0)
return 0;
}
unsigned char *lnotab = (unsigned char *)
PyBytes_AS_STRING(*bytes) + *offset;
*lnotab++ = a;
*lnotab++ = b;
*offset += 2;
return 1;
}
static int
emit_delta(PyObject **bytes, int bdelta, int ldelta, int *offset)
{
while (bdelta > 255) {
if (!emit_pair(bytes, offset, 255, 0)) {
return 0;
}
bdelta -= 255;
}
while (ldelta > 127) {
if (!emit_pair(bytes, offset, bdelta, 127)) {
return 0;
}
bdelta = 0;
ldelta -= 127;
}
while (ldelta < -128) {
if (!emit_pair(bytes, offset, bdelta, -128)) {
return 0;
}
bdelta = 0;
ldelta += 128;
}
return emit_pair(bytes, offset, bdelta, ldelta);
}
static PyObject *
code_getlnotab(PyCodeObject *code, void *closure)
{
PyCodeAddressRange bounds;
PyObject *bytes;
int table_offset = 0;
int code_offset = 0;
int line = code->co_firstlineno;
bytes = PyBytes_FromStringAndSize(NULL, 64);
if (bytes == NULL) {
return NULL;
}
_PyCode_InitAddressRange(code, &bounds);
while (PyLineTable_NextAddressRange(&bounds)) {
if (bounds.ar_computed_line != line) {
int bdelta = bounds.ar_start - code_offset;
int ldelta = bounds.ar_computed_line - line;
if (!emit_delta(&bytes, bdelta, ldelta, &table_offset)) {
Py_DECREF(bytes);
return NULL;
}
code_offset = bounds.ar_start;
line = bounds.ar_computed_line;
}
}
_PyBytes_Resize(&bytes, table_offset);
return bytes;
}
static PyGetSetDef code_getsetlist[] = {
{"co_lnotab", (getter)code_getlnotab, NULL, NULL},
{0}
};
/* Helper for code_new: return a shallow copy of a tuple that is /* Helper for code_new: return a shallow copy of a tuple that is
guaranteed to contain exact strings, by converting string subclasses guaranteed to contain exact strings, by converting string subclasses
to exact strings and complaining if a non-string is found. */ to exact strings and complaining if a non-string is found. */
@ -459,7 +537,7 @@ code.__new__ as code_new
filename: unicode filename: unicode
name: unicode name: unicode
firstlineno: int firstlineno: int
lnotab: object(subclass_of="&PyBytes_Type") linetable: object(subclass_of="&PyBytes_Type")
freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = () freevars: object(subclass_of="&PyTuple_Type", c_default="NULL") = ()
cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = () cellvars: object(subclass_of="&PyTuple_Type", c_default="NULL") = ()
/ /
@ -472,9 +550,9 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
int kwonlyargcount, int nlocals, int stacksize, int flags, int kwonlyargcount, int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names, PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *filename, PyObject *name, PyObject *varnames, PyObject *filename, PyObject *name,
int firstlineno, PyObject *lnotab, PyObject *freevars, int firstlineno, PyObject *linetable, PyObject *freevars,
PyObject *cellvars) PyObject *cellvars)
/*[clinic end generated code: output=612aac5395830184 input=85e678ea4178f234]*/ /*[clinic end generated code: output=42c1839b082ba293 input=0ec80da632b99f57]*/
{ {
PyObject *co = NULL; PyObject *co = NULL;
PyObject *ournames = NULL; PyObject *ournames = NULL;
@ -540,7 +618,7 @@ code_new_impl(PyTypeObject *type, int argcount, int posonlyargcount,
code, consts, ournames, code, consts, ournames,
ourvarnames, ourfreevars, ourvarnames, ourfreevars,
ourcellvars, filename, ourcellvars, filename,
name, firstlineno, lnotab); name, firstlineno, linetable);
cleanup: cleanup:
Py_XDECREF(ournames); Py_XDECREF(ournames);
Py_XDECREF(ourvarnames); Py_XDECREF(ourvarnames);
@ -584,7 +662,7 @@ code_dealloc(PyCodeObject *co)
Py_XDECREF(co->co_cellvars); Py_XDECREF(co->co_cellvars);
Py_XDECREF(co->co_filename); Py_XDECREF(co->co_filename);
Py_XDECREF(co->co_name); Py_XDECREF(co->co_name);
Py_XDECREF(co->co_lnotab); Py_XDECREF(co->co_linetable);
if (co->co_cell2arg != NULL) if (co->co_cell2arg != NULL)
PyMem_FREE(co->co_cell2arg); PyMem_FREE(co->co_cell2arg);
if (co->co_zombieframe != NULL) if (co->co_zombieframe != NULL)
@ -636,7 +714,7 @@ code.replace
co_cellvars: object(subclass_of="&PyTuple_Type", c_default="self->co_cellvars") = None co_cellvars: object(subclass_of="&PyTuple_Type", c_default="self->co_cellvars") = None
co_filename: unicode(c_default="self->co_filename") = None co_filename: unicode(c_default="self->co_filename") = None
co_name: unicode(c_default="self->co_name") = None co_name: unicode(c_default="self->co_name") = None
co_lnotab: PyBytesObject(c_default="(PyBytesObject *)self->co_lnotab") = None co_linetable: PyBytesObject(c_default="(PyBytesObject *)self->co_linetable") = None
Return a copy of the code object with new values for the specified fields. Return a copy of the code object with new values for the specified fields.
[clinic start generated code]*/ [clinic start generated code]*/
@ -649,8 +727,8 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
PyObject *co_consts, PyObject *co_names, PyObject *co_consts, PyObject *co_names,
PyObject *co_varnames, PyObject *co_freevars, PyObject *co_varnames, PyObject *co_freevars,
PyObject *co_cellvars, PyObject *co_filename, PyObject *co_cellvars, PyObject *co_filename,
PyObject *co_name, PyBytesObject *co_lnotab) PyObject *co_name, PyBytesObject *co_linetable)
/*[clinic end generated code: output=25c8e303913bcace input=d9051bc8f24e6b28]*/ /*[clinic end generated code: output=50d77e668d3b449b input=a5f997b173d7f636]*/
{ {
#define CHECK_INT_ARG(ARG) \ #define CHECK_INT_ARG(ARG) \
if (ARG < 0) { \ if (ARG < 0) { \
@ -680,7 +758,7 @@ code_replace_impl(PyCodeObject *self, int co_argcount,
co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals, co_argcount, co_posonlyargcount, co_kwonlyargcount, co_nlocals,
co_stacksize, co_flags, (PyObject*)co_code, co_consts, co_names, co_stacksize, co_flags, (PyObject*)co_code, co_consts, co_names,
co_varnames, co_freevars, co_cellvars, co_filename, co_name, co_varnames, co_freevars, co_cellvars, co_filename, co_name,
co_firstlineno, (PyObject*)co_lnotab); co_firstlineno, (PyObject*)co_linetable);
} }
static PyObject * static PyObject *
@ -933,10 +1011,189 @@ code_hash(PyCodeObject *co)
return h; return h;
} }
typedef struct {
PyObject_HEAD
PyCodeObject *li_code;
PyCodeAddressRange li_line;
char *li_end;
} lineiterator;
static void
lineiter_dealloc(lineiterator *li)
{
Py_DECREF(li->li_code);
Py_TYPE(li)->tp_free(li);
}
static PyObject *
lineiter_next(lineiterator *li)
{
PyCodeAddressRange *bounds = &li->li_line;
if (!PyLineTable_NextAddressRange(bounds)) {
return NULL;
}
PyObject *start = NULL;
PyObject *end = NULL;
PyObject *line = NULL;
PyObject *result = PyTuple_New(3);
start = PyLong_FromLong(bounds->ar_start);
end = PyLong_FromLong(bounds->ar_end);
if (bounds->ar_line < 0) {
Py_INCREF(Py_None);
line = Py_None;
}
else {
line = PyLong_FromLong(bounds->ar_line);
}
if (result == NULL || start == NULL || end == NULL || line == NULL) {
goto error;
}
PyTuple_SET_ITEM(result, 0, start);
PyTuple_SET_ITEM(result, 1, end);
PyTuple_SET_ITEM(result, 2, line);
return result;
error:
Py_XDECREF(start);
Py_XDECREF(end);
Py_XDECREF(line);
Py_XDECREF(result);
return result;
}
static PyTypeObject LineIterator = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"line_iterator", /* tp_name */
sizeof(lineiterator), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)lineiter_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc)lineiter_next, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
PyObject_Del, /* tp_free */
};
static PyObject *
code_linesiterator(PyCodeObject *code, PyObject *Py_UNUSED(args))
{
lineiterator *li = (lineiterator *)PyType_GenericAlloc(&LineIterator, 0);
if (li == NULL) {
return NULL;
}
Py_INCREF(code);
li->li_code = code;
_PyCode_InitAddressRange(code, &li->li_line);
return (PyObject *)li;
}
static void
retreat(PyCodeAddressRange *bounds)
{
int ldelta = ((signed char *)bounds->lo_next)[-1];
if (ldelta == -128) {
ldelta = 0;
}
bounds->ar_computed_line -= ldelta;
bounds->lo_next -= 2;
bounds->ar_end = bounds->ar_start;
bounds->ar_start -= ((unsigned char *)bounds->lo_next)[-2];
ldelta = ((signed char *)bounds->lo_next)[-1];
if (ldelta == -128) {
bounds->ar_line = -1;
}
else {
bounds->ar_line = bounds->ar_computed_line;
}
}
static void
advance(PyCodeAddressRange *bounds)
{
bounds->ar_start = bounds->ar_end;
int delta = ((unsigned char *)bounds->lo_next)[0];
assert (delta < 255);
bounds->ar_end += delta;
int ldelta = ((signed char *)bounds->lo_next)[1];
bounds->lo_next += 2;
if (ldelta == -128) {
bounds->ar_line = -1;
}
else {
bounds->ar_computed_line += ldelta;
bounds->ar_line = bounds->ar_computed_line;
}
}
static inline int
at_end(PyCodeAddressRange *bounds) {
return ((unsigned char *)bounds->lo_next)[0] == 255;
}
int
PyLineTable_PreviousAddressRange(PyCodeAddressRange *range)
{
if (range->ar_start <= 0) {
return 0;
}
retreat(range);
while (range->ar_start == range->ar_end) {
assert(range->ar_start > 0);
retreat(range);
}
return 1;
}
int
PyLineTable_NextAddressRange(PyCodeAddressRange *range)
{
if (at_end(range)) {
return 0;
}
advance(range);
while (range->ar_start == range->ar_end) {
assert(!at_end(range));
advance(range);
}
return 1;
}
/* XXX code objects need to participate in GC? */ /* XXX code objects need to participate in GC? */
static struct PyMethodDef code_methods[] = { static struct PyMethodDef code_methods[] = {
{"__sizeof__", (PyCFunction)code_sizeof, METH_NOARGS}, {"__sizeof__", (PyCFunction)code_sizeof, METH_NOARGS},
{"co_lines", (PyCFunction)code_linesiterator, METH_NOARGS},
CODE_REPLACE_METHODDEF CODE_REPLACE_METHODDEF
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
@ -971,7 +1228,7 @@ PyTypeObject PyCode_Type = {
0, /* tp_iternext */ 0, /* tp_iternext */
code_methods, /* tp_methods */ code_methods, /* tp_methods */
code_memberlist, /* tp_members */ code_memberlist, /* tp_members */
0, /* tp_getset */ code_getsetlist, /* tp_getset */
0, /* tp_base */ 0, /* tp_base */
0, /* tp_dict */ 0, /* tp_dict */
0, /* tp_descr_get */ 0, /* tp_descr_get */
@ -982,78 +1239,55 @@ PyTypeObject PyCode_Type = {
code_new, /* tp_new */ code_new, /* tp_new */
}; };
/* Use co_lnotab to compute the line number from a bytecode index, addrq. See /* Use co_linetable to compute the line number from a bytecode index, addrq. See
lnotab_notes.txt for the details of the lnotab representation. lnotab_notes.txt for the details of the lnotab representation.
*/ */
int int
PyCode_Addr2Line(PyCodeObject *co, int addrq) PyCode_Addr2Line(PyCodeObject *co, int addrq)
{ {
Py_ssize_t size = PyBytes_Size(co->co_lnotab) / 2; if (addrq == -1) {
unsigned char *p = (unsigned char*)PyBytes_AsString(co->co_lnotab); return co->co_firstlineno;
int line = co->co_firstlineno;
int addr = 0;
while (--size >= 0) {
addr += *p++;
if (addr > addrq)
break;
line += (signed char)*p;
p++;
} }
return line; assert(addrq >= 0 && addrq < PyBytes_GET_SIZE(co->co_code));
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(co, &bounds);
return _PyCode_CheckLineNumber(addrq, &bounds);
}
void
PyLineTable_InitAddressRange(char *linetable, int firstlineno, PyCodeAddressRange *range)
{
range->lo_next = linetable;
range->ar_start = -1;
range->ar_end = 0;
range->ar_computed_line = range->ar_line = firstlineno;
}
int
_PyCode_InitAddressRange(PyCodeObject* co, PyCodeAddressRange *bounds)
{
char *linetable = PyBytes_AS_STRING(co->co_linetable);
PyLineTable_InitAddressRange(linetable, co->co_firstlineno, bounds);
return bounds->ar_line;
} }
/* Update *bounds to describe the first and one-past-the-last instructions in /* Update *bounds to describe the first and one-past-the-last instructions in
the same line as lasti. Return the number of that line. */ the same line as lasti. Return the number of that line, or -1 if lasti is out of bounds. */
int int
_PyCode_CheckLineNumber(PyCodeObject* co, int lasti, PyAddrPair *bounds) _PyCode_CheckLineNumber(int lasti, PyCodeAddressRange *bounds)
{ {
Py_ssize_t size; while (bounds->ar_end <= lasti) {
int addr, line; if (!PyLineTable_NextAddressRange(bounds)) {
unsigned char* p; return -1;
p = (unsigned char*)PyBytes_AS_STRING(co->co_lnotab);
size = PyBytes_GET_SIZE(co->co_lnotab) / 2;
addr = 0;
line = co->co_firstlineno;
assert(line > 0);
/* possible optimization: if f->f_lasti == instr_ub
(likely to be a common case) then we already know
instr_lb -- if we stored the matching value of p
somewhere we could skip the first while loop. */
/* See lnotab_notes.txt for the description of
co_lnotab. A point to remember: increments to p
come in (addr, line) pairs. */
bounds->ap_lower = 0;
while (size > 0) {
if (addr + *p > lasti)
break;
addr += *p++;
if ((signed char)*p)
bounds->ap_lower = addr;
line += (signed char)*p;
p++;
--size;
}
if (size > 0) {
while (--size >= 0) {
addr += *p++;
if ((signed char)*p)
break;
p++;
} }
bounds->ap_upper = addr;
} }
else { while (bounds->ar_start > lasti) {
bounds->ap_upper = INT_MAX; if (!PyLineTable_PreviousAddressRange(bounds)) {
return -1;
}
} }
return bounds->ar_line;
return line;
} }

View File

@ -249,36 +249,22 @@ explain_incompatible_block_stack(int64_t to_stack)
static int * static int *
marklines(PyCodeObject *code, int len) marklines(PyCodeObject *code, int len)
{ {
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(code, &bounds);
assert (bounds.ar_end == 0);
int *linestarts = PyMem_New(int, len); int *linestarts = PyMem_New(int, len);
if (linestarts == NULL) { if (linestarts == NULL) {
return NULL; return NULL;
} }
Py_ssize_t size = PyBytes_GET_SIZE(code->co_lnotab) / 2; for (int i = 0; i < len; i++) {
unsigned char *p = (unsigned char*)PyBytes_AS_STRING(code->co_lnotab); linestarts[i] = -1;
int line = code->co_firstlineno;
int addr = 0;
int index = 0;
while (--size >= 0) {
addr += *p++;
if (index*2 < addr) {
linestarts[index++] = line;
}
while (index*2 < addr) {
linestarts[index++] = -1;
if (index >= len) {
break;
}
}
line += (signed char)*p;
p++;
} }
if (index < len) {
linestarts[index++] = line; while (PyLineTable_NextAddressRange(&bounds)) {
assert(bounds.ar_start/2 < len);
linestarts[bounds.ar_start/2] = bounds.ar_line;
} }
while (index < len) {
linestarts[index++] = -1;
}
assert(index == len);
return linestarts; return linestarts;
} }
@ -925,7 +911,7 @@ _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
} }
f->f_lasti = -1; f->f_lasti = -1;
f->f_lineno = code->co_firstlineno; f->f_lineno = 0;
f->f_iblock = 0; f->f_iblock = 0;
f->f_state = FRAME_CREATED; f->f_state = FRAME_CREATED;
f->f_gen = NULL; f->f_gen = NULL;

View File

@ -1,11 +1,103 @@
All about co_lnotab, the line number table. Description of the internal format of the line number table
Conceptually, the line number table consists of a sequence of triples:
start-offset (inclusive), end-offset (exclusive), line-number.
Note that note all byte codes have a line number so we need handle `None` for the line-number.
However, storing the above sequence directly would be very inefficient as we would need 12 bytes per entry.
First of all, we can note that the end of one entry is the same as the start of the next, so we can overlap entries.
Secondly we also note that we don't really need arbitrary access to the sequence, so we can store deltas.
We just need to store (end - start, line delta) pairs. The start offset of the first entry is always zero.
Thirdly, most deltas are small, so we can use a single byte for each value, as long we allow several entries for the same line.
Consider the following table
Start End Line
0 6 1
6 50 2
50 350 7
350 360 No line number
360 376 8
376 380 208
Stripping the redundant ends gives:
End-Start Line-delta
6 +1
44 +1
300 +5
10 No line number
16 +1
4 +200
Note that the end - start value is always positive.
Finally in order, to fit into a single byte we need to convert start deltas to the range 0 <= delta <= 254,
and line deltas to the range -127 <= delta <= 127.
A line delta of -128 is used to indicate no line number.
A start delta of 255 is used as a sentinel to mark the end of the table.
Also note that a delta of zero indicates that there are no bytecodes in the given range,
which means can use an invalidate line number for that range.
Final form:
Start delta Line delta
6 +1
44 +1
254 +5
46 0
10 -128 (No line number, treated as a delta of zero)
16 +1
0 +127 (line 135, but the range is empty as no bytecodes are at line 135)
4 +73
255 (end mark) ---
Iterating over the table.
-------------------------
For the `co_lines` attribute we want to emit the full form, omitting the (350, 360, No line number) and empty entries.
The code is as follows:
def co_lines(code):
line = code.co_firstlineno
end = 0
table_iter = iter(code.internal_line_table):
for sdelta, ldelta in table_iter:
if sdelta == 255:
break
if ldelta == 0: # No change to line number, just accumulate changes to end
end += odelta
continue
start = end
end = start + sdelta
if ldelta == -128: # No valid line number -- skip entry
continue
line += ldelta
if end == start: # Empty range, omit.
continue
yield start, end, line
The historical co_lnotab format
-------------------------------
prior to 3.10 code objects stored a field named co_lnotab.
This was an array of unsigned bytes disguised as a Python bytes object.
The old co_lnotab did not account for the presence of bytecodes without a line number,
nor was it well suited to tracing as a number of workarounds were required.
The old format can still be accessed via `code.co_lnotab`, which is lazily computed from the new format.
Below is the description of the old co_lnotab format:
Code objects store a field named co_lnotab. This is an array of unsigned bytes
disguised as a Python bytes object. It is used to map bytecode offsets to
source code line #s for tracebacks and to identify line number boundaries for
line tracing. Because of internals of the peephole optimizer, it's possible
for lnotab to contain bytecode offsets that are no longer valid (for example
if the optimizer removed the last line in a function).
The array is conceptually a compressed list of The array is conceptually a compressed list of
(bytecode offset increment, line number increment) (bytecode offset increment, line number increment)

View File

@ -66,8 +66,8 @@ static void call_exc_trace(Py_tracefunc, PyObject *,
PyThreadState *, PyFrameObject *); PyThreadState *, PyFrameObject *);
static int maybe_call_line_trace(Py_tracefunc, PyObject *, static int maybe_call_line_trace(Py_tracefunc, PyObject *,
PyThreadState *, PyFrameObject *, PyThreadState *, PyFrameObject *,
int *, int *, int *); PyCodeAddressRange *, int *);
static void maybe_dtrace_line(PyFrameObject *, int *, int *, int *); static void maybe_dtrace_line(PyFrameObject *, PyCodeAddressRange *, int *);
static void dtrace_function_entry(PyFrameObject *); static void dtrace_function_entry(PyFrameObject *);
static void dtrace_function_return(PyFrameObject *); static void dtrace_function_return(PyFrameObject *);
@ -976,7 +976,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
is true when the line being executed has changed. The is true when the line being executed has changed. The
initial values are such as to make this false the first initial values are such as to make this false the first
time it is tested. */ time it is tested. */
int instr_ub = -1, instr_lb = 0, instr_prev = -1;
const _Py_CODEUNIT *first_instr; const _Py_CODEUNIT *first_instr;
PyObject *names; PyObject *names;
@ -1390,6 +1389,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
dtrace_function_entry(f); dtrace_function_entry(f);
co = f->f_code; co = f->f_code;
PyCodeAddressRange bounds;
_PyCode_InitAddressRange(co, &bounds);
int instr_prev = -1;
names = co->co_names; names = co->co_names;
consts = co->co_consts; consts = co->co_consts;
fastlocals = f->f_localsplus; fastlocals = f->f_localsplus;
@ -1514,7 +1517,7 @@ main_loop:
f->f_lasti = INSTR_OFFSET(); f->f_lasti = INSTR_OFFSET();
if (PyDTrace_LINE_ENABLED()) if (PyDTrace_LINE_ENABLED())
maybe_dtrace_line(f, &instr_lb, &instr_ub, &instr_prev); maybe_dtrace_line(f, &bounds, &instr_prev);
/* line-by-line tracing support */ /* line-by-line tracing support */
@ -1528,7 +1531,7 @@ main_loop:
err = maybe_call_line_trace(tstate->c_tracefunc, err = maybe_call_line_trace(tstate->c_tracefunc,
tstate->c_traceobj, tstate->c_traceobj,
tstate, f, tstate, f,
&instr_lb, &instr_ub, &instr_prev); &bounds, &instr_prev);
/* Reload possibly changed frame fields */ /* Reload possibly changed frame fields */
JUMPTO(f->f_lasti); JUMPTO(f->f_lasti);
stack_pointer = f->f_valuestack+f->f_stackdepth; stack_pointer = f->f_valuestack+f->f_stackdepth;
@ -4039,14 +4042,7 @@ exception_unwind:
PUSH(exc); PUSH(exc);
JUMPTO(handler); JUMPTO(handler);
if (_Py_TracingPossible(ceval2)) { if (_Py_TracingPossible(ceval2)) {
int needs_new_execution_window = (f->f_lasti < instr_lb || f->f_lasti >= instr_ub); instr_prev = INT_MAX;
int needs_line_update = (f->f_lasti == instr_lb || f->f_lasti < instr_prev);
/* Make sure that we trace line after exception if we are in a new execution
* window or we don't need a line update and we are not in the first instruction
* of the line. */
if (needs_new_execution_window || (!needs_line_update && instr_lb > 0)) {
instr_prev = INT_MAX;
}
} }
/* Resume normal execution */ /* Resume normal execution */
f->f_state = FRAME_EXECUTING; f->f_state = FRAME_EXECUTING;
@ -4960,7 +4956,7 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
static int static int
maybe_call_line_trace(Py_tracefunc func, PyObject *obj, maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
PyThreadState *tstate, PyFrameObject *frame, PyThreadState *tstate, PyFrameObject *frame,
int *instr_lb, int *instr_ub, int *instr_prev) PyCodeAddressRange *bounds, int *instr_prev)
{ {
int result = 0; int result = 0;
int line = frame->f_lineno; int line = frame->f_lineno;
@ -4968,21 +4964,17 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
/* If the last instruction executed isn't in the current /* If the last instruction executed isn't in the current
instruction window, reset the window. instruction window, reset the window.
*/ */
if (frame->f_lasti < *instr_lb || frame->f_lasti >= *instr_ub) { line = _PyCode_CheckLineNumber(frame->f_lasti, bounds);
PyAddrPair bounds;
line = _PyCode_CheckLineNumber(frame->f_code, frame->f_lasti,
&bounds);
*instr_lb = bounds.ap_lower;
*instr_ub = bounds.ap_upper;
}
/* If the last instruction falls at the start of a line or if it /* If the last instruction falls at the start of a line or if it
represents a jump backwards, update the frame's line number and represents a jump backwards, update the frame's line number and
then call the trace function if we're tracing source lines. then call the trace function if we're tracing source lines.
*/ */
if ((frame->f_lasti == *instr_lb || frame->f_lasti < *instr_prev)) { if ((line != frame->f_lineno || frame->f_lasti < *instr_prev)) {
frame->f_lineno = line; if (line != -1) {
if (frame->f_trace_lines) { frame->f_lineno = line;
result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None); if (frame->f_trace_lines) {
result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None);
}
} }
} }
/* Always emit an opcode event if we're tracing all opcodes. */ /* Always emit an opcode event if we're tracing all opcodes. */
@ -5907,33 +5899,28 @@ dtrace_function_return(PyFrameObject *f)
/* DTrace equivalent of maybe_call_line_trace. */ /* DTrace equivalent of maybe_call_line_trace. */
static void static void
maybe_dtrace_line(PyFrameObject *frame, maybe_dtrace_line(PyFrameObject *frame,
int *instr_lb, int *instr_ub, int *instr_prev) PyCodeAddressRange *bounds, int *instr_prev)
{ {
int line = frame->f_lineno;
const char *co_filename, *co_name; const char *co_filename, *co_name;
/* If the last instruction executed isn't in the current /* If the last instruction executed isn't in the current
instruction window, reset the window. instruction window, reset the window.
*/ */
if (frame->f_lasti < *instr_lb || frame->f_lasti >= *instr_ub) { int line = _PyCode_CheckLineNumber(frame->f_lasti, bounds);
PyAddrPair bounds;
line = _PyCode_CheckLineNumber(frame->f_code, frame->f_lasti,
&bounds);
*instr_lb = bounds.ap_lower;
*instr_ub = bounds.ap_upper;
}
/* If the last instruction falls at the start of a line or if /* If the last instruction falls at the start of a line or if
it represents a jump backwards, update the frame's line it represents a jump backwards, update the frame's line
number and call the trace function. */ number and call the trace function. */
if (frame->f_lasti == *instr_lb || frame->f_lasti < *instr_prev) { if (line != frame->f_lineno || frame->f_lasti < *instr_prev) {
frame->f_lineno = line; if (line != -1) {
co_filename = PyUnicode_AsUTF8(frame->f_code->co_filename); frame->f_lineno = line;
if (!co_filename) co_filename = PyUnicode_AsUTF8(frame->f_code->co_filename);
co_filename = "?"; if (!co_filename)
co_name = PyUnicode_AsUTF8(frame->f_code->co_name); co_filename = "?";
if (!co_name) co_name = PyUnicode_AsUTF8(frame->f_code->co_name);
co_name = "?"; if (!co_name)
PyDTrace_LINE(co_filename, co_name, line); co_name = "?";
PyDTrace_LINE(co_filename, co_name, line);
}
} }
*instr_prev = frame->f_lasti; *instr_prev = frame->f_lasti;
} }

View File

@ -1827,7 +1827,7 @@ compiler_mod(struct compiler *c, mod_ty mod)
return NULL; return NULL;
} }
/* Use 0 for firstlineno initially, will fixup in assemble(). */ /* Use 0 for firstlineno initially, will fixup in assemble(). */
if (!compiler_enter_scope(c, module, COMPILER_SCOPE_MODULE, mod, 0)) if (!compiler_enter_scope(c, module, COMPILER_SCOPE_MODULE, mod, 1))
return NULL; return NULL;
switch (mod->kind) { switch (mod->kind) {
case Module_kind: case Module_kind:
@ -2271,7 +2271,9 @@ compiler_function(struct compiler *c, stmt_ty s, int is_async)
c->u->u_argcount = asdl_seq_LEN(args->args); c->u->u_argcount = asdl_seq_LEN(args->args);
c->u->u_posonlyargcount = asdl_seq_LEN(args->posonlyargs); c->u->u_posonlyargcount = asdl_seq_LEN(args->posonlyargs);
c->u->u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs); c->u->u_kwonlyargcount = asdl_seq_LEN(args->kwonlyargs);
VISIT_SEQ_IN_SCOPE(c, stmt, body); for (i = docstring ? 1 : 0; i < asdl_seq_LEN(body); i++) {
VISIT(c, stmt, (stmt_ty)asdl_seq_GET(body, i));
}
co = assemble(c, 1); co = assemble(c, 1);
qualname = c->u->u_qualname; qualname = c->u->u_qualname;
Py_INCREF(qualname); Py_INCREF(qualname);
@ -2808,6 +2810,8 @@ compiler_async_for(struct compiler *c, stmt_ty s)
/* Except block for __anext__ */ /* Except block for __anext__ */
compiler_use_next_block(c, except); compiler_use_next_block(c, except);
c->u->u_lineno = -1;
ADDOP(c, END_ASYNC_FOR); ADDOP(c, END_ASYNC_FOR);
/* `else` block */ /* `else` block */
@ -3115,7 +3119,8 @@ compiler_try_except(struct compiler *c, stmt_ty s)
compiler_pop_fblock(c, HANDLER_CLEANUP, cleanup_body); compiler_pop_fblock(c, HANDLER_CLEANUP, cleanup_body);
ADDOP(c, POP_BLOCK); ADDOP(c, POP_BLOCK);
ADDOP(c, POP_EXCEPT); ADDOP(c, POP_EXCEPT);
/* name = None; del name */ /* name = None; del name; # Mark as artificial */
c->u->u_lineno = -1;
ADDOP_LOAD_CONST(c, Py_None); ADDOP_LOAD_CONST(c, Py_None);
compiler_nameop(c, handler->v.ExceptHandler.name, Store); compiler_nameop(c, handler->v.ExceptHandler.name, Store);
compiler_nameop(c, handler->v.ExceptHandler.name, Del); compiler_nameop(c, handler->v.ExceptHandler.name, Del);
@ -3124,7 +3129,8 @@ compiler_try_except(struct compiler *c, stmt_ty s)
/* except: */ /* except: */
compiler_use_next_block(c, cleanup_end); compiler_use_next_block(c, cleanup_end);
/* name = None; del name */ /* name = None; del name; # Mark as artificial */
c->u->u_lineno = -1;
ADDOP_LOAD_CONST(c, Py_None); ADDOP_LOAD_CONST(c, Py_None);
compiler_nameop(c, handler->v.ExceptHandler.name, Store); compiler_nameop(c, handler->v.ExceptHandler.name, Store);
compiler_nameop(c, handler->v.ExceptHandler.name, Del); compiler_nameop(c, handler->v.ExceptHandler.name, Del);
@ -3359,6 +3365,7 @@ compiler_visit_stmt_expr(struct compiler *c, expr_ty value)
if (value->kind == Constant_kind) { if (value->kind == Constant_kind) {
/* ignore constant statement */ /* ignore constant statement */
ADDOP(c, NOP);
return 1; return 1;
} }
@ -3431,6 +3438,7 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s)
case Expr_kind: case Expr_kind:
return compiler_visit_stmt_expr(c, s->v.Expr.value); return compiler_visit_stmt_expr(c, s->v.Expr.value);
case Pass_kind: case Pass_kind:
ADDOP(c, NOP);
break; break;
case Break_kind: case Break_kind:
return compiler_break(c); return compiler_break(c);
@ -5429,8 +5437,9 @@ struct assembler {
basicblock **a_reverse_postorder; /* list of blocks in dfs postorder */ basicblock **a_reverse_postorder; /* list of blocks in dfs postorder */
PyObject *a_lnotab; /* string containing lnotab */ PyObject *a_lnotab; /* string containing lnotab */
int a_lnotab_off; /* offset into lnotab */ int a_lnotab_off; /* offset into lnotab */
int a_lineno; /* last lineno of emitted instruction */ int a_prevlineno; /* lineno of last emitted line in line table */
int a_lineno_off; /* bytecode offset of last lineno */ int a_lineno; /* lineno of last emitted instruction */
int a_lineno_start; /* bytecode start offset of current lineno */
}; };
static void static void
@ -5533,7 +5542,7 @@ static int
assemble_init(struct assembler *a, int nblocks, int firstlineno) assemble_init(struct assembler *a, int nblocks, int firstlineno)
{ {
memset(a, 0, sizeof(struct assembler)); memset(a, 0, sizeof(struct assembler));
a->a_lineno = firstlineno; a->a_prevlineno = a->a_lineno = firstlineno;
a->a_bytecode = PyBytes_FromStringAndSize(NULL, DEFAULT_CODE_SIZE); a->a_bytecode = PyBytes_FromStringAndSize(NULL, DEFAULT_CODE_SIZE);
if (!a->a_bytecode) if (!a->a_bytecode)
return 0; return 0;
@ -5573,114 +5582,82 @@ blocksize(basicblock *b)
return size; return size;
} }
/* Appends a pair to the end of the line number table, a_lnotab, representing
the instruction's bytecode offset and line number. See
Objects/lnotab_notes.txt for the description of the line number table. */
static int static int
assemble_lnotab(struct assembler *a, struct instr *i) assemble_emit_linetable_pair(struct assembler *a, int bdelta, int ldelta)
{ {
int d_bytecode, d_lineno; Py_ssize_t len = PyBytes_GET_SIZE(a->a_lnotab);
Py_ssize_t len;
unsigned char *lnotab;
d_lineno = i->i_lineno - a->a_lineno;
if (d_lineno == 0) {
return 1;
}
d_bytecode = (a->a_offset - a->a_lineno_off) * sizeof(_Py_CODEUNIT);
assert(d_bytecode >= 0);
if (d_bytecode > 255) {
int j, nbytes, ncodes = d_bytecode / 255;
nbytes = a->a_lnotab_off + 2 * ncodes;
len = PyBytes_GET_SIZE(a->a_lnotab);
if (nbytes >= len) {
if ((len <= INT_MAX / 2) && (len * 2 < nbytes))
len = nbytes;
else if (len <= INT_MAX / 2)
len *= 2;
else {
PyErr_NoMemory();
return 0;
}
if (_PyBytes_Resize(&a->a_lnotab, len) < 0)
return 0;
}
lnotab = (unsigned char *)
PyBytes_AS_STRING(a->a_lnotab) + a->a_lnotab_off;
for (j = 0; j < ncodes; j++) {
*lnotab++ = 255;
*lnotab++ = 0;
}
d_bytecode -= ncodes * 255;
a->a_lnotab_off += ncodes * 2;
}
assert(0 <= d_bytecode && d_bytecode <= 255);
if (d_lineno < -128 || 127 < d_lineno) {
int j, nbytes, ncodes, k;
if (d_lineno < 0) {
k = -128;
/* use division on positive numbers */
ncodes = (-d_lineno) / 128;
}
else {
k = 127;
ncodes = d_lineno / 127;
}
d_lineno -= ncodes * k;
assert(ncodes >= 1);
nbytes = a->a_lnotab_off + 2 * ncodes;
len = PyBytes_GET_SIZE(a->a_lnotab);
if (nbytes >= len) {
if ((len <= INT_MAX / 2) && len * 2 < nbytes)
len = nbytes;
else if (len <= INT_MAX / 2)
len *= 2;
else {
PyErr_NoMemory();
return 0;
}
if (_PyBytes_Resize(&a->a_lnotab, len) < 0)
return 0;
}
lnotab = (unsigned char *)
PyBytes_AS_STRING(a->a_lnotab) + a->a_lnotab_off;
*lnotab++ = d_bytecode;
*lnotab++ = k;
d_bytecode = 0;
for (j = 1; j < ncodes; j++) {
*lnotab++ = 0;
*lnotab++ = k;
}
a->a_lnotab_off += ncodes * 2;
}
assert(-128 <= d_lineno && d_lineno <= 127);
len = PyBytes_GET_SIZE(a->a_lnotab);
if (a->a_lnotab_off + 2 >= len) { if (a->a_lnotab_off + 2 >= len) {
if (_PyBytes_Resize(&a->a_lnotab, len * 2) < 0) if (_PyBytes_Resize(&a->a_lnotab, len * 2) < 0)
return 0; return 0;
} }
lnotab = (unsigned char *) unsigned char *lnotab = (unsigned char *)
PyBytes_AS_STRING(a->a_lnotab) + a->a_lnotab_off; PyBytes_AS_STRING(a->a_lnotab) + a->a_lnotab_off;
a->a_lnotab_off += 2; a->a_lnotab_off += 2;
if (d_bytecode) { *lnotab++ = bdelta;
*lnotab++ = d_bytecode; *lnotab++ = ldelta;
*lnotab++ = d_lineno;
}
else { /* First line of a block; def stmt, etc. */
*lnotab++ = 0;
*lnotab++ = d_lineno;
}
a->a_lineno = i->i_lineno;
a->a_lineno_off = a->a_offset;
return 1; return 1;
} }
/* Appends a range to the end of the line number table. See
* Objects/lnotab_notes.txt for the description of the line number table. */
static int
assemble_line_range(struct assembler *a)
{
int ldelta, bdelta;
bdelta = (a->a_offset - a->a_lineno_start) * 2;
if (bdelta == 0) {
return 1;
}
if (a->a_lineno < 0) {
ldelta = -128;
}
else {
ldelta = a->a_lineno - a->a_prevlineno;
a->a_prevlineno = a->a_lineno;
while (ldelta > 127) {
if (!assemble_emit_linetable_pair(a, 0, 127)) {
return 0;
}
ldelta -= 127;
}
while (ldelta < -127) {
if (!assemble_emit_linetable_pair(a, 0, -127)) {
return 0;
}
ldelta += 127;
}
}
assert(-128 <= ldelta && ldelta < 128);
while (bdelta > 254) {
if (!assemble_emit_linetable_pair(a, 254, ldelta)) {
return 0;
}
ldelta = a->a_lineno < 0 ? -128 : 0;
bdelta -= 254;
}
if (!assemble_emit_linetable_pair(a, bdelta, ldelta)) {
return 0;
}
a->a_lineno_start = a->a_offset;
return 1;
}
static int
assemble_lnotab(struct assembler *a, struct instr *i)
{
if (i->i_lineno == a->a_lineno) {
return 1;
}
if (!assemble_line_range(a)) {
return 0;
}
a->a_lineno = i->i_lineno;
return 1;
}
/* assemble_emit() /* assemble_emit()
Extend the bytecode with a new instruction. Extend the bytecode with a new instruction.
Update lnotab if necessary. Update lnotab if necessary.
@ -5998,7 +5975,7 @@ assemble(struct compiler *c, int addNone)
block ends with a jump or return b_next shouldn't set. block ends with a jump or return b_next shouldn't set.
*/ */
if (!c->u->u_curblock->b_return) { if (!c->u->u_curblock->b_return) {
NEXT_BLOCK(c); c->u->u_lineno = -1;
if (addNone) if (addNone)
ADDOP_LOAD_CONST(c, Py_None); ADDOP_LOAD_CONST(c, Py_None);
ADDOP(c, RETURN_VALUE); ADDOP(c, RETURN_VALUE);
@ -6015,7 +5992,7 @@ assemble(struct compiler *c, int addNone)
if (!c->u->u_firstlineno) { if (!c->u->u_firstlineno) {
if (entryblock && entryblock->b_instr && entryblock->b_instr->i_lineno) if (entryblock && entryblock->b_instr && entryblock->b_instr->i_lineno)
c->u->u_firstlineno = entryblock->b_instr->i_lineno; c->u->u_firstlineno = entryblock->b_instr->i_lineno;
else else
c->u->u_firstlineno = 1; c->u->u_firstlineno = 1;
} }
if (!assemble_init(&a, nblocks, c->u->u_firstlineno)) if (!assemble_init(&a, nblocks, c->u->u_firstlineno))
@ -6040,6 +6017,13 @@ assemble(struct compiler *c, int addNone)
if (!assemble_emit(&a, &b->b_instr[j])) if (!assemble_emit(&a, &b->b_instr[j]))
goto error; goto error;
} }
if (!assemble_line_range(&a)) {
return 0;
}
/* Emit sentinel at end of line number table */
if (!assemble_emit_linetable_pair(&a, 255, -128)) {
goto error;
}
if (_PyBytes_Resize(&a.a_lnotab, a.a_lnotab_off) < 0) if (_PyBytes_Resize(&a.a_lnotab, a.a_lnotab_off) < 0)
goto error; goto error;
@ -6280,22 +6264,42 @@ static void
clean_basic_block(basicblock *bb) { clean_basic_block(basicblock *bb) {
/* Remove NOPs and any code following a return or re-raise. */ /* Remove NOPs and any code following a return or re-raise. */
int dest = 0; int dest = 0;
int prev_lineno = -1;
for (int src = 0; src < bb->b_iused; src++) { for (int src = 0; src < bb->b_iused; src++) {
int lineno = bb->b_instr[src].i_lineno;
switch(bb->b_instr[src].i_opcode) { switch(bb->b_instr[src].i_opcode) {
case NOP:
/* skip */
break;
case RETURN_VALUE: case RETURN_VALUE:
case RERAISE: case RERAISE:
bb->b_next = NULL; bb->b_next = NULL;
bb->b_instr[dest] = bb->b_instr[src]; bb->b_instr[dest] = bb->b_instr[src];
dest++; dest++;
goto end; goto end;
case NOP:
{
/* Eliminate no-op if it doesn't have a line number, or
* if the next instruction has same line number or no line number, or
* if the previous instruction had the same line number. */
if (lineno < 0) {
break;
}
if (prev_lineno == lineno) {
break;
}
if (src < bb->b_iused - 1) {
int next_lineno = bb->b_instr[src+1].i_lineno;
if (next_lineno < 0 || next_lineno == lineno) {
bb->b_instr[src+1].i_lineno = lineno;
break;
}
}
}
/* fallthrough */
default: default:
if (dest != src) { if (dest != src) {
bb->b_instr[dest] = bb->b_instr[src]; bb->b_instr[dest] = bb->b_instr[src];
} }
dest++; dest++;
prev_lineno = lineno;
break; break;
} }
} }

3051
Python/importlib.h generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -524,7 +524,7 @@ w_complex_object(PyObject *v, char flag, WFILE *p)
w_object(co->co_filename, p); w_object(co->co_filename, p);
w_object(co->co_name, p); w_object(co->co_name, p);
w_long(co->co_firstlineno, p); w_long(co->co_firstlineno, p);
w_object(co->co_lnotab, p); w_object(co->co_linetable, p);
} }
else if (PyObject_CheckBuffer(v)) { else if (PyObject_CheckBuffer(v)) {
/* Write unknown bytes-like objects as a bytes object */ /* Write unknown bytes-like objects as a bytes object */
@ -1312,7 +1312,7 @@ r_object(RFILE *p)
PyObject *filename = NULL; PyObject *filename = NULL;
PyObject *name = NULL; PyObject *name = NULL;
int firstlineno; int firstlineno;
PyObject *lnotab = NULL; PyObject *linetable = NULL;
idx = r_ref_reserve(flag, p); idx = r_ref_reserve(flag, p);
if (idx < 0) if (idx < 0)
@ -1367,8 +1367,8 @@ r_object(RFILE *p)
firstlineno = (int)r_long(p); firstlineno = (int)r_long(p);
if (firstlineno == -1 && PyErr_Occurred()) if (firstlineno == -1 && PyErr_Occurred())
break; break;
lnotab = r_object(p); linetable = r_object(p);
if (lnotab == NULL) if (linetable == NULL)
goto code_error; goto code_error;
if (PySys_Audit("code.__new__", "OOOiiiiii", if (PySys_Audit("code.__new__", "OOOiiiiii",
@ -1382,7 +1382,7 @@ r_object(RFILE *p)
nlocals, stacksize, flags, nlocals, stacksize, flags,
code, consts, names, varnames, code, consts, names, varnames,
freevars, cellvars, filename, name, freevars, cellvars, filename, name,
firstlineno, lnotab); firstlineno, linetable);
v = r_ref_insert(v, idx, flag, p); v = r_ref_insert(v, idx, flag, p);
code_error: code_error:
@ -1394,7 +1394,7 @@ r_object(RFILE *p)
Py_XDECREF(cellvars); Py_XDECREF(cellvars);
Py_XDECREF(filename); Py_XDECREF(filename);
Py_XDECREF(name); Py_XDECREF(name);
Py_XDECREF(lnotab); Py_XDECREF(linetable);
} }
retval = v; retval = v;
break; break;

View File

@ -648,19 +648,28 @@ class PyCodeObjectPtr(PyObjectPtr):
Analogous to PyCode_Addr2Line; translated from pseudocode in Analogous to PyCode_Addr2Line; translated from pseudocode in
Objects/lnotab_notes.txt Objects/lnotab_notes.txt
''' '''
co_lnotab = self.pyop_field('co_lnotab').proxyval(set()) co_linetable = self.pyop_field('co_linetable').proxyval(set())
# Initialize lineno to co_firstlineno as per PyCode_Addr2Line # Initialize lineno to co_firstlineno as per PyCode_Addr2Line
# not 0, as lnotab_notes.txt has it: # not 0, as lnotab_notes.txt has it:
lineno = int_from_int(self.field('co_firstlineno')) lineno = int_from_int(self.field('co_firstlineno'))
if addrq < 0:
return lineno
addr = 0 addr = 0
for addr_incr, line_incr in zip(co_lnotab[::2], co_lnotab[1::2]): for addr_incr, line_incr in zip(co_linetable[::2], co_linetable[1::2]):
if addr_incr == 255:
break
addr += ord(addr_incr) addr += ord(addr_incr)
line_delta = ord(line_incr)
if line_delta == 128:
line_delta = 0
elif line_delta > 128:
line_delta -= 256
lineno += line_delta
if addr > addrq: if addr > addrq:
return lineno return lineno
lineno += ord(line_incr) assert False, "Unreachable"
return lineno
class PyDictObjectPtr(PyObjectPtr): class PyDictObjectPtr(PyObjectPtr):