Skip to content

GH-131498: manage stacks automatically #132074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 4, 2025
12 changes: 6 additions & 6 deletions Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 0 additions & 5 deletions Include/internal/pycore_stackref.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
extern "C" {
#endif

// Define this to get precise tracking of closed stackrefs.
// This will use unbounded memory, as it can only grow.
// Use this to track double closes in short-lived programs
// #define Py_STACKREF_CLOSE_DEBUG 1

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif
Expand Down
6 changes: 6 additions & 0 deletions Include/internal/pycore_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ typedef struct {
// Define this to get precise tracking of stackrefs.
// #define Py_STACKREF_DEBUG 1

// Define this to get precise tracking of closed stackrefs.
// This will use unbounded memory, as it can only grow.
// Use this to track double closes in short-lived programs
// #define Py_STACKREF_CLOSE_DEBUG 1


typedef union _PyStackRef {
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
uint64_t index;
Expand Down
67 changes: 37 additions & 30 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ dummy_func(
ptrdiff_t off = this_instr - _PyFrame_GetBytecode(frame);
frame->tlbc_index = ((_PyThreadStateImpl *)tstate)->tlbc_index;
frame->instr_ptr = bytecode + off;
// Make sure this_instr gets reset correctley for any uops that
// Make sure this_instr gets reset correctly for any uops that
// follow
next_instr = frame->instr_ptr;
DISPATCH();
Expand Down Expand Up @@ -1111,7 +1111,7 @@ dummy_func(
tstate->current_frame = frame->previous;
assert(!_PyErr_Occurred(tstate));
PyObject *result = PyStackRef_AsPyObjectSteal(retval);
SYNC_SP(); /* Not strictly necessary, but prevents warnings */
LLTRACE_RESUME_FRAME();
return result;
}

Expand All @@ -1123,7 +1123,7 @@ dummy_func(
_PyStackRef temp = PyStackRef_MakeHeapSafe(retval);
DEAD(retval);
SAVE_STACK();
assert(EMPTY());
assert(STACK_LEVEL() == 0);
_Py_LeaveRecursiveCallPy(tstate);
// GH-99729: We need to unlink the frame *before* clearing it:
_PyInterpreterFrame *dying = frame;
Expand Down Expand Up @@ -1223,8 +1223,9 @@ dummy_func(
{
PyGenObject *gen = (PyGenObject *)receiver_o;
_PyInterpreterFrame *gen_frame = &gen->gi_iframe;
STACK_SHRINK(1);
_PyFrame_StackPush(gen_frame, PyStackRef_MakeHeapSafe(v));
DEAD(v);
SYNC_SP();
gen->gi_frame_state = FRAME_EXECUTING;
gen->gi_exc_state.previous_item = tstate->exc_info;
tstate->exc_info = &gen->gi_exc_state;
Expand Down Expand Up @@ -2436,10 +2437,10 @@ dummy_func(
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(
tstate, PyStackRef_FromPyObjectNew(f), 2, frame);
// Manipulate stack directly because we exit with DISPATCH_INLINED().
STACK_SHRINK(1);
new_frame->localsplus[0] = owner;
DEAD(owner);
// Manipulate stack directly because we exit with DISPATCH_INLINED().
SYNC_SP();
new_frame->localsplus[1] = PyStackRef_FromPyObjectNew(name);
frame->return_offset = INSTRUCTION_SIZE;
DISPATCH_INLINED(new_frame);
Expand Down Expand Up @@ -3083,12 +3084,11 @@ dummy_func(
macro(FOR_ITER) = _SPECIALIZE_FOR_ITER + _FOR_ITER;


inst(INSTRUMENTED_FOR_ITER, (unused/1 -- )) {
_PyStackRef iter_stackref = TOP();
PyObject *iter = PyStackRef_AsPyObjectBorrow(iter_stackref);
PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter);
if (next != NULL) {
PUSH(PyStackRef_FromPyObjectSteal(next));
inst(INSTRUMENTED_FOR_ITER, (unused/1, iter -- iter, next)) {
PyObject *iter_o = PyStackRef_AsPyObjectBorrow(iter);
PyObject *next_o = (*Py_TYPE(iter_o)->tp_iternext)(iter_o);
if (next_o != NULL) {
next = PyStackRef_FromPyObjectSteal(next_o);
INSTRUMENTED_JUMP(this_instr, next_instr, PY_MONITORING_EVENT_BRANCH_LEFT);
}
else {
Expand All @@ -3105,6 +3105,7 @@ dummy_func(
next_instr[oparg].op.code == INSTRUMENTED_END_FOR);
/* Skip END_FOR */
JUMPBY(oparg + 1);
DISPATCH();
}
}

Expand Down Expand Up @@ -4022,7 +4023,6 @@ dummy_func(
_PUSH_FRAME;

inst(EXIT_INIT_CHECK, (should_be_none -- )) {
assert(STACK_LEVEL() == 2);
if (!PyStackRef_IsNone(should_be_none)) {
PyErr_Format(PyExc_TypeError,
"__init__() should return None, not '%.200s'",
Expand Down Expand Up @@ -4813,7 +4813,7 @@ dummy_func(
PyFunctionObject *func = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(frame->f_funcobj);
PyGenObject *gen = (PyGenObject *)_Py_MakeCoro(func);
ERROR_IF(gen == NULL, error);
assert(EMPTY());
assert(STACK_LEVEL() == 0);
SAVE_STACK();
_PyInterpreterFrame *gen_frame = &gen->gi_iframe;
frame->instr_ptr++;
Expand Down Expand Up @@ -4932,6 +4932,7 @@ dummy_func(
}
next_instr = frame->instr_ptr;
if (next_instr != this_instr) {
SYNC_SP();
DISPATCH();
}
}
Expand Down Expand Up @@ -4976,46 +4977,48 @@ dummy_func(
_CHECK_PERIODIC +
_MONITOR_JUMP_BACKWARD;

inst(INSTRUMENTED_POP_JUMP_IF_TRUE, (unused/1 -- )) {
_PyStackRef cond = POP();
inst(INSTRUMENTED_POP_JUMP_IF_TRUE, (unused/1, cond -- )) {
assert(PyStackRef_BoolCheck(cond));
int jump = PyStackRef_IsTrue(cond);
DEAD(cond);
RECORD_BRANCH_TAKEN(this_instr[1].cache, jump);
if (jump) {
INSTRUMENTED_JUMP(this_instr, next_instr + oparg, PY_MONITORING_EVENT_BRANCH_RIGHT);
}
}

inst(INSTRUMENTED_POP_JUMP_IF_FALSE, (unused/1 -- )) {
_PyStackRef cond = POP();
inst(INSTRUMENTED_POP_JUMP_IF_FALSE, (unused/1, cond -- )) {
assert(PyStackRef_BoolCheck(cond));
int jump = PyStackRef_IsFalse(cond);
DEAD(cond);
RECORD_BRANCH_TAKEN(this_instr[1].cache, jump);
if (jump) {
INSTRUMENTED_JUMP(this_instr, next_instr + oparg, PY_MONITORING_EVENT_BRANCH_RIGHT);
}
}

inst(INSTRUMENTED_POP_JUMP_IF_NONE, (unused/1 -- )) {
_PyStackRef value_stackref = POP();
int jump = PyStackRef_IsNone(value_stackref);
inst(INSTRUMENTED_POP_JUMP_IF_NONE, (unused/1, value -- )) {
int jump = PyStackRef_IsNone(value);
RECORD_BRANCH_TAKEN(this_instr[1].cache, jump);
if (jump) {
DEAD(value);
INSTRUMENTED_JUMP(this_instr, next_instr + oparg, PY_MONITORING_EVENT_BRANCH_RIGHT);
}
else {
PyStackRef_CLOSE(value_stackref);
PyStackRef_CLOSE(value);
}
}

inst(INSTRUMENTED_POP_JUMP_IF_NOT_NONE, (unused/1 -- )) {
_PyStackRef value_stackref = POP();
int jump = !PyStackRef_IsNone(value_stackref);
inst(INSTRUMENTED_POP_JUMP_IF_NOT_NONE, (unused/1, value -- )) {
int jump = !PyStackRef_IsNone(value);
RECORD_BRANCH_TAKEN(this_instr[1].cache, jump);
if (jump) {
PyStackRef_CLOSE(value_stackref);
PyStackRef_CLOSE(value);
INSTRUMENTED_JUMP(this_instr, next_instr + oparg, PY_MONITORING_EVENT_BRANCH_RIGHT);
}
else {
DEAD(value);
}
}

tier1 inst(EXTENDED_ARG, ( -- )) {
Expand Down Expand Up @@ -5219,22 +5222,26 @@ dummy_func(
}

label(pop_4_error) {
STACK_SHRINK(4);
stack_pointer -= 4;
assert(WITHIN_STACK_BOUNDS());
goto error;
}

label(pop_3_error) {
STACK_SHRINK(3);
stack_pointer -= 3;
assert(WITHIN_STACK_BOUNDS());
goto error;
}

label(pop_2_error) {
STACK_SHRINK(2);
stack_pointer -= 2;
assert(WITHIN_STACK_BOUNDS());
goto error;
}

label(pop_1_error) {
STACK_SHRINK(1);
stack_pointer -= 1;
assert(WITHIN_STACK_BOUNDS());
goto error;
}

Expand Down
44 changes: 19 additions & 25 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,6 @@ dump_item(_PyStackRef item)
printf("<nil>");
return;
}
if (
obj == Py_None
|| PyBool_Check(obj)
|| PyLong_CheckExact(obj)
|| PyFloat_CheckExact(obj)
|| PyUnicode_CheckExact(obj)
) {
if (PyObject_Print(obj, stdout, 0) == 0) {
return;
}
PyErr_Clear();
}
// Don't call __repr__(), it might recurse into the interpreter.
printf("<%s at %p>", Py_TYPE(obj)->tp_name, (void *)obj);
}
Expand All @@ -182,14 +170,19 @@ dump_stack(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer)
dump_item(*ptr);
}
printf("]\n");
printf(" stack=[");
for (_PyStackRef *ptr = stack_base; ptr < stack_pointer; ptr++) {
if (ptr != stack_base) {
printf(", ");
if (stack_pointer < stack_base) {
printf(" stack=%d\n", (int)(stack_pointer-stack_base));
}
else {
printf(" stack=[");
for (_PyStackRef *ptr = stack_base; ptr < stack_pointer; ptr++) {
if (ptr != stack_base) {
printf(", ");
}
dump_item(*ptr);
}
dump_item(*ptr);
printf("]\n");
}
printf("]\n");
fflush(stdout);
PyErr_SetRaisedException(exc);
_PyFrame_GetStackPointer(frame);
Expand All @@ -202,13 +195,13 @@ lltrace_instruction(_PyInterpreterFrame *frame,
int opcode,
int oparg)
{
if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
return;
int offset = 0;
if (frame->owner < FRAME_OWNED_BY_INTERPRETER) {
dump_stack(frame, stack_pointer);
offset = (int)(next_instr - _PyFrame_GetBytecode(frame));
}
dump_stack(frame, stack_pointer);
const char *opname = _PyOpcode_OpName[opcode];
assert(opname != NULL);
int offset = (int)(next_instr - _PyFrame_GetBytecode(frame));
if (OPCODE_HAS_ARG((int)_PyOpcode_Deopt[opcode])) {
printf("%d: %s %d\n", offset * 2, opname, oparg);
}
Expand Down Expand Up @@ -986,8 +979,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
* These are cached values from the frame and code object. */
_Py_CODEUNIT *next_instr;
_PyStackRef *stack_pointer;

#if defined(Py_DEBUG) && !defined(Py_STACKREF_DEBUG)
entry_frame.localsplus[0] = PyStackRef_NULL;
#ifdef Py_STACKREF_DEBUG
entry_frame.f_funcobj = PyStackRef_None;
#elif defined(Py_DEBUG)
/* Set these to invalid but identifiable values for debugging. */
entry_frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0};
entry_frame.f_locals = (PyObject*)0xaaa1;
Expand Down Expand Up @@ -1044,7 +1039,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
_PyExecutorObject *current_executor = NULL;
const _PyUOpInstruction *next_uop = NULL;
#endif

#if Py_TAIL_CALL_INTERP
return _TAIL_CALL_start_frame(frame, NULL, tstate, NULL, 0);
#else
Expand Down
40 changes: 0 additions & 40 deletions Python/ceval_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,48 +194,8 @@ GETITEM(PyObject *v, Py_ssize_t i) {
#define JUMPBY(x) (next_instr += (x))
#define SKIP_OVER(x) (next_instr += (x))


/* Stack manipulation macros */

/* The stack can grow at most MAXINT deep, as co_nlocals and
co_stacksize are ints. */
#define STACK_LEVEL() ((int)(stack_pointer - _PyFrame_Stackbase(frame)))
#define STACK_SIZE() (_PyFrame_GetCode(frame)->co_stacksize)
#define EMPTY() (STACK_LEVEL() == 0)
#define TOP() (stack_pointer[-1])
#define SECOND() (stack_pointer[-2])
#define THIRD() (stack_pointer[-3])
#define FOURTH() (stack_pointer[-4])
#define PEEK(n) (stack_pointer[-(n)])
#define POKE(n, v) (stack_pointer[-(n)] = (v))
#define SET_TOP(v) (stack_pointer[-1] = (v))
#define SET_SECOND(v) (stack_pointer[-2] = (v))
#define BASIC_STACKADJ(n) (stack_pointer += n)
#define BASIC_PUSH(v) (*stack_pointer++ = (v))
#define BASIC_POP() (*--stack_pointer)

#ifdef Py_DEBUG
#define PUSH(v) do { \
BASIC_PUSH(v); \
assert(STACK_LEVEL() <= STACK_SIZE()); \
} while (0)
#define POP() (assert(STACK_LEVEL() > 0), BASIC_POP())
#define STACK_GROW(n) do { \
assert(n >= 0); \
BASIC_STACKADJ(n); \
assert(STACK_LEVEL() <= STACK_SIZE()); \
} while (0)
#define STACK_SHRINK(n) do { \
assert(n >= 0); \
assert(STACK_LEVEL() >= n); \
BASIC_STACKADJ(-(n)); \
} while (0)
#else
#define PUSH(v) BASIC_PUSH(v)
#define POP() BASIC_POP()
#define STACK_GROW(n) BASIC_STACKADJ(n)
#define STACK_SHRINK(n) BASIC_STACKADJ(-(n))
#endif

#define WITHIN_STACK_BOUNDS() \
(frame->owner == FRAME_OWNED_BY_INTERPRETER || (STACK_LEVEL() >= 0 && STACK_LEVEL() <= STACK_SIZE()))
Expand Down
Loading
Loading