bpo-44590: Lazily allocate frame objects (GH-27077)

* Convert "specials" array to InterpreterFrame struct, adding f_lasti, f_state and other non-debug FrameObject fields to it.

* Refactor, calls pushing the call to the interpreter upward toward _PyEval_Vector.

* Compute f_back when on thread stack, only filling in value when frame object outlives stack invocation.

* Move ownership of InterpreterFrame in generator from frame object to generator object.

* Do not create frame objects for Python calls.

* Do not create frame objects for generators.
This commit is contained in:
Mark Shannon 2021-07-26 11:22:16 +01:00 committed by GitHub
parent 0363a4014d
commit ae0a2b7562
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1037 additions and 619 deletions

View file

@ -854,28 +854,102 @@ class PyNoneStructPtr(PyObjectPtr):
def proxyval(self, visited):
return None
FRAME_SPECIALS_GLOBAL_OFFSET = 0
FRAME_SPECIALS_BUILTINS_OFFSET = 1
FRAME_SPECIALS_CODE_OFFSET = 3
FRAME_SPECIALS_SIZE = 4
class PyFrameObjectPtr(PyObjectPtr):
_typename = 'PyFrameObject'
def __init__(self, gdbval, cast_to=None):
PyObjectPtr.__init__(self, gdbval, cast_to)
if not self.is_optimized_out():
self._frame = PyFramePtr(self.field('f_frame'))
def iter_locals(self):
'''
Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
the local variables of this frame
'''
if self.is_optimized_out():
return
return self._frame.iter_locals()
def iter_globals(self):
'''
Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
the global variables of this frame
'''
if self.is_optimized_out():
return ()
return self._frame.iter_globals()
def iter_builtins(self):
'''
Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
the builtin variables
'''
if self.is_optimized_out():
return ()
return self._frame.iter_builtins()
def get_var_by_name(self, name):
if self.is_optimized_out():
return None, None
return self._frame.get_var_by_name(name)
def filename(self):
'''Get the path of the current Python source file, as a string'''
if self.is_optimized_out():
return FRAME_INFO_OPTIMIZED_OUT
return self._frame.filename()
def current_line_num(self):
'''Get current line number as an integer (1-based)
Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
See Objects/lnotab_notes.txt
'''
if self.is_optimized_out():
return None
return self._frame.current_line_num()
def current_line(self):
'''Get the text of the current source line as a string, with a trailing
newline character'''
if self.is_optimized_out():
return FRAME_INFO_OPTIMIZED_OUT
return self._frame.current_line()
def write_repr(self, out, visited):
if self.is_optimized_out():
out.write(FRAME_INFO_OPTIMIZED_OUT)
return
return self._frame.write_repr(out, visited)
def print_traceback(self):
if self.is_optimized_out():
sys.stdout.write(' %s\n' % FRAME_INFO_OPTIMIZED_OUT)
return
return self._frame.print_traceback()
class PyFramePtr:
def __init__(self, gdbval):
self._gdbval = gdbval
if not self.is_optimized_out():
self.co = self._f_code()
self.co_name = self.co.pyop_field('co_name')
self.co_filename = self.co.pyop_field('co_filename')
self.f_lineno = int_from_int(self.field('f_lineno'))
self.f_lasti = int_from_int(self.field('f_lasti'))
self.f_lasti = self._f_lasti()
self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
pnames = self.co.field('co_localsplusnames')
self.co_localsplusnames = PyTupleObjectPtr.from_pyobject_ptr(pnames)
def is_optimized_out(self):
return self._gdbval.is_optimized_out
def iter_locals(self):
'''
Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
@ -884,26 +958,34 @@ class PyFrameObjectPtr(PyObjectPtr):
if self.is_optimized_out():
return
f_localsplus = self.field('f_localsptr')
obj_ptr_ptr = gdb.lookup_type("PyObject").pointer().pointer()
base = self._gdbval.cast(obj_ptr_ptr)
localsplus = base - self._f_nlocalsplus()
for i in safe_range(self.co_nlocals):
pyop_value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i])
pyop_value = PyObjectPtr.from_pyobject_ptr(localsplus[i])
if pyop_value.is_null():
continue
pyop_name = PyObjectPtr.from_pyobject_ptr(self.co_localsplusnames[i])
yield (pyop_name, pyop_value)
def _f_specials(self, index, cls=PyObjectPtr):
f_valuestack = self.field('f_valuestack')
return cls.from_pyobject_ptr(f_valuestack[index - FRAME_SPECIALS_SIZE])
def _f_special(self, name, convert=PyObjectPtr.from_pyobject_ptr):
return convert(self._gdbval[name])
def _f_globals(self):
return self._f_specials(FRAME_SPECIALS_GLOBAL_OFFSET)
return self._f_special("f_globals")
def _f_builtins(self):
return self._f_specials(FRAME_SPECIALS_BUILTINS_OFFSET)
return self._f_special("f_builtins")
def _f_code(self):
return self._f_specials(FRAME_SPECIALS_CODE_OFFSET, PyCodeObjectPtr)
return self._f_special("f_code", PyCodeObjectPtr.from_pyobject_ptr)
def _f_nlocalsplus(self):
return self._f_special("nlocalsplus", int_from_int)
def _f_lasti(self):
return self._f_special("f_lasti", int_from_int)
def iter_globals(self):
'''
@ -960,11 +1042,6 @@ class PyFrameObjectPtr(PyObjectPtr):
'''
if self.is_optimized_out():
return None
f_trace = self.field('f_trace')
if long(f_trace) != 0:
# we have a non-NULL f_trace:
return self.f_lineno
try:
return self.co.addr2line(self.f_lasti*2)
except Exception:
@ -1021,6 +1098,9 @@ class PyFrameObjectPtr(PyObjectPtr):
out.write(')')
def as_address(self):
return long(self._gdbval)
def print_traceback(self):
if self.is_optimized_out():
sys.stdout.write(' %s\n' % FRAME_INFO_OPTIMIZED_OUT)
@ -1033,6 +1113,21 @@ class PyFrameObjectPtr(PyObjectPtr):
lineno,
self.co_name.proxyval(visited)))
def get_truncated_repr(self, maxlen):
'''
Get a repr-like string for the data, but truncate it at "maxlen" bytes
(ending the object graph traversal as soon as you do)
'''
out = TruncatedStringIO(maxlen)
try:
self.write_repr(out, set())
except StringTruncated:
# Truncation occurred:
return out.getvalue() + '...(truncated)'
# No truncation occurred:
return out.getvalue()
class PySetObjectPtr(PyObjectPtr):
_typename = 'PySetObject'
@ -1638,18 +1733,18 @@ class Frame(object):
def get_pyop(self):
try:
f = self._gdbframe.read_var('f')
frame = PyFrameObjectPtr.from_pyobject_ptr(f)
frame = self._gdbframe.read_var('frame')
frame = PyFramePtr(frame)
if not frame.is_optimized_out():
return frame
# gdb is unable to get the "f" argument of PyEval_EvalFrameEx()
# because it was "optimized out". Try to get "f" from the frame
# of the caller, PyEval_EvalCodeEx().
# gdb is unable to get the "frame" argument of PyEval_EvalFrameEx()
# because it was "optimized out". Try to get "frame" from the frame
# of the caller, _PyEval_Vector().
orig_frame = frame
caller = self._gdbframe.older()
if caller:
f = caller.read_var('f')
frame = PyFrameObjectPtr.from_pyobject_ptr(f)
frame = caller.read_var('frame')
frame = PyFramePtr(frame)
if not frame.is_optimized_out():
return frame
return orig_frame