Skip to content

Commit 45e62a2

Browse files
authored
GH-93897: Store frame size in code object and de-opt if insufficient space on thread frame stack. (GH-93908)
1 parent 774ef28 commit 45e62a2

File tree

8 files changed

+50
-67
lines changed

8 files changed

+50
-67
lines changed

Include/cpython/code.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ typedef uint16_t _Py_CODEUNIT;
7373
\
7474
/* redundant values (derived from co_localsplusnames and \
7575
co_localspluskinds) */ \
76-
int co_nlocalsplus; /* number of local + cell + free variables \
77-
*/ \
76+
int co_nlocalsplus; /* number of local + cell + free variables */ \
77+
int co_framesize; /* Size of frame in words */ \
7878
int co_nlocals; /* number of local variables */ \
7979
int co_nplaincellvars; /* number of non-arg cell variables */ \
8080
int co_ncellvars; /* total number of cell variables */ \

Include/internal/pycore_frame.h

+27-23
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ extern "C" {
66

77
#include <stdbool.h>
88
#include <stddef.h>
9+
#include "pycore_code.h" // STATS
910

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

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

97-
/* Consumes reference to func */
98+
/* Consumes reference to func and locals */
9899
static inline void
99100
_PyFrame_InitializeSpecials(
100101
_PyInterpreterFrame *frame, PyFunctionObject *func,
101-
PyObject *locals, int nlocalsplus)
102+
PyObject *locals, PyCodeObject *code)
102103
{
103104
frame->f_func = func;
104-
frame->f_code = (PyCodeObject *)Py_NewRef(func->func_code);
105+
frame->f_code = (PyCodeObject *)Py_NewRef(code);
105106
frame->f_builtins = func->func_builtins;
106107
frame->f_globals = func->func_globals;
107-
frame->f_locals = Py_XNewRef(locals);
108-
frame->stacktop = nlocalsplus;
108+
frame->f_locals = locals;
109+
frame->stacktop = code->co_nlocalsplus;
109110
frame->frame_obj = NULL;
110-
frame->prev_instr = _PyCode_CODE(frame->f_code) - 1;
111+
frame->prev_instr = _PyCode_CODE(code) - 1;
111112
frame->is_entry = false;
112113
frame->owner = FRAME_OWNED_BY_THREAD;
113114
}
@@ -172,29 +173,32 @@ _PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame);
172173
void
173174
_PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear);
174175

175-
extern _PyInterpreterFrame *
176-
_PyThreadState_BumpFramePointerSlow(PyThreadState *tstate, size_t size);
177176

178-
static inline _PyInterpreterFrame *
179-
_PyThreadState_BumpFramePointer(PyThreadState *tstate, size_t size)
177+
static inline bool
178+
_PyThreadState_HasStackSpace(PyThreadState *tstate, int size)
180179
{
181-
PyObject **base = tstate->datastack_top;
182-
if (base) {
183-
PyObject **top = base + size;
184-
assert(tstate->datastack_limit);
185-
if (top < tstate->datastack_limit) {
186-
tstate->datastack_top = top;
187-
return (_PyInterpreterFrame *)base;
188-
}
189-
}
190-
return _PyThreadState_BumpFramePointerSlow(tstate, size);
180+
return tstate->datastack_top + size < tstate->datastack_limit;
191181
}
192182

183+
extern _PyInterpreterFrame *
184+
_PyThreadState_PushFrame(PyThreadState *tstate, size_t size);
185+
193186
void _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame *frame);
194187

195-
/* Consume reference to func */
196-
_PyInterpreterFrame *
197-
_PyFrame_Push(PyThreadState *tstate, PyFunctionObject *func);
188+
/* Pushes a frame without checking for space.
189+
* Must be guarded by _PyThreadState_HasStackSpace()
190+
* Consumes reference to func. */
191+
static inline _PyInterpreterFrame *
192+
_PyFrame_PushUnchecked(PyThreadState *tstate, PyFunctionObject *func)
193+
{
194+
CALL_STAT_INC(frames_pushed);
195+
PyCodeObject *code = (PyCodeObject *)func->func_code;
196+
_PyInterpreterFrame *new_frame = (_PyInterpreterFrame *)tstate->datastack_top;
197+
tstate->datastack_top += code->co_framesize;
198+
assert(tstate->datastack_top < tstate->datastack_limit);
199+
_PyFrame_InitializeSpecials(new_frame, func, NULL, code);
200+
return new_frame;
201+
}
198202

199203
int _PyInterpreterFrame_GetLine(_PyInterpreterFrame *frame);
200204

Objects/codeobject.c

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "opcode.h"
55
#include "structmember.h" // PyMemberDef
66
#include "pycore_code.h" // _PyCodeConstructor
7+
#include "pycore_frame.h" // FRAME_SPECIALS_SIZE
78
#include "pycore_interp.h" // PyInterpreterState.co_extra_freefuncs
89
#include "pycore_opcode.h" // _PyOpcode_Deopt
910
#include "pycore_pystate.h" // _PyInterpreterState_GET()
@@ -327,6 +328,7 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
327328
/* derived values */
328329
co->co_nlocalsplus = nlocalsplus;
329330
co->co_nlocals = nlocals;
331+
co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;
330332
co->co_nplaincellvars = nplaincellvars;
331333
co->co_ncellvars = ncellvars;
332334
co->co_nfreevars = nfreevars;

Objects/frameobject.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -858,8 +858,9 @@ init_frame(_PyInterpreterFrame *frame, PyFunctionObject *func, PyObject *locals)
858858
{
859859
/* _PyFrame_InitializeSpecials consumes reference to func */
860860
Py_INCREF(func);
861+
Py_XINCREF(locals);
861862
PyCodeObject *code = (PyCodeObject *)func->func_code;
862-
_PyFrame_InitializeSpecials(frame, func, locals, code->co_nlocalsplus);
863+
_PyFrame_InitializeSpecials(frame, func, locals, code);
863864
for (Py_ssize_t i = 0; i < code->co_nlocalsplus; i++) {
864865
frame->localsplus[i] = NULL;
865866
}

Python/ceval.c

+14-24
Original file line numberDiff line numberDiff line change
@@ -2240,13 +2240,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
22402240
DEOPT_IF(getitem->func_version != cache->func_version, BINARY_SUBSCR);
22412241
PyCodeObject *code = (PyCodeObject *)getitem->func_code;
22422242
assert(code->co_argcount == 2);
2243+
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), BINARY_SUBSCR);
22432244
STAT_INC(BINARY_SUBSCR, hit);
2244-
22452245
Py_INCREF(getitem);
2246-
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, getitem);
2247-
if (new_frame == NULL) {
2248-
goto error;
2249-
}
2246+
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, getitem);
22502247
CALL_STAT_INC(inlined_py_calls);
22512248
STACK_SHRINK(2);
22522249
new_frame->localsplus[0] = container;
@@ -3664,13 +3661,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
36643661
DEOPT_IF(f->func_version != func_version, LOAD_ATTR);
36653662
PyCodeObject *code = (PyCodeObject *)f->func_code;
36663663
assert(code->co_argcount == 1);
3664+
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), LOAD_ATTR);
36673665
STAT_INC(LOAD_ATTR, hit);
3668-
36693666
Py_INCREF(fget);
3670-
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, f);
3671-
if (new_frame == NULL) {
3672-
goto error;
3673-
}
3667+
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, f);
36743668
SET_TOP(NULL);
36753669
int push_null = !(oparg & 1);
36763670
STACK_SHRINK(push_null);
@@ -4724,7 +4718,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
47244718
// Check if the call can be inlined or not
47254719
if (Py_TYPE(function) == &PyFunction_Type && tstate->interp->eval_frame == NULL) {
47264720
int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(function))->co_flags;
4727-
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function);
4721+
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(function));
47284722
STACK_SHRINK(total_args);
47294723
_PyInterpreterFrame *new_frame = _PyEvalFramePushAndInit(
47304724
tstate, (PyFunctionObject *)function, locals,
@@ -4810,11 +4804,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
48104804
DEOPT_IF(func->func_version != read_u32(cache->func_version), CALL);
48114805
PyCodeObject *code = (PyCodeObject *)func->func_code;
48124806
DEOPT_IF(code->co_argcount != argcount, CALL);
4807+
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL);
48134808
STAT_INC(CALL, hit);
4814-
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, func);
4815-
if (new_frame == NULL) {
4816-
goto error;
4817-
}
4809+
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, func);
48184810
CALL_STAT_INC(inlined_py_calls);
48194811
STACK_SHRINK(argcount);
48204812
for (int i = 0; i < argcount; i++) {
@@ -4846,11 +4838,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
48464838
DEOPT_IF(argcount > code->co_argcount, CALL);
48474839
int minargs = cache->min_args;
48484840
DEOPT_IF(argcount < minargs, CALL);
4841+
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize), CALL);
48494842
STAT_INC(CALL, hit);
4850-
_PyInterpreterFrame *new_frame = _PyFrame_Push(tstate, func);
4851-
if (new_frame == NULL) {
4852-
goto error;
4853-
}
4843+
_PyInterpreterFrame *new_frame = _PyFrame_PushUnchecked(tstate, func);
48544844
CALL_STAT_INC(inlined_py_calls);
48554845
STACK_SHRINK(argcount);
48564846
for (int i = 0; i < argcount; i++) {
@@ -6298,20 +6288,19 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
62986288
return -1;
62996289
}
63006290

6301-
/* Consumes references to func and all the args */
6291+
/* Consumes references to func, locals and all the args */
63026292
static _PyInterpreterFrame *
63036293
_PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
63046294
PyObject *locals, PyObject* const* args,
63056295
size_t argcount, PyObject *kwnames)
63066296
{
63076297
PyCodeObject * code = (PyCodeObject *)func->func_code;
6308-
size_t size = code->co_nlocalsplus + code->co_stacksize + FRAME_SPECIALS_SIZE;
63096298
CALL_STAT_INC(frames_pushed);
6310-
_PyInterpreterFrame *frame = _PyThreadState_BumpFramePointer(tstate, size);
6299+
_PyInterpreterFrame *frame = _PyThreadState_PushFrame(tstate, code->co_framesize);
63116300
if (frame == NULL) {
63126301
goto fail;
63136302
}
6314-
_PyFrame_InitializeSpecials(frame, func, locals, code->co_nlocalsplus);
6303+
_PyFrame_InitializeSpecials(frame, func, locals, code);
63156304
PyObject **localsarray = &frame->localsplus[0];
63166305
for (int i = 0; i < code->co_nlocalsplus; i++) {
63176306
localsarray[i] = NULL;
@@ -6355,8 +6344,9 @@ _PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func,
63556344
PyObject *kwnames)
63566345
{
63576346
/* _PyEvalFramePushAndInit consumes the references
6358-
* to func and all its arguments */
6347+
* to func, locals and all its arguments */
63596348
Py_INCREF(func);
6349+
Py_XINCREF(locals);
63606350
for (size_t i = 0; i < argcount; i++) {
63616351
Py_INCREF(args[i]);
63626352
}

Python/frame.c

-16
Original file line numberDiff line numberDiff line change
@@ -114,22 +114,6 @@ _PyFrame_Clear(_PyInterpreterFrame *frame)
114114
Py_DECREF(frame->f_code);
115115
}
116116

117-
/* Consumes reference to func */
118-
_PyInterpreterFrame *
119-
_PyFrame_Push(PyThreadState *tstate, PyFunctionObject *func)
120-
{
121-
PyCodeObject *code = (PyCodeObject *)func->func_code;
122-
size_t size = code->co_nlocalsplus + code->co_stacksize + FRAME_SPECIALS_SIZE;
123-
CALL_STAT_INC(frames_pushed);
124-
_PyInterpreterFrame *new_frame = _PyThreadState_BumpFramePointer(tstate, size);
125-
if (new_frame == NULL) {
126-
Py_DECREF(func);
127-
return NULL;
128-
}
129-
_PyFrame_InitializeSpecials(new_frame, func, NULL, code->co_nlocalsplus);
130-
return new_frame;
131-
}
132-
133117
int
134118
_PyInterpreterFrame_GetLine(_PyInterpreterFrame *frame)
135119
{

Python/pystate.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -2168,7 +2168,7 @@ push_chunk(PyThreadState *tstate, int size)
21682168
}
21692169

21702170
_PyInterpreterFrame *
2171-
_PyThreadState_BumpFramePointerSlow(PyThreadState *tstate, size_t size)
2171+
_PyThreadState_PushFrame(PyThreadState *tstate, size_t size)
21722172
{
21732173
assert(size < INT_MAX/sizeof(PyObject *));
21742174
PyObject **base = tstate->datastack_top;

Tools/scripts/deepfreeze.py

+2
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def __init__(self, file: TextIO) -> None:
118118
self.write('#include "Python.h"')
119119
self.write('#include "internal/pycore_gc.h"')
120120
self.write('#include "internal/pycore_code.h"')
121+
self.write('#include "internal/pycore_frame.h"')
121122
self.write('#include "internal/pycore_long.h"')
122123
self.write("")
123124

@@ -256,6 +257,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str:
256257
self.field(code, "co_argcount")
257258
self.field(code, "co_posonlyargcount")
258259
self.field(code, "co_kwonlyargcount")
260+
self.write(f".co_framesize = {code.co_stacksize + len(localsplusnames)} + FRAME_SPECIALS_SIZE,")
259261
self.field(code, "co_stacksize")
260262
self.field(code, "co_firstlineno")
261263
self.write(f".co_nlocalsplus = {len(localsplusnames)},")

0 commit comments

Comments
 (0)