Skip to content

Commit b11a951

Browse files
authored
bpo-44032: Move data stack to thread from FrameObject. (GH-26076)
* Remove 'zombie' frames. We won't need them once we are allocating fixed-size frames. * Add co_nlocalplus field to code object to avoid recomputing size of locals + frees + cells. * Move locals, cells and freevars out of frame object into separate memory buffer. * Use per-threadstate allocated memory chunks for local variables. * Move globals and builtins from frame object to per-thread stack. * Move (slow) locals frame object to per-thread stack. * Move internal frame functions to internal header.
1 parent be4dd7f commit b11a951

File tree

20 files changed

+454
-250
lines changed

20 files changed

+454
-250
lines changed

Include/cpython/code.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ struct PyCodeObject {
4040
PyObject *co_name; /* unicode (name, for reference) */
4141
PyObject *co_linetable; /* string (encoding addr<->lineno mapping) See
4242
Objects/lnotab_notes.txt for details. */
43+
int co_nlocalsplus; /* Number of locals + free + cell variables */
4344
PyObject *co_exceptiontable; /* Byte string encoding exception handling table */
44-
void *co_zombieframe; /* for optimization only (see frameobject.c) */
4545
PyObject *co_weakreflist; /* to support weakrefs to code objects */
4646
/* Scratch space for extra data relating to the code object.
4747
Type is a void* to keep the format private in codeobject.c to force

Include/cpython/frameobject.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,9 @@ enum _framestate {
2020
typedef signed char PyFrameState;
2121

2222
struct _frame {
23-
PyObject_VAR_HEAD
23+
PyObject_HEAD
2424
struct _frame *f_back; /* previous frame, or NULL */
2525
PyCodeObject *f_code; /* code segment */
26-
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */
27-
PyObject *f_globals; /* global symbol table (PyDictObject) */
28-
PyObject *f_locals; /* local symbol table (any mapping) */
2926
PyObject **f_valuestack; /* points after the last local */
3027
PyObject *f_trace; /* Trace function */
3128
/* Borrowed reference to a generator, or NULL */
@@ -36,7 +33,8 @@ struct _frame {
3633
PyFrameState f_state; /* What state the frame is in */
3734
char f_trace_lines; /* Emit per-line trace events? */
3835
char f_trace_opcodes; /* Emit per-opcode trace events? */
39-
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
36+
char f_own_locals_memory; /* This frame owns the memory for the locals */
37+
PyObject **f_localsptr; /* Pointer to locals, cells, free */
4038
};
4139

4240
static inline int _PyFrame_IsRunnable(struct _frame *f) {
@@ -62,7 +60,7 @@ PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *,
6260

6361
/* only internal use */
6462
PyFrameObject*
65-
_PyFrame_New_NoTrack(PyThreadState *, PyFrameConstructor *, PyObject *);
63+
_PyFrame_New_NoTrack(PyThreadState *, PyFrameConstructor *, PyObject *, PyObject **);
6664

6765

6866
/* The rest of the interface is specific for frame objects */

Include/cpython/pystate.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ typedef struct _err_stackitem {
5757

5858
} _PyErr_StackItem;
5959

60+
typedef struct _stack_chunk {
61+
struct _stack_chunk *previous;
62+
size_t size;
63+
size_t top;
64+
PyObject * data[1]; /* Variable sized */
65+
} _PyStackChunk;
6066

6167
// The PyThreadState typedef is in Include/pystate.h.
6268
struct _ts {
@@ -149,6 +155,9 @@ struct _ts {
149155

150156
CFrame root_cframe;
151157

158+
_PyStackChunk *datastack_chunk;
159+
PyObject **datastack_top;
160+
PyObject **datastack_limit;
152161
/* XXX signal handlers should also be here */
153162

154163
};

Include/genobject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ extern "C" {
1818
/* Note: gi_frame can be NULL if the generator is "finished" */ \
1919
PyFrameObject *prefix##_frame; \
2020
/* The code object backing the generator */ \
21-
PyObject *prefix##_code; \
21+
PyCodeObject *prefix##_code; \
2222
/* List of weak reference. */ \
2323
PyObject *prefix##_weakreflist; \
2424
/* Name of the generator. */ \

Include/internal/pycore_frame.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#ifndef Py_INTERNAL_FRAME_H
2+
#define Py_INTERNAL_FRAME_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
enum {
8+
FRAME_SPECIALS_GLOBALS_OFFSET = 0,
9+
FRAME_SPECIALS_BUILTINS_OFFSET = 1,
10+
FRAME_SPECIALS_LOCALS_OFFSET = 2,
11+
FRAME_SPECIALS_SIZE = 3
12+
};
13+
14+
static inline PyObject **
15+
_PyFrame_Specials(PyFrameObject *f) {
16+
return &f->f_valuestack[-FRAME_SPECIALS_SIZE];
17+
}
18+
19+
/* Returns a *borrowed* reference. */
20+
static inline PyObject *
21+
_PyFrame_GetGlobals(PyFrameObject *f)
22+
{
23+
return _PyFrame_Specials(f)[FRAME_SPECIALS_GLOBALS_OFFSET];
24+
}
25+
26+
/* Returns a *borrowed* reference. */
27+
static inline PyObject *
28+
_PyFrame_GetBuiltins(PyFrameObject *f)
29+
{
30+
return _PyFrame_Specials(f)[FRAME_SPECIALS_BUILTINS_OFFSET];
31+
}
32+
33+
int _PyFrame_TakeLocals(PyFrameObject *f);
34+
35+
#ifdef __cplusplus
36+
}
37+
#endif
38+
#endif /* !Py_INTERNAL_FRAME_H */

Include/internal/pycore_pymem.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ struct _PyTraceMalloc_Config {
9494

9595
PyAPI_DATA(struct _PyTraceMalloc_Config) _Py_tracemalloc_config;
9696

97+
/* Allocate memory directly from the O/S virtual memory system,
98+
* where supported. Otherwise fallback on malloc */
99+
void *_PyObject_VirtualAlloc(size_t size);
100+
void _PyObject_VirtualFree(void *, size_t size);
101+
97102

98103
#ifdef __cplusplus
99104
}

Include/internal/pycore_pystate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ PyAPI_FUNC(int) _PyState_AddModule(
147147

148148
PyAPI_FUNC(int) _PyOS_InterruptOccurred(PyThreadState *tstate);
149149

150+
PyObject **_PyThreadState_PushLocals(PyThreadState *, int size);
151+
void _PyThreadState_PopLocals(PyThreadState *, PyObject **);
152+
150153
#ifdef __cplusplus
151154
}
152155
#endif

Lib/test/test_gdb.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -666,15 +666,16 @@ def test_builtin_method(self):
666666

667667
def test_frames(self):
668668
gdb_output = self.get_stack_trace('''
669+
import sys
669670
def foo(a, b, c):
670-
pass
671+
return sys._getframe(0)
671672
672-
foo(3, 4, 5)
673-
id(foo.__code__)''',
673+
f = foo(3, 4, 5)
674+
id(f)''',
674675
breakpoint='builtin_id',
675-
cmds_after_breakpoint=['print (PyFrameObject*)(((PyCodeObject*)v)->co_zombieframe)']
676+
cmds_after_breakpoint=['print (PyFrameObject*)v']
676677
)
677-
self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 3, in foo \(\)\s+.*',
678+
self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 4, in foo \(a=3.*',
678679
gdb_output,
679680
re.DOTALL),
680681
'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))

Lib/test/test_sys.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,11 +1274,7 @@ class C(object): pass
12741274
# frame
12751275
import inspect
12761276
x = inspect.currentframe()
1277-
ncells = len(x.f_code.co_cellvars)
1278-
nfrees = len(x.f_code.co_freevars)
1279-
localsplus = x.f_code.co_stacksize + x.f_code.co_nlocals +\
1280-
ncells + nfrees
1281-
check(x, vsize('8P3i3c' + localsplus*'P'))
1277+
check(x, size('5P3i4cP'))
12821278
# function
12831279
def func(): pass
12841280
check(func, size('14P'))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Move 'fast' locals and other variables from the frame object to a per-thread
2+
datastack.

Objects/codeobject.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,
280280
co->co_posonlyargcount = posonlyargcount;
281281
co->co_kwonlyargcount = kwonlyargcount;
282282
co->co_nlocals = nlocals;
283+
co->co_nlocalsplus = nlocals +
284+
(int)PyTuple_GET_SIZE(freevars) + (int)PyTuple_GET_SIZE(cellvars);
283285
co->co_stacksize = stacksize;
284286
co->co_flags = flags;
285287
Py_INCREF(code);
@@ -304,7 +306,6 @@ PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount,
304306
co->co_linetable = linetable;
305307
Py_INCREF(exceptiontable);
306308
co->co_exceptiontable = exceptiontable;
307-
co->co_zombieframe = NULL;
308309
co->co_weakreflist = NULL;
309310
co->co_extra = NULL;
310311

@@ -968,8 +969,6 @@ code_dealloc(PyCodeObject *co)
968969
Py_XDECREF(co->co_exceptiontable);
969970
if (co->co_cell2arg != NULL)
970971
PyMem_Free(co->co_cell2arg);
971-
if (co->co_zombieframe != NULL)
972-
PyObject_GC_Del(co->co_zombieframe);
973972
if (co->co_weakreflist != NULL)
974973
PyObject_ClearWeakRefs((PyObject*)co);
975974
PyObject_Free(co);

0 commit comments

Comments
 (0)