Skip to content

Commit 22b0de2

Browse files
gh-117139: Convert the evaluation stack to stack refs (#118450)
This PR sets up tagged pointers for CPython. The general idea is to create a separate struct _PyStackRef for everything on the evaluation stack to store the bits. This forces the C compiler to warn us if we try to cast things or pull things out of the struct directly. Only for free threading: We tag the low bit if something is deferred - that means we skip incref and decref operations on it. This behavior may change in the future if Mark's plans to defer all objects in the interpreter loop pans out. This implies a strict stack reference discipline is required. ALL incref and decref operations on stackrefs must use the stackref variants. It is unsafe to untag something then do normal incref/decref ops on it. The new incref and decref variants are called dup and close. They mimic a "handle" API operating on these stackrefs. Please read Include/internal/pycore_stackref.h for more information! --------- Co-authored-by: Mark Shannon <9448417+markshannon@users.noreply.github.com>
1 parent d611c4c commit 22b0de2

35 files changed

Lines changed: 5215 additions & 3745 deletions

‎Include/internal/pycore_ceval.h‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,11 @@ PyAPI_FUNC(void) _PyEval_FormatExcUnbound(PyThreadState *tstate, PyCodeObject *c
261261
PyAPI_FUNC(void) _PyEval_FormatKwargsError(PyThreadState*tstate, PyObject*func, PyObject*kwargs);
262262
PyAPI_FUNC(PyObject*)_PyEval_MatchClass(PyThreadState*tstate, PyObject*subject, PyObject*type, Py_ssize_tnargs, PyObject*kwargs);
263263
PyAPI_FUNC(PyObject*)_PyEval_MatchKeys(PyThreadState*tstate, PyObject*map, PyObject*keys);
264-
PyAPI_FUNC(int) _PyEval_UnpackIterable(PyThreadState*tstate, PyObject*v, intargcnt, intargcntafter, PyObject**sp);
264+
PyAPI_FUNC(int) _PyEval_UnpackIterableStackRef(PyThreadState*tstate, _PyStackRefv, intargcnt, intargcntafter, _PyStackRef*sp);
265265
PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState*tstate, _PyInterpreterFrame*frame);
266+
PyAPI_FUNC(PyObject**) _PyObjectArray_FromStackRefArray(_PyStackRef*input, Py_ssize_tnargs, PyObject**scratch);
267+
268+
PyAPI_FUNC(void) _PyObjectArray_Free(PyObject**array, PyObject**scratch);
266269

267270

268271
/* Bits that can be set in PyThreadState.eval_breaker */

‎Include/internal/pycore_code.h‎

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
#include"pycore_stackref.h"// _PyStackRef
1112
#include"pycore_lock.h"// PyMutex
1213
#include"pycore_backoff.h"// _Py_BackoffCounter
1314

@@ -317,30 +318,30 @@ extern void _PyCode_Clear_Executors(PyCodeObject *code);
317318

318319
/* Specialization functions */
319320

320-
externvoid_Py_Specialize_LoadSuperAttr(PyObject*global_super, PyObject*cls,
321+
externvoid_Py_Specialize_LoadSuperAttr(_PyStackRefglobal_super, _PyStackRefcls,
321322
_Py_CODEUNIT*instr, intload_method);
322-
externvoid_Py_Specialize_LoadAttr(PyObject*owner, _Py_CODEUNIT*instr,
323+
externvoid_Py_Specialize_LoadAttr(_PyStackRefowner, _Py_CODEUNIT*instr,
323324
PyObject*name);
324-
externvoid_Py_Specialize_StoreAttr(PyObject*owner, _Py_CODEUNIT*instr,
325+
externvoid_Py_Specialize_StoreAttr(_PyStackRefowner, _Py_CODEUNIT*instr,
325326
PyObject*name);
326327
externvoid_Py_Specialize_LoadGlobal(PyObject*globals, PyObject*builtins,
327328
_Py_CODEUNIT*instr, PyObject*name);
328-
externvoid_Py_Specialize_BinarySubscr(PyObject*sub, PyObject*container,
329+
externvoid_Py_Specialize_BinarySubscr(_PyStackRefsub, _PyStackRefcontainer,
329330
_Py_CODEUNIT*instr);
330-
externvoid_Py_Specialize_StoreSubscr(PyObject*container, PyObject*sub,
331+
externvoid_Py_Specialize_StoreSubscr(_PyStackRefcontainer, _PyStackRefsub,
331332
_Py_CODEUNIT*instr);
332-
externvoid_Py_Specialize_Call(PyObject*callable, _Py_CODEUNIT*instr,
333+
externvoid_Py_Specialize_Call(_PyStackRefcallable, _Py_CODEUNIT*instr,
333334
intnargs);
334-
externvoid_Py_Specialize_BinaryOp(PyObject*lhs, PyObject*rhs, _Py_CODEUNIT*instr,
335-
intoparg, PyObject**locals);
336-
externvoid_Py_Specialize_CompareOp(PyObject*lhs, PyObject*rhs,
335+
externvoid_Py_Specialize_BinaryOp(_PyStackReflhs, _PyStackRefrhs, _Py_CODEUNIT*instr,
336+
intoparg, _PyStackRef*locals);
337+
externvoid_Py_Specialize_CompareOp(_PyStackReflhs, _PyStackRefrhs,
337338
_Py_CODEUNIT*instr, intoparg);
338-
externvoid_Py_Specialize_UnpackSequence(PyObject*seq, _Py_CODEUNIT*instr,
339+
externvoid_Py_Specialize_UnpackSequence(_PyStackRefseq, _Py_CODEUNIT*instr,
339340
intoparg);
340-
externvoid_Py_Specialize_ForIter(PyObject*iter, _Py_CODEUNIT*instr, intoparg);
341-
externvoid_Py_Specialize_Send(PyObject*receiver, _Py_CODEUNIT*instr);
342-
externvoid_Py_Specialize_ToBool(PyObject*value, _Py_CODEUNIT*instr);
343-
externvoid_Py_Specialize_ContainsOp(PyObject*value, _Py_CODEUNIT*instr);
341+
externvoid_Py_Specialize_ForIter(_PyStackRefiter, _Py_CODEUNIT*instr, intoparg);
342+
externvoid_Py_Specialize_Send(_PyStackRefreceiver, _Py_CODEUNIT*instr);
343+
externvoid_Py_Specialize_ToBool(_PyStackRefvalue, _Py_CODEUNIT*instr);
344+
externvoid_Py_Specialize_ContainsOp(_PyStackRefvalue, _Py_CODEUNIT*instr);
344345

345346
#ifdefPy_STATS
346347

‎Include/internal/pycore_frame.h‎

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extern "C" {
1111
#include<stdbool.h>
1212
#include<stddef.h>// offsetof()
1313
#include"pycore_code.h"// STATS
14+
#include"pycore_stackref.h"// _PyStackRef
1415

1516
/* See Objects/frame_layout.md for an explanation of the frame stack
1617
* including explanation of the PyFrameObject and _PyInterpreterFrame
@@ -67,7 +68,7 @@ typedef struct _PyInterpreterFrame {
6768
uint16_treturn_offset; /* Only relevant during a function call */
6869
charowner;
6970
/* Locals and stack */
70-
PyObject*localsplus[1];
71+
_PyStackReflocalsplus[1];
7172
} _PyInterpreterFrame;
7273

7374
#define_PyInterpreterFrame_LASTI(IF) \
@@ -78,23 +79,23 @@ static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
7879
return (PyCodeObject*)f->f_executable;
7980
}
8081

81-
staticinlinePyObject**_PyFrame_Stackbase(_PyInterpreterFrame*f) {
82-
returnf->localsplus+_PyFrame_GetCode(f)->co_nlocalsplus;
82+
staticinline_PyStackRef*_PyFrame_Stackbase(_PyInterpreterFrame*f) {
83+
return(f->localsplus+_PyFrame_GetCode(f)->co_nlocalsplus);
8384
}
8485

85-
staticinlinePyObject*_PyFrame_StackPeek(_PyInterpreterFrame*f) {
86+
staticinline_PyStackRef_PyFrame_StackPeek(_PyInterpreterFrame*f) {
8687
assert(f->stacktop>_PyFrame_GetCode(f)->co_nlocalsplus);
87-
assert(f->localsplus[f->stacktop-1]!=NULL);
88+
assert(!PyStackRef_IsNull(f->localsplus[f->stacktop-1]));
8889
returnf->localsplus[f->stacktop-1];
8990
}
9091

91-
staticinlinePyObject*_PyFrame_StackPop(_PyInterpreterFrame*f) {
92+
staticinline_PyStackRef_PyFrame_StackPop(_PyInterpreterFrame*f) {
9293
assert(f->stacktop>_PyFrame_GetCode(f)->co_nlocalsplus);
9394
f->stacktop--;
9495
returnf->localsplus[f->stacktop];
9596
}
9697

97-
staticinlinevoid_PyFrame_StackPush(_PyInterpreterFrame*f, PyObject*value) {
98+
staticinlinevoid_PyFrame_StackPush(_PyInterpreterFrame*f, _PyStackRefvalue) {
9899
f->localsplus[f->stacktop] =value;
99100
f->stacktop++;
100101
}
@@ -143,14 +144,14 @@ _PyFrame_Initialize(
143144
frame->owner=FRAME_OWNED_BY_THREAD;
144145

145146
for (inti=null_locals_from; i<code->co_nlocalsplus; i++) {
146-
frame->localsplus[i] =NULL;
147+
frame->localsplus[i] =PyStackRef_NULL;
147148
}
148149
}
149150

150151
/* Gets the pointer to the locals array
151152
* that precedes this frame.
152153
*/
153-
staticinlinePyObject**
154+
staticinline_PyStackRef*
154155
_PyFrame_GetLocalsArray(_PyInterpreterFrame*frame)
155156
{
156157
returnframe->localsplus;
@@ -160,16 +161,16 @@ _PyFrame_GetLocalsArray(_PyInterpreterFrame *frame)
160161
Having stacktop <= 0 ensures that invalid
161162
values are not visible to the cycle GC.
162163
We choose -1 rather than 0 to assist debugging. */
163-
staticinlinePyObject**
164+
staticinline_PyStackRef*
164165
_PyFrame_GetStackPointer(_PyInterpreterFrame*frame)
165166
{
166-
PyObject**sp=frame->localsplus+frame->stacktop;
167+
_PyStackRef*sp=frame->localsplus+frame->stacktop;
167168
frame->stacktop=-1;
168169
returnsp;
169170
}
170171

171172
staticinlinevoid
172-
_PyFrame_SetStackPointer(_PyInterpreterFrame*frame, PyObject**stack_pointer)
173+
_PyFrame_SetStackPointer(_PyInterpreterFrame*frame, _PyStackRef*stack_pointer)
173174
{
174175
frame->stacktop= (int)(stack_pointer-frame->localsplus);
175176
}
@@ -309,7 +310,7 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
309310

310311
PyAPI_FUNC(_PyInterpreterFrame*)
311312
_PyEvalFramePushAndInit(PyThreadState*tstate, PyFunctionObject*func,
312-
PyObject*locals, PyObject*const*args,
313+
PyObject*locals, _PyStackRefconst*args,
313314
size_targcount, PyObject*kwnames);
314315

315316
#ifdef__cplusplus

‎Include/internal/pycore_jit.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extern "C" {
1111

1212
#ifdef_Py_JIT
1313

14-
typedef_Py_CODEUNIT*(*jit_func)(_PyInterpreterFrame*frame, PyObject**stack_pointer, PyThreadState*tstate);
14+
typedef_Py_CODEUNIT*(*jit_func)(_PyInterpreterFrame*frame, _PyStackRef*stack_pointer, PyThreadState*tstate);
1515

1616
int_PyJIT_Compile(_PyExecutorObject*executor, const_PyUOpInstruction*trace, size_tlength);
1717
void_PyJIT_Free(_PyExecutorObject*executor);

‎Include/internal/pycore_object.h‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,6 @@ static inline void _Py_ClearImmortal(PyObject *op)
159159
op = NULL; \
160160
} while (0)
161161

162-
// Mark an object as supporting deferred reference counting. This is a no-op
163-
// in the default (with GIL) build. Objects that use deferred reference
164-
// counting should be tracked by the GC so that they are eventually collected.
165-
externvoid_PyObject_SetDeferredRefcount(PyObject*op);
166-
167-
staticinlineint
168-
_PyObject_HasDeferredRefcount(PyObject*op)
169-
{
170-
#ifdefPy_GIL_DISABLED
171-
return_PyObject_HAS_GC_BITS(op, _PyGC_BITS_DEFERRED);
172-
#else
173-
return0;
174-
#endif
175-
}
176-
177162
#if !defined(Py_GIL_DISABLED)
178163
staticinlinevoid
179164
_Py_DECREF_SPECIALIZED(PyObject*op, constdestructordestruct)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#ifndefPy_INTERNAL_OBJECT_DEFERRED_H
2+
#definePy_INTERNAL_OBJECT_DEFERRED_H
3+
4+
#ifdef__cplusplus
5+
extern"C" {
6+
#endif
7+
8+
#include"pycore_gc.h"
9+
10+
#ifndefPy_BUILD_CORE
11+
# error "this header requires Py_BUILD_CORE define"
12+
#endif
13+
14+
// Mark an object as supporting deferred reference counting. This is a no-op
15+
// in the default (with GIL) build. Objects that use deferred reference
16+
// counting should be tracked by the GC so that they are eventually collected.
17+
externvoid_PyObject_SetDeferredRefcount(PyObject*op);
18+
19+
staticinlineint
20+
_PyObject_HasDeferredRefcount(PyObject*op)
21+
{
22+
#ifdefPy_GIL_DISABLED
23+
return_PyObject_HAS_GC_BITS(op, _PyGC_BITS_DEFERRED);
24+
#else
25+
return0;
26+
#endif
27+
}
28+
29+
#ifdef__cplusplus
30+
}
31+
#endif
32+
#endif// !Py_INTERNAL_OBJECT_DEFERRED_H

‎Include/internal/pycore_optimizer.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ extern int _Py_uop_frame_pop(_Py_UOpsContext *ctx);
271271

272272
PyAPI_FUNC(PyObject*) _Py_uop_symbols_test(PyObject*self, PyObject*ignored);
273273

274-
PyAPI_FUNC(int) _PyOptimizer_Optimize(struct_PyInterpreterFrame*frame, _Py_CODEUNIT*start, PyObject**stack_pointer, _PyExecutorObject**exec_ptr);
274+
PyAPI_FUNC(int) _PyOptimizer_Optimize(struct_PyInterpreterFrame*frame, _Py_CODEUNIT*start, _PyStackRef*stack_pointer, _PyExecutorObject**exec_ptr);
275275

276276
#ifdef__cplusplus
277277
}

0 commit comments

Comments
 (0)