Skip to content

Commit

Permalink
GH-93897: Store frame size in code object and de-opt if insufficient …
Browse files Browse the repository at this point in the history
…space on thread frame stack. (GH-93908)
  • Loading branch information
markshannon authored Jun 20, 2022
1 parent 774ef28 commit 45e62a2
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 67 deletions.
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);
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 @@ -2240,13 +2240,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 @@ -3664,13 +3661,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 @@ -4724,7 +4718,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 @@ -4810,11 +4804,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 @@ -4846,11 +4838,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 @@ -6298,20 +6288,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 @@ -6355,8 +6344,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

0 comments on commit 45e62a2

Please sign in to comment.