Skip to content
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

GH-93897: Store frame size in code object and de-opt if insufficient space on thread frame stack. #93908

Merged
merged 6 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ typedef uint16_t _Py_CODEUNIT;
\
/* redundant values (derived from co_localsplusnames and \
co_localspluskinds) */ \
int co_nlocalsplus; /* number of local + cell + free variables \
*/ \
int co_nlocalsplus; /* number of local + cell + free variables */ \
int co_framesize; /* Size of frame in words */ \
int co_nlocals; /* number of local variables */ \
int co_nplaincellvars; /* number of non-arg cell variables */ \
int co_ncellvars; /* total number of cell variables */ \
Expand Down
50 changes: 27 additions & 23 deletions Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern "C" {

#include <stdbool.h>
#include <stddef.h>
#include "pycore_code.h" // STATS

/* See Objects/frame_layout.md for an explanation of the frame stack
* including explanation of the PyFrameObject and _PyInterpreterFrame
Expand Down Expand Up @@ -94,20 +95,20 @@ static inline void _PyFrame_StackPush(_PyInterpreterFrame *f, PyObject *value) {

void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *dest);

/* Consumes reference to func */
/* Consumes reference to func and locals */
static inline void
_PyFrame_InitializeSpecials(
_PyInterpreterFrame *frame, PyFunctionObject *func,
PyObject *locals, int nlocalsplus)
PyObject *locals, PyCodeObject *code)
{
frame->f_func = func;
frame->f_code = (PyCodeObject *)Py_NewRef(func->func_code);
frame->f_code = (PyCodeObject *)Py_NewRef(code);
frame->f_builtins = func->func_builtins;
frame->f_globals = func->func_globals;
frame->f_locals = Py_XNewRef(locals);
Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved
frame->stacktop = nlocalsplus;
frame->f_locals = locals;
frame->stacktop = code->co_nlocalsplus;
frame->frame_obj = NULL;
frame->prev_instr = _PyCode_CODE(frame->f_code) - 1;
frame->prev_instr = _PyCode_CODE(code) - 1;
frame->is_entry = false;
frame->owner = FRAME_OWNED_BY_THREAD;
}
Expand Down Expand Up @@ -172,29 +173,32 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame);
void
_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear);

extern _PyInterpreterFrame *
_PyThreadState_BumpFramePointerSlow(PyThreadState *tstate, size_t size);

static inline _PyInterpreterFrame *
_PyThreadState_BumpFramePointer(PyThreadState *tstate, size_t size)
static inline bool
_PyThreadState_HasStackSpace(PyThreadState *tstate, int size)
{
PyObject **base = tstate->datastack_top;
if (base) {
PyObject **top = base + size;
assert(tstate->datastack_limit);
if (top < tstate->datastack_limit) {
tstate->datastack_top = top;
return (_PyInterpreterFrame *)base;
}
}
return _PyThreadState_BumpFramePointerSlow(tstate, size);
return tstate->datastack_top + size < tstate->datastack_limit;
}

extern _PyInterpreterFrame *
_PyThreadState_PushFrame(PyThreadState *tstate, size_t size);

void _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame *frame);

/* Consume reference to func */
_PyInterpreterFrame *
_PyFrame_Push(PyThreadState *tstate, PyFunctionObject *func);
/* Pushes a frame without checking for space.
* Must be guarded by _PyThreadState_HasStackSpace()
* Consumes reference to func. */
static inline _PyInterpreterFrame *
_PyFrame_PushUnchecked(PyThreadState *tstate, PyFunctionObject *func)
{
CALL_STAT_INC(frames_pushed);
PyCodeObject *code = (PyCodeObject *)func->func_code;
_PyInterpreterFrame *new_frame = (_PyInterpreterFrame *)tstate->datastack_top;
tstate->datastack_top += code->co_framesize;
assert(tstate->datastack_top < tstate->datastack_limit);
_PyFrame_InitializeSpecials(new_frame, func, NULL, code);
return new_frame;
}

int _PyInterpreterFrame_GetLine(_PyInterpreterFrame *frame);

Expand Down
2 changes: 2 additions & 0 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "opcode.h"
#include "structmember.h" // PyMemberDef
#include "pycore_code.h" // _PyCodeConstructor
#include "pycore_frame.h" // FRAME_SPECIALS_SIZE
#include "pycore_interp.h" // PyInterpreterState.co_extra_freefuncs
#include "pycore_opcode.h" // _PyOpcode_Deopt
#include "pycore_pystate.h" // _PyInterpreterState_GET()
Expand Down Expand Up @@ -327,6 +328,7 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
/* derived values */
co->co_nlocalsplus = nlocalsplus;
co->co_nlocals = nlocals;
co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;
co->co_nplaincellvars = nplaincellvars;
co->co_ncellvars = ncellvars;
co->co_nfreevars = nfreevars;
Expand Down
3 changes: 2 additions & 1 deletion Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -858,8 +858,9 @@ init_frame(_PyInterpreterFrame *frame, PyFunctionObject *func, PyObject *locals)
{
/* _PyFrame_InitializeSpecials consumes reference to func */
Py_INCREF(func);
Py_XINCREF(locals);
PyCodeObject *code = (PyCodeObject *)func->func_code;
_PyFrame_InitializeSpecials(frame, func, locals, code->co_nlocalsplus);
_PyFrame_InitializeSpecials(frame, func, locals, code);
for (Py_ssize_t i = 0; i < code->co_nlocalsplus; i++) {
frame->localsplus[i] = NULL;
}
Expand Down
38 changes: 14 additions & 24 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2241,13 +2241,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
DEOPT_IF(getitem->func_version != cache->func_version, BINARY_SUBSCR);
PyCodeObject *code = (PyCodeObject *)getitem->func_code;
assert(code->co_argcount == 2);
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), BINARY_SUBSCR);
STAT_INC(BINARY_SUBSCR, hit);

Py_INCREF(getitem);
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, getitem);
if (new_frame == NULL) {
goto error;
}
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, getitem);
CALL_STAT_INC(inlined_py_calls);
STACK_SHRINK(2);
new_frame->localsplus[0] = container;
Expand Down Expand Up @@ -3665,13 +3662,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
DEOPT_IF(f->func_version != func_version, LOAD_ATTR);
PyCodeObject *code = (PyCodeObject *)f->func_code;
assert(code->co_argcount == 1);
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), LOAD_ATTR);
STAT_INC(LOAD_ATTR, hit);

Py_INCREF(fget);
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, f);
if (new_frame == NULL) {
goto error;
}
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, f);
SET_TOP(NULL);
int push_null = !(oparg & 1);
STACK_SHRINK(push_null);
Expand Down Expand Up @@ -4725,7 +4719,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
// Check if the call can be inlined or not
if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL) {
int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags;
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function);
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(function));
STACK_SHRINK(total_args);
_PyInterpreterFrame *new_frame = _PyEvalFramePushAndInit(
tstate, (PyFunctionObject *)function, locals,
Expand Down Expand Up @@ -4811,11 +4805,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
DEOPT_IF(func->func_version != read_u32(cache->func_version), CALL);
PyCodeObject *code = (PyCodeObject *)func->func_code;
DEOPT_IF(code->co_argcount != argcount, CALL);
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL);
STAT_INC(CALL, hit);
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, func);
if (new_frame == NULL) {
goto error;
}
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, func);
CALL_STAT_INC(inlined_py_calls);
STACK_SHRINK(argcount);
for (int i = 0; i < argcount; i++) {
Expand Down Expand Up @@ -4847,11 +4839,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
DEOPT_IF(argcount > code->co_argcount, CALL);
int minargs = cache->min_args;
DEOPT_IF(argcount < minargs, CALL);
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL);
STAT_INC(CALL, hit);
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, func);
if (new_frame == NULL) {
goto error;
}
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, func);
CALL_STAT_INC(inlined_py_calls);
STACK_SHRINK(argcount);
for (int i = 0; i < argcount; i++) {
Expand Down Expand Up @@ -6299,20 +6289,19 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
return -1;
}

/* Consumes references to func and all the args */
/* Consumes references to func, locals and all the args */
static _PyInterpreterFrame *
_PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
PyObject *locals, PyObject* const* args,
size_t argcount, PyObject *kwnames)
{
PyCodeObject * code = (PyCodeObject *)func->func_code;
size_t size = code->co_nlocalsplus + code->co_stacksize + FRAME_SPECIALS_SIZE;
CALL_STAT_INC(frames_pushed);
_PyInterpreterFrame *frame = _PyThreadState_BumpFramePointer(tstate, size);
_PyInterpreterFrame *frame = _PyThreadState_PushFrame(tstate, code->co_framesize);
if (frame == NULL) {
goto fail;
}
_PyFrame_InitializeSpecials(frame, func, locals, code->co_nlocalsplus);
_PyFrame_InitializeSpecials(frame, func, locals, code);
PyObject **localsarray = &frame->localsplus[0];
for (int i = 0; i < code->co_nlocalsplus; i++) {
localsarray[i] = NULL;
Expand Down Expand Up @@ -6356,8 +6345,9 @@ _PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func,
PyObject *kwnames)
{
/* _PyEvalFramePushAndInit consumes the references
* to func and all its arguments */
* to func, locals and all its arguments */
Py_INCREF(func);
Py_XINCREF(locals);
for (size_t i = 0; i < argcount; i++) {
Py_INCREF(args[i]);
}
Expand Down
16 changes: 0 additions & 16 deletions Python/frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,6 @@ _PyFrame_Clear(_PyInterpreterFrame *frame)
Py_DECREF(frame->f_code);
}

/* Consumes reference to func */
_PyInterpreterFrame *
_PyFrame_Push(PyThreadState *tstate, PyFunctionObject *func)
{
PyCodeObject *code = (PyCodeObject *)func->func_code;
size_t size = code->co_nlocalsplus + code->co_stacksize + FRAME_SPECIALS_SIZE;
CALL_STAT_INC(frames_pushed);
_PyInterpreterFrame *new_frame = _PyThreadState_BumpFramePointer(tstate, size);
if (new_frame == NULL) {
Py_DECREF(func);
return NULL;
}
_PyFrame_InitializeSpecials(new_frame, func, NULL, code->co_nlocalsplus);
return new_frame;
}

int
_PyInterpreterFrame_GetLine(_PyInterpreterFrame *frame)
{
Expand Down
2 changes: 1 addition & 1 deletion Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -2168,7 +2168,7 @@ push_chunk(PyThreadState *tstate, int size)
}

_PyInterpreterFrame *
_PyThreadState_BumpFramePointerSlow(PyThreadState *tstate, size_t size)
_PyThreadState_PushFrame(PyThreadState *tstate, size_t size)
{
assert(size < INT_MAX/sizeof(PyObject *));
PyObject **base = tstate->datastack_top;
Expand Down
2 changes: 2 additions & 0 deletions Tools/scripts/deepfreeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def __init__(self, file: TextIO) -> None:
self.write('#include "Python.h"')
self.write('#include "internal/pycore_gc.h"')
self.write('#include "internal/pycore_code.h"')
self.write('#include "internal/pycore_frame.h"')
self.write('#include "internal/pycore_long.h"')
self.write("")

Expand Down Expand Up @@ -256,6 +257,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
self.field(code, "co_argcount")
self.field(code, "co_posonlyargcount")
self.field(code, "co_kwonlyargcount")
self.write(f".co_framesize = {code.co_stacksize + len(localsplusnames)} + FRAME_SPECIALS_SIZE,")
self.field(code, "co_stacksize")
self.field(code, "co_firstlineno")
self.write(f".co_nlocalsplus = {len(localsplusnames)},")
Expand Down