bpo-45256: Remove the usage of the C stack in Python to Python calls (GH-28488)

Ths commit inlines calls to Python functions in the eval loop and steals all the arguments in the call from the caller for
performance.
This commit is contained in:
Pablo Galindo Salgado 2021-10-09 16:51:30 +01:00 committed by GitHub
parent ec04db74e2
commit b4903afd4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 224 additions and 64 deletions

View File

@ -31,6 +31,7 @@ typedef struct _interpreter_frame {
int f_lasti; /* Last instruction if called */ int f_lasti; /* Last instruction if called */
int stacktop; /* Offset of TOS from localsplus */ int stacktop; /* Offset of TOS from localsplus */
PyFrameState f_state; /* What state the frame is in */ PyFrameState f_state; /* What state the frame is in */
int depth; /* Depth of the frame in a ceval loop */
PyObject *localsplus[1]; PyObject *localsplus[1];
} InterpreterFrame; } InterpreterFrame;
@ -85,6 +86,7 @@ _PyFrame_InitializeSpecials(
frame->generator = NULL; frame->generator = NULL;
frame->f_lasti = -1; frame->f_lasti = -1;
frame->f_state = FRAME_CREATED; frame->f_state = FRAME_CREATED;
frame->depth = 0;
} }
/* Gets the pointer to the locals array /* Gets the pointer to the locals array

View File

@ -13,6 +13,7 @@ extern "C" {
#define _PyTuple_ITEMS(op) (_PyTuple_CAST(op)->ob_item) #define _PyTuple_ITEMS(op) (_PyTuple_CAST(op)->ob_item)
extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t); extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t);
extern PyObject *_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -1,7 +1,7 @@
# Sample script for use by test_gdb.py # Sample script for use by test_gdb.py
def foo(a, b, c): def foo(a, b, c):
bar(a, b, c) bar(a=a, b=b, c=c)
def bar(a, b, c): def bar(a, b, c):
baz(a, b, c) baz(a, b, c)

View File

@ -734,8 +734,14 @@ class StackNavigationTests(DebuggerTests):
cmds_after_breakpoint=['py-up', 'py-up']) cmds_after_breakpoint=['py-up', 'py-up'])
self.assertMultilineMatches(bt, self.assertMultilineMatches(bt,
r'''^.* r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
id\(42\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\) baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
bar\(a=a, b=b, c=c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
foo\(1, 2, 3\)
$''') $''')
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands") @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
@ -763,10 +769,18 @@ $''')
cmds_after_breakpoint=['py-up', 'py-up', 'py-down']) cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
self.assertMultilineMatches(bt, self.assertMultilineMatches(bt,
r'''^.* r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
id\(42\) id\(42\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
bar\(a=a, b=b, c=c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
foo\(1, 2, 3\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
id\(42\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
$''') $''')
class PyBtTests(DebuggerTests): class PyBtTests(DebuggerTests):
@ -785,7 +799,7 @@ Traceback \(most recent call first\):
File ".*gdb_sample.py", line 7, in bar File ".*gdb_sample.py", line 7, in bar
baz\(a, b, c\) baz\(a, b, c\)
File ".*gdb_sample.py", line 4, in foo File ".*gdb_sample.py", line 4, in foo
bar\(a, b, c\) bar\(a=a, b=b, c=c\)
File ".*gdb_sample.py", line 12, in <module> File ".*gdb_sample.py", line 12, in <module>
foo\(1, 2, 3\) foo\(1, 2, 3\)
''') ''')
@ -801,7 +815,7 @@ Traceback \(most recent call first\):
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\) baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
bar\(a, b, c\) bar\(a=a, b=b, c=c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
foo\(1, 2, 3\) foo\(1, 2, 3\)
''') ''')
@ -1008,7 +1022,13 @@ class PyLocalsTests(DebuggerTests):
bt = self.get_stack_trace(script=self.get_sample_script(), bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-up', 'py-locals']) cmds_after_breakpoint=['py-up', 'py-up', 'py-locals'])
self.assertMultilineMatches(bt, self.assertMultilineMatches(bt,
r".*\na = 1\nb = 2\nc = 3\n.*") r'''^.*
Locals for foo
a = 1
b = 2
c = 3
Locals for <module>
.*$''')
def setUpModule(): def setUpModule():

View File

@ -484,6 +484,26 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
return (PyObject *)tuple; return (PyObject *)tuple;
} }
PyObject *
_PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n)
{
if (n == 0) {
return tuple_get_empty();
}
PyTupleObject *tuple = tuple_alloc(n);
if (tuple == NULL) {
return NULL;
}
PyObject **dst = tuple->ob_item;
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *item = src[i];
dst[i] = item;
}
_PyObject_GC_TRACK(tuple);
return (PyObject *)tuple;
}
static PyObject * static PyObject *
tupleslice(PyTupleObject *a, Py_ssize_t ilow, tupleslice(PyTupleObject *a, Py_ssize_t ilow,
Py_ssize_t ihigh) Py_ssize_t ihigh)

View File

@ -98,6 +98,12 @@ static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg
static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs); static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int); static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
static int get_exception_handler(PyCodeObject *, int, int*, int*, int*); static int get_exception_handler(PyCodeObject *, int, int*, int*, int*);
static InterpreterFrame *
_PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
PyObject *locals, PyObject* const* args,
size_t argcount, PyObject *kwnames, int steal_args);
static int
_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame);
#define NAME_ERROR_MSG \ #define NAME_ERROR_MSG \
"name '%.200s' is not defined" "name '%.200s' is not defined"
@ -1516,6 +1522,12 @@ trace_function_entry(PyThreadState *tstate, InterpreterFrame *frame)
return 0; return 0;
} }
static PyObject *
make_coro(PyThreadState *tstate, PyFrameConstructor *con,
PyObject *locals,
PyObject* const* args, size_t argcount,
PyObject *kwnames);
static int static int
skip_backwards_over_extended_args(PyCodeObject *code, int offset) { skip_backwards_over_extended_args(PyCodeObject *code, int offset) {
_Py_CODEUNIT *instrs = (_Py_CODEUNIT *)PyBytes_AS_STRING(code->co_code); _Py_CODEUNIT *instrs = (_Py_CODEUNIT *)PyBytes_AS_STRING(code->co_code);
@ -1543,10 +1555,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
PyObject *retval = NULL; /* Return value */ PyObject *retval = NULL; /* Return value */
_Py_atomic_int * const eval_breaker = &tstate->interp->ceval.eval_breaker; _Py_atomic_int * const eval_breaker = &tstate->interp->ceval.eval_breaker;
if (_Py_EnterRecursiveCall(tstate, "")) {
return NULL;
}
CFrame cframe; CFrame cframe;
/* WARNING: Because the CFrame lives on the C stack, /* WARNING: Because the CFrame lives on the C stack,
@ -1558,9 +1566,18 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
cframe.previous = prev_cframe; cframe.previous = prev_cframe;
tstate->cframe = &cframe; tstate->cframe = &cframe;
assert(frame->depth == 0);
/* push frame */ /* push frame */
tstate->frame = frame; tstate->frame = frame;
start_frame:
if (_Py_EnterRecursiveCall(tstate, "")) {
tstate->recursion_depth++;
goto exit_eval_frame;
}
assert(frame == tstate->frame);
if (cframe.use_tracing) { if (cframe.use_tracing) {
if (trace_function_entry(tstate, frame)) { if (trace_function_entry(tstate, frame)) {
goto exit_eval_frame; goto exit_eval_frame;
@ -1582,7 +1599,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
} }
} }
resume_frame:
co = frame->f_code;
PyObject *names = co->co_names; PyObject *names = co->co_names;
PyObject *consts = co->co_consts; PyObject *consts = co->co_consts;
_Py_CODEUNIT *first_instr = co->co_firstinstr; _Py_CODEUNIT *first_instr = co->co_firstinstr;
@ -1594,12 +1612,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
multiple values. multiple values.
When the PREDICT() macros are enabled, some opcode pairs follow in When the PREDICT() macros are enabled, some opcode pairs follow in
direct succession without updating frame->f_lasti. A successful direct succession. A successful prediction effectively links the two
prediction effectively links the two codes together as if they codes together as if they were a single new opcode, but the value
were a single new opcode; accordingly,frame->f_lasti will point to of frame->f_lasti is correctly updated so potential inlined calls
the first code in the pair (for instance, GET_ITER followed by or lookups of frame->f_lasti are aways correct when the macros are used.
FOR_ITER is effectively a single opcode and frame->f_lasti will point
to the beginning of the combined pair.)
*/ */
assert(frame->f_lasti >= -1); assert(frame->f_lasti >= -1);
_Py_CODEUNIT *next_instr = first_instr + frame->f_lasti + 1; _Py_CODEUNIT *next_instr = first_instr + frame->f_lasti + 1;
@ -1625,6 +1641,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
#endif #endif
if (throwflag) { /* support for generator.throw() */ if (throwflag) { /* support for generator.throw() */
throwflag = 0;
goto error; goto error;
} }
@ -4591,10 +4608,44 @@ check_eval_breaker:
TARGET(CALL_FUNCTION) { TARGET(CALL_FUNCTION) {
PREDICTED(CALL_FUNCTION); PREDICTED(CALL_FUNCTION);
PyObject **sp, *res; PyObject *res;
sp = stack_pointer;
res = call_function(tstate, &sp, oparg, NULL, cframe.use_tracing); // Check if the call can be inlined or not
stack_pointer = sp; PyObject *function = PEEK(oparg + 1);
if (Py_TYPE(function) == &PyFunction_Type) {
PyCodeObject *code = (PyCodeObject*)PyFunction_GET_CODE(function);
PyObject *locals = code->co_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function);
if ((code->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) == 0) {
InterpreterFrame *new_frame = _PyEvalFramePushAndInit(
tstate, PyFunction_AS_FRAME_CONSTRUCTOR(function), locals, stack_pointer-oparg, oparg, NULL, 1);
if (new_frame == NULL) {
// When we exit here, we own all variables in the stack (the frame creation has not stolen
// any variable) so we need to clean the whole stack (done in the "error" label).
goto error;
}
STACK_SHRINK(oparg + 1);
assert(tstate->interp->eval_frame != NULL);
// The frame has stolen all the arguments from the stack, so there is no need to clean them up.```
Py_DECREF(function);
_PyFrame_SetStackPointer(frame, stack_pointer);
new_frame->depth = frame->depth + 1;
tstate->frame = frame = new_frame;
goto start_frame;
}
else {
/* Callable is a generator or coroutine function: create coroutine or generator. */
res = make_coro(tstate, PyFunction_AS_FRAME_CONSTRUCTOR(function), locals, stack_pointer-oparg, oparg, NULL);
STACK_SHRINK(oparg + 1);
for (int i = 0; i < oparg + 1; i++) {
Py_DECREF(stack_pointer[i]);
}
}
}
else {
PyObject **sp = stack_pointer;
res = call_function(tstate, &sp, oparg, NULL, cframe.use_tracing);
stack_pointer = sp;
}
PUSH(res); PUSH(res);
if (res == NULL) { if (res == NULL) {
goto error; goto error;
@ -5018,14 +5069,28 @@ exiting:
/* pop frame */ /* pop frame */
exit_eval_frame: exit_eval_frame:
/* Restore previous cframe */
tstate->cframe = cframe.previous;
tstate->cframe->use_tracing = cframe.use_tracing;
if (PyDTrace_FUNCTION_RETURN_ENABLED()) if (PyDTrace_FUNCTION_RETURN_ENABLED())
dtrace_function_return(frame); dtrace_function_return(frame);
_Py_LeaveRecursiveCall(tstate); _Py_LeaveRecursiveCall(tstate);
if (frame->depth) {
_PyFrame_StackPush(frame->previous, retval);
if (_PyEvalFrameClearAndPop(tstate, frame)) {
retval = NULL;
}
frame = tstate->frame;
if (retval == NULL) {
assert(_PyErr_Occurred(tstate));
throwflag = 1;
}
retval = NULL;
goto resume_frame;
}
tstate->frame = frame->previous; tstate->frame = frame->previous;
/* Restore previous cframe */
tstate->cframe = cframe.previous;
tstate->cframe->use_tracing = cframe.use_tracing;
return _Py_CheckFunctionResult(tstate, NULL, retval, __func__); return _Py_CheckFunctionResult(tstate, NULL, retval, __func__);
} }
@ -5336,7 +5401,7 @@ get_exception_handler(PyCodeObject *code, int index, int *level, int *handler, i
static int static int
initialize_locals(PyThreadState *tstate, PyFrameConstructor *con, initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
PyObject **localsplus, PyObject *const *args, PyObject **localsplus, PyObject *const *args,
Py_ssize_t argcount, PyObject *kwnames) Py_ssize_t argcount, PyObject *kwnames, int steal_args)
{ {
PyCodeObject *co = (PyCodeObject*)con->fc_code; PyCodeObject *co = (PyCodeObject*)con->fc_code;
const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount; const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
@ -5346,8 +5411,9 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
Py_ssize_t i; Py_ssize_t i;
if (co->co_flags & CO_VARKEYWORDS) { if (co->co_flags & CO_VARKEYWORDS) {
kwdict = PyDict_New(); kwdict = PyDict_New();
if (kwdict == NULL) if (kwdict == NULL) {
goto fail; goto fail;
}
i = total_args; i = total_args;
if (co->co_flags & CO_VARARGS) { if (co->co_flags & CO_VARARGS) {
i++; i++;
@ -5369,14 +5435,21 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
} }
for (j = 0; j < n; j++) { for (j = 0; j < n; j++) {
PyObject *x = args[j]; PyObject *x = args[j];
Py_INCREF(x); if (!steal_args) {
Py_INCREF(x);
}
assert(localsplus[j] == NULL); assert(localsplus[j] == NULL);
localsplus[j] = x; localsplus[j] = x;
} }
/* Pack other positional arguments into the *args argument */ /* Pack other positional arguments into the *args argument */
if (co->co_flags & CO_VARARGS) { if (co->co_flags & CO_VARARGS) {
PyObject *u = _PyTuple_FromArray(args + n, argcount - n); PyObject *u = NULL;
if (steal_args) {
u = _PyTuple_FromArraySteal(args + n, argcount - n);
} else {
u = _PyTuple_FromArray(args + n, argcount - n);
}
if (u == NULL) { if (u == NULL) {
goto fail; goto fail;
} }
@ -5442,6 +5515,9 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
if (PyDict_SetItem(kwdict, keyword, value) == -1) { if (PyDict_SetItem(kwdict, keyword, value) == -1) {
goto fail; goto fail;
} }
if (steal_args) {
Py_DECREF(value);
}
continue; continue;
kw_found: kw_found:
@ -5451,7 +5527,9 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
con->fc_qualname, keyword); con->fc_qualname, keyword);
goto fail; goto fail;
} }
Py_INCREF(value); if (!steal_args) {
Py_INCREF(value);
}
localsplus[j] = value; localsplus[j] = value;
} }
} }
@ -5555,7 +5633,7 @@ make_coro_frame(PyThreadState *tstate,
} }
_PyFrame_InitializeSpecials(frame, con, locals, code->co_nlocalsplus); _PyFrame_InitializeSpecials(frame, con, locals, code->co_nlocalsplus);
assert(frame->frame_obj == NULL); assert(frame->frame_obj == NULL);
if (initialize_locals(tstate, con, frame->localsplus, args, argcount, kwnames)) { if (initialize_locals(tstate, con, frame->localsplus, args, argcount, kwnames, 0)) {
_PyFrame_Clear(frame, 1); _PyFrame_Clear(frame, 1);
return NULL; return NULL;
} }
@ -5581,17 +5659,30 @@ make_coro(PyThreadState *tstate, PyFrameConstructor *con,
return gen; return gen;
} }
// If *steal_args* is set, the function will steal the references to all the arguments.
// In case of error, the function returns null and if *steal_args* is set, the caller
// will still own all the arguments.
static InterpreterFrame * static InterpreterFrame *
_PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con, _PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
PyObject *locals, PyObject* const* args, PyObject *locals, PyObject* const* args,
size_t argcount, PyObject *kwnames) size_t argcount, PyObject *kwnames, int steal_args)
{ {
InterpreterFrame * frame = _PyThreadState_PushFrame(tstate, con, locals); InterpreterFrame * frame = _PyThreadState_PushFrame(tstate, con, locals);
if (frame == NULL) { if (frame == NULL) {
return NULL; return NULL;
} }
PyObject **localsarray = _PyFrame_GetLocalsArray(frame); PyObject **localsarray = _PyFrame_GetLocalsArray(frame);
if (initialize_locals(tstate, con, localsarray, args, argcount, kwnames)) { if (initialize_locals(tstate, con, localsarray, args, argcount, kwnames, steal_args)) {
if (steal_args) {
// If we failed to initialize locals, make sure the caller still own all the
// arguments. Notice that we only need to increase the reference count of the
// *valid* arguments (i.e. the ones that fit into the frame).
PyCodeObject *co = (PyCodeObject*)con->fc_code;
const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
for (Py_ssize_t i = 0; i < Py_MIN(argcount, total_args); i++) {
Py_XINCREF(frame->localsplus[i]);
}
}
_PyFrame_Clear(frame, 0); _PyFrame_Clear(frame, 0);
return NULL; return NULL;
} }
@ -5606,9 +5697,9 @@ _PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
++tstate->recursion_depth; ++tstate->recursion_depth;
assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0); assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
if (_PyFrame_Clear(frame, 0)) { if (_PyFrame_Clear(frame, 0)) {
--tstate->recursion_depth;
return -1; return -1;
} }
assert(frame->frame_obj == NULL);
--tstate->recursion_depth; --tstate->recursion_depth;
tstate->frame = frame->previous; tstate->frame = frame->previous;
_PyThreadState_PopFrame(tstate, frame); _PyThreadState_PopFrame(tstate, frame);
@ -5628,7 +5719,7 @@ _PyEval_Vector(PyThreadState *tstate, PyFrameConstructor *con,
return make_coro(tstate, con, locals, args, argcount, kwnames); return make_coro(tstate, con, locals, args, argcount, kwnames);
} }
InterpreterFrame *frame = _PyEvalFramePushAndInit( InterpreterFrame *frame = _PyEvalFramePushAndInit(
tstate, con, locals, args, argcount, kwnames); tstate, con, locals, args, argcount, kwnames, 0);
if (frame == NULL) { if (frame == NULL) {
return NULL; return NULL;
} }

View File

@ -45,6 +45,7 @@ The module also extends gdb with some python-specific commands.
# compatible (2.6+ and 3.0+). See #19308. # compatible (2.6+ and 3.0+). See #19308.
from __future__ import print_function from __future__ import print_function
import gdb import gdb
import os import os
import locale import locale
@ -991,6 +992,11 @@ class PyFramePtr:
def _f_lasti(self): def _f_lasti(self):
return self._f_special("f_lasti", int_from_int) return self._f_special("f_lasti", int_from_int)
def depth(self):
return self._f_special("depth", int_from_int)
def previous(self):
return self._f_special("previous", PyFramePtr)
def iter_globals(self): def iter_globals(self):
''' '''
@ -1797,16 +1803,20 @@ class Frame(object):
def print_summary(self): def print_summary(self):
if self.is_evalframe(): if self.is_evalframe():
pyop = self.get_pyop() interp_frame = self.get_pyop()
if pyop: while True:
line = pyop.get_truncated_repr(MAX_OUTPUT_LEN) if interp_frame:
write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line)) line = interp_frame.get_truncated_repr(MAX_OUTPUT_LEN)
if not pyop.is_optimized_out(): write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line))
line = pyop.current_line() if not interp_frame.is_optimized_out():
if line is not None: line = interp_frame.current_line()
sys.stdout.write(' %s\n' % line.strip()) if line is not None:
else: sys.stdout.write(' %s\n' % line.strip())
sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index()) if interp_frame.depth() == 0:
break
else:
sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
interp_frame = interp_frame.previous()
else: else:
info = self.is_other_python_frame() info = self.is_other_python_frame()
if info: if info:
@ -1816,15 +1826,19 @@ class Frame(object):
def print_traceback(self): def print_traceback(self):
if self.is_evalframe(): if self.is_evalframe():
pyop = self.get_pyop() interp_frame = self.get_pyop()
if pyop: while True:
pyop.print_traceback() if interp_frame:
if not pyop.is_optimized_out(): interp_frame.print_traceback()
line = pyop.current_line() if not interp_frame.is_optimized_out():
if line is not None: line = interp_frame.current_line()
sys.stdout.write(' %s\n' % line.strip()) if line is not None:
else: sys.stdout.write(' %s\n' % line.strip())
sys.stdout.write(' (unable to read python frame information)\n') if interp_frame.depth() == 0:
break
else:
sys.stdout.write(' (unable to read python frame information)\n')
interp_frame = interp_frame.previous()
else: else:
info = self.is_other_python_frame() info = self.is_other_python_frame()
if info: if info:
@ -1914,11 +1928,15 @@ PyList()
def move_in_stack(move_up): def move_in_stack(move_up):
'''Move up or down the stack (for the py-up/py-down command)''' '''Move up or down the stack (for the py-up/py-down command)'''
# Important:
# The amount of frames that are printed out depends on how many frames are inlined
# in the same evaluation loop. As this command links directly the C stack with the
# Python stack, the results are sensitive to the number of inlined frames and this
# is likely to change between versions and optimizations.
frame = Frame.get_selected_python_frame() frame = Frame.get_selected_python_frame()
if not frame: if not frame:
print('Unable to locate python frame') print('Unable to locate python frame')
return return
while frame: while frame:
if move_up: if move_up:
iter_frame = frame.older() iter_frame = frame.older()
@ -1941,8 +1959,9 @@ def move_in_stack(move_up):
else: else:
print('Unable to find a newer python frame') print('Unable to find a newer python frame')
class PyUp(gdb.Command): class PyUp(gdb.Command):
'Select and print the python stack frame that called this one (if any)' 'Select and print all python stack frame in the same eval loop starting from the one that called this one (if any)'
def __init__(self): def __init__(self):
gdb.Command.__init__ (self, gdb.Command.__init__ (self,
"py-up", "py-up",
@ -1954,7 +1973,7 @@ class PyUp(gdb.Command):
move_in_stack(move_up=True) move_in_stack(move_up=True)
class PyDown(gdb.Command): class PyDown(gdb.Command):
'Select and print the python stack frame called by this one (if any)' 'Select and print all python stack frame in the same eval loop starting from the one called this one (if any)'
def __init__(self): def __init__(self):
gdb.Command.__init__ (self, gdb.Command.__init__ (self,
"py-down", "py-down",
@ -2067,13 +2086,20 @@ class PyLocals(gdb.Command):
return return
pyop_frame = frame.get_pyop() pyop_frame = frame.get_pyop()
if not pyop_frame: while True:
print(UNABLE_READ_INFO_PYTHON_FRAME) if not pyop_frame:
return print(UNABLE_READ_INFO_PYTHON_FRAME)
for pyop_name, pyop_value in pyop_frame.iter_locals(): sys.stdout.write('Locals for %s\n' % (pyop_frame.co_name.proxyval(set())))
print('%s = %s'
% (pyop_name.proxyval(set()), for pyop_name, pyop_value in pyop_frame.iter_locals():
pyop_value.get_truncated_repr(MAX_OUTPUT_LEN))) print('%s = %s'
% (pyop_name.proxyval(set()),
pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
if pyop_frame.depth() == 0:
break
pyop_frame = pyop_frame.previous()
PyLocals() PyLocals()