Skip to content

Commit 34974d1

Browse files
committed
Port basics of stack spilling
1 parent 4999e0b commit 34974d1

File tree

14 files changed

+474
-368
lines changed

14 files changed

+474
-368
lines changed

Doc/whatsnew/3.13.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -753,9 +753,14 @@ fractions
753753
gc
754754
--
755755

756-
* The cyclic garbage collector is now incremental, which changes the meanings
757-
of the results of :meth:`gc.get_threshold` and :meth:`gc.set_threshold` as
758-
well as :meth:`gc.get_count` and :meth:`gc.get_stats`.
756+
* The cyclic garbage collector is now incremental. Incremental garbage collection
757+
scans only a portion of the heap at each collection, so can only collect a
758+
portion of the potential cycles. This means that pauses are generally a lot shorter,
759+
although memory use might increase as there can be a longer delay before cycles are
760+
collected. All cyclic garbage will be collected eventually.
761+
762+
The meanings of :meth:`gc.get_threshold` and :meth:`gc.set_threshold` as
763+
well as :meth:`gc.get_count` and :meth:`gc.get_stats` have changed:
759764

760765
* :meth:`gc.get_threshold` returns a three-item tuple for backwards compatibility.
761766
The first value is the threshold for young collections, as before; the second

Include/cpython/pystate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ struct _ts {
192192
PyObject *previous_executor;
193193

194194
uint64_t dict_global_version;
195+
int sp_cached; /* Only used in debug builds */
195196
};
196197

197198
#ifdef Py_DEBUG

Include/internal/pycore_frame.h

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ typedef struct _PyInterpreterFrame {
6464
PyObject *f_locals; /* Strong reference, may be NULL. Only valid if not on C stack */
6565
PyFrameObject *frame_obj; /* Strong reference, may be NULL. Only valid if not on C stack */
6666
_Py_CODEUNIT *instr_ptr; /* Instruction currently executing (or about to begin) */
67-
int stacktop; /* Offset of TOS from localsplus */
67+
_PyStackRef *stackpointer;
6868
uint16_t return_offset; /* Only relevant during a function call */
6969
char owner;
7070
/* Locals and stack */
@@ -84,20 +84,20 @@ static inline _PyStackRef *_PyFrame_Stackbase(_PyInterpreterFrame *f) {
8484
}
8585

8686
static inline _PyStackRef _PyFrame_StackPeek(_PyInterpreterFrame *f) {
87-
assert(f->stacktop > _PyFrame_GetCode(f)->co_nlocalsplus);
88-
assert(!PyStackRef_IsNull(f->localsplus[f->stacktop-1]));
89-
return f->localsplus[f->stacktop-1];
87+
assert(f->stackpointer > f->localsplus + _PyFrame_GetCode(f)->co_nlocalsplus);
88+
assert(!PyStackRef_IsNull(f->stackpointer[-1]));
89+
return f->stackpointer[-1];
9090
}
9191

9292
static inline _PyStackRef _PyFrame_StackPop(_PyInterpreterFrame *f) {
93-
assert(f->stacktop > _PyFrame_GetCode(f)->co_nlocalsplus);
94-
f->stacktop--;
95-
return f->localsplus[f->stacktop];
93+
assert(f->stackpointer > f->localsplus + _PyFrame_GetCode(f)->co_nlocalsplus);
94+
f->stackpointer--;
95+
return *f->stackpointer;
9696
}
9797

9898
static inline void _PyFrame_StackPush(_PyInterpreterFrame *f, _PyStackRef value) {
99-
f->localsplus[f->stacktop] = value;
100-
f->stacktop++;
99+
*f->stackpointer = value;
100+
f->stackpointer++;
101101
}
102102

103103
#define FRAME_SPECIALS_SIZE ((int)((sizeof(_PyInterpreterFrame)-1)/sizeof(PyObject *)))
@@ -113,9 +113,12 @@ _PyFrame_NumSlotsForCodeObject(PyCodeObject *code)
113113

114114
static inline void _PyFrame_Copy(_PyInterpreterFrame *src, _PyInterpreterFrame *dest)
115115
{
116-
assert(src->stacktop >= _PyFrame_GetCode(src)->co_nlocalsplus);
117116
*dest = *src;
118-
for (int i = 1; i < src->stacktop; i++) {
117+
assert(src->stackpointer != NULL);
118+
int stacktop = (int)(src->stackpointer - src->localsplus);
119+
assert(stacktop >= _PyFrame_GetCode(src)->co_nlocalsplus);
120+
dest->stackpointer = dest->localsplus + stacktop;
121+
for (int i = 1; i < stacktop; i++) {
119122
dest->localsplus[i] = src->localsplus[i];
120123
}
121124
// Don't leave a dangling pointer to the old frame when creating generators
@@ -137,7 +140,7 @@ _PyFrame_Initialize(
137140
frame->f_builtins = func->func_builtins;
138141
frame->f_globals = func->func_globals;
139142
frame->f_locals = locals;
140-
frame->stacktop = code->co_nlocalsplus;
143+
frame->stackpointer = frame->localsplus + code->co_nlocalsplus;
141144
frame->frame_obj = NULL;
142145
frame->instr_ptr = _PyCode_CODE(code);
143146
frame->return_offset = 0;
@@ -157,22 +160,29 @@ _PyFrame_GetLocalsArray(_PyInterpreterFrame *frame)
157160
return frame->localsplus;
158161
}
159162

160-
/* Fetches the stack pointer, and sets stacktop to -1.
161-
Having stacktop <= 0 ensures that invalid
162-
values are not visible to the cycle GC.
163-
We choose -1 rather than 0 to assist debugging. */
163+
/* Fetches the stack pointer, and sets stackpointer to NULL.
164+
Having stackpointer == NULL ensures that invalid
165+
values are not visible to the cycle GC. */
164166
static inline _PyStackRef*
165167
_PyFrame_GetStackPointer(_PyInterpreterFrame *frame)
166168
{
167-
_PyStackRef *sp = frame->localsplus + frame->stacktop;
168-
frame->stacktop = -1;
169+
#ifdef Py_DEBUG
170+
PyThreadState_GET()->sp_cached++;
171+
#endif
172+
assert(frame->stackpointer != NULL);
173+
_PyStackRef *sp = frame->stackpointer;
174+
frame->stackpointer = NULL;
169175
return sp;
170176
}
171177

172178
static inline void
173179
_PyFrame_SetStackPointer(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer)
174180
{
175-
frame->stacktop = (int)(stack_pointer - frame->localsplus);
181+
#ifdef Py_DEBUG
182+
PyThreadState_GET()->sp_cached--;
183+
#endif
184+
assert(frame->stackpointer == NULL);
185+
frame->stackpointer = stack_pointer;
176186
}
177187

178188
/* Determine whether a frame is incomplete.
@@ -300,7 +310,8 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
300310
frame->f_globals = NULL;
301311
#endif
302312
frame->f_locals = NULL;
303-
frame->stacktop = code->co_nlocalsplus + stackdepth;
313+
assert(stackdepth <= code->co_stacksize);
314+
frame->stackpointer = frame->localsplus + code->co_nlocalsplus + stackdepth;
304315
frame->frame_obj = NULL;
305316
frame->instr_ptr = _PyCode_CODE(code);
306317
frame->owner = FRAME_OWNED_BY_THREAD;

Lib/test/test_sys.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,7 +1603,7 @@ class C(object): pass
16031603
def func():
16041604
return sys._getframe()
16051605
x = func()
1606-
check(x, size('3Pi2cP7P2ic??2P'))
1606+
check(x, size('3Pi2cP9Phc2P'))
16071607
# function
16081608
def func(): pass
16091609
check(func, size('16Pi'))
@@ -1620,7 +1620,7 @@ def bar(cls):
16201620
check(bar, size('PP'))
16211621
# generator
16221622
def get_gen(): yield 1
1623-
check(get_gen(), size('PP4P4c7P2ic??2P'))
1623+
check(get_gen(), size('PP4P4cP9PhcP'))
16241624
# iterator
16251625
check(iter('abc'), size('lP'))
16261626
# callable-iterator

Objects/frameobject.c

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,8 +1620,10 @@ frame_dealloc(PyFrameObject *f)
16201620
Py_CLEAR(frame->f_funcobj);
16211621
Py_CLEAR(frame->f_locals);
16221622
_PyStackRef *locals = _PyFrame_GetLocalsArray(frame);
1623-
for (int i = 0; i < frame->stacktop; i++) {
1624-
PyStackRef_CLEAR(locals[i]);
1623+
_PyStackRef *sp = frame->stackpointer;
1624+
while (sp > locals) {
1625+
sp--;
1626+
PyStackRef_CLEAR(*sp);
16251627
}
16261628
}
16271629
Py_CLEAR(f->f_back);
@@ -1653,11 +1655,13 @@ frame_tp_clear(PyFrameObject *f)
16531655

16541656
/* locals and stack */
16551657
_PyStackRef *locals = _PyFrame_GetLocalsArray(f->f_frame);
1656-
assert(f->f_frame->stacktop >= 0);
1657-
for (int i = 0; i < f->f_frame->stacktop; i++) {
1658-
PyStackRef_CLEAR(locals[i]);
1658+
_PyStackRef *sp = f->f_frame->stackpointer;
1659+
assert(sp >= locals);
1660+
while (sp > locals) {
1661+
sp--;
1662+
PyStackRef_CLEAR(*sp);
16591663
}
1660-
f->f_frame->stacktop = 0;
1664+
f->f_frame->stackpointer = locals;
16611665
Py_CLEAR(f->f_frame->f_locals);
16621666
return 0;
16631667
}
@@ -1875,7 +1879,7 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
18751879
}
18761880

18771881
PyObject *value = PyStackRef_AsPyObjectBorrow(frame->localsplus[i]);
1878-
if (frame->stacktop) {
1882+
if (frame->stackpointer > frame->localsplus) {
18791883
if (kind & CO_FAST_FREE) {
18801884
// The cell was set by COPY_FREE_VARS.
18811885
assert(value != NULL && PyCell_Check(value));

Objects/object.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,6 +2842,9 @@ _Py_Dealloc(PyObject *op)
28422842
destructor dealloc = type->tp_dealloc;
28432843
#ifdef Py_DEBUG
28442844
PyThreadState *tstate = _PyThreadState_GET();
2845+
#ifndef Py_GIL_DISABLED
2846+
assert(tstate->sp_cached == 0);
2847+
#endif
28452848
PyObject *old_exc = tstate != NULL ? tstate->current_exception : NULL;
28462849
// Keep the old exception type alive to prevent undefined behavior
28472850
// on (tstate->curexc_type != old_exc_type) below

Python/bytecodes.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,9 @@ dummy_func(
863863

864864
inst(CALL_INTRINSIC_1, (value -- res)) {
865865
assert(oparg <= MAX_INTRINSIC_1);
866-
PyObject *res_o = _PyIntrinsics_UnaryFunctions[oparg].func(tstate, PyStackRef_AsPyObjectBorrow(value));
866+
ESCAPING_CALL(
867+
PyObject *res_o = _PyIntrinsics_UnaryFunctions[oparg].func(tstate, PyStackRef_AsPyObjectBorrow(value))
868+
);
867869
DECREF_INPUTS();
868870
ERROR_IF(res_o == NULL, error);
869871
res = PyStackRef_FromPyObjectSteal(res_o);
@@ -873,8 +875,9 @@ dummy_func(
873875
assert(oparg <= MAX_INTRINSIC_2);
874876
PyObject *value1 = PyStackRef_AsPyObjectBorrow(value1_st);
875877
PyObject *value2 = PyStackRef_AsPyObjectBorrow(value2_st);
876-
877-
PyObject *res_o = _PyIntrinsics_BinaryFunctions[oparg].func(tstate, value2, value1);
878+
ESCAPING_CALL(
879+
PyObject *res_o = _PyIntrinsics_BinaryFunctions[oparg].func(tstate, value2, value1)
880+
);
878881
DECREF_INPUTS();
879882
ERROR_IF(res_o == NULL, error);
880883
res = PyStackRef_FromPyObjectSteal(res_o);
@@ -2406,7 +2409,9 @@ dummy_func(
24062409
STAT_INC(STORE_ATTR, hit);
24072410
PyObject *old_value = *(PyObject **)addr;
24082411
*(PyObject **)addr = PyStackRef_AsPyObjectSteal(value);
2409-
Py_XDECREF(old_value);
2412+
if (old_value != NULL) {
2413+
Py_DECREF(old_value);
2414+
}
24102415
PyStackRef_CLOSE(owner);
24112416
}
24122417

Python/ceval.c

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,36 +52,50 @@
5252
# error "ceval.c must be build with Py_BUILD_CORE define for best performance"
5353
#endif
5454

55-
#if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_GIL_DISABLED)
56-
// GH-89279: The MSVC compiler does not inline these static inline functions
57-
// in PGO build in _PyEval_EvalFrameDefault(), because this function is over
58-
// the limit of PGO, and that limit cannot be configured.
59-
// Define them as macros to make sure that they are always inlined by the
60-
// preprocessor.
61-
// TODO: implement Py_DECREF macro for Py_GIL_DISABLED
62-
63-
#undef Py_DECREF
64-
#define Py_DECREF(arg) \
55+
#ifdef Py_GIL_DISABLED
56+
57+
#define INTERPRETER_REFCLOSE PyStackRef_CLOSE
58+
59+
#else
60+
61+
#ifdef Py_REF_DEBUG
62+
static inline void interpreter_refclose(_PyInterpreterFrame *frame, _PyStackRef *sp, const char *filename, int lineno, _PyStackRef ref)
63+
{
64+
PyObject *op = PyStackRef_AsPyObjectBorrow(ref);
65+
if (op->ob_refcnt <= 0) {
66+
_Py_NegativeRefcount(filename, lineno, op);
67+
}
68+
if (_Py_IsImmortal(op)) {
69+
return;
70+
}
71+
_Py_DECREF_STAT_INC();
72+
_Py_DECREF_DecRefTotal();
73+
if (--op->ob_refcnt == 0) {
74+
_PyFrame_SetStackPointer(frame, sp);
75+
_Py_Dealloc(op);
76+
_PyFrame_GetStackPointer(frame);
77+
}
78+
}
79+
#define INTERPRETER_REFCLOSE(ref) interpreter_refclose(frame, stack_pointer, __FILE__, __LINE__, ref)
80+
81+
#else
82+
83+
#define INTERPRETER_REFCLOSE(ref) \
6584
do { \
66-
PyObject *op = _PyObject_CAST(arg); \
85+
PyObject *op = PyStackRef_AsPyObjectBorrow(ref); \
6786
if (_Py_IsImmortal(op)) { \
6887
break; \
6988
} \
7089
_Py_DECREF_STAT_INC(); \
7190
if (--op->ob_refcnt == 0) { \
72-
destructor dealloc = Py_TYPE(op)->tp_dealloc; \
73-
(*dealloc)(op); \
91+
_PyFrame_SetStackPointer(frame, stack_pointer); \
92+
_Py_Dealloc(op); \
93+
stack_pointer = _PyFrame_GetStackPointer(frame); \
7494
} \
7595
} while (0)
7696

77-
#undef Py_XDECREF
78-
#define Py_XDECREF(arg) \
79-
do { \
80-
PyObject *xop = _PyObject_CAST(arg); \
81-
if (xop != NULL) { \
82-
Py_DECREF(xop); \
83-
} \
84-
} while (0)
97+
#endif // Py_REF_DEBUG
98+
#endif // Py_GIL_DISABLED
8599

86100
#undef Py_IS_TYPE
87101
#define Py_IS_TYPE(ob, type) \
@@ -100,7 +114,6 @@
100114
d(op); \
101115
} \
102116
} while (0)
103-
#endif
104117

105118

106119
#ifdef LLTRACE
@@ -773,7 +786,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
773786
#endif
774787
entry_frame.f_executable = Py_None;
775788
entry_frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
776-
entry_frame.stacktop = 0;
789+
entry_frame.stackpointer = entry_frame.localsplus;
777790
entry_frame.owner = FRAME_OWNED_BY_CSTACK;
778791
entry_frame.return_offset = 0;
779792
/* Push frame */
@@ -936,7 +949,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
936949
PyTraceBack_Here(f);
937950
}
938951
}
939-
monitor_raise(tstate, frame, next_instr-1);
952+
ESCAPING_CALL(monitor_raise(tstate, frame, next_instr-1));
940953
exception_unwind:
941954
{
942955
/* We can't use frame->instr_ptr here, as RERAISE may have set it */
@@ -949,7 +962,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
949962
/* Pop remaining stack entries. */
950963
_PyStackRef *stackbase = _PyFrame_Stackbase(frame);
951964
while (stack_pointer > stackbase) {
952-
PyStackRef_XCLOSE(POP());
965+
_PyStackRef ref = POP();
966+
if (!PyStackRef_IsNull(ref)) {
967+
INTERPRETER_REFCLOSE(ref);
968+
};
953969
}
954970
assert(STACK_LEVEL() == 0);
955971
_PyFrame_SetStackPointer(frame, stack_pointer);
@@ -960,7 +976,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
960976
assert(STACK_LEVEL() >= level);
961977
_PyStackRef *new_top = _PyFrame_Stackbase(frame) + level;
962978
while (stack_pointer > new_top) {
963-
PyStackRef_XCLOSE(POP());
979+
_PyStackRef ref = POP();
980+
if (!PyStackRef_IsNull(ref)) {
981+
INTERPRETER_REFCLOSE(ref);
982+
};
964983
}
965984
if (lasti) {
966985
int frame_lasti = _PyInterpreterFrame_LASTI(frame);
@@ -979,7 +998,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
979998
PUSH(PyStackRef_FromPyObjectSteal(exc));
980999
next_instr = _PyCode_CODE(_PyFrame_GetCode(frame)) + handler;
9811000

982-
if (monitor_handled(tstate, frame, next_instr, exc) < 0) {
1001+
ESCAPING_CALL(int err = monitor_handled(tstate, frame, next_instr, exc));
1002+
if (err < 0) {
9831003
goto exception_unwind;
9841004
}
9851005
/* Resume normal execution */

0 commit comments

Comments
 (0)