Skip to content

Commit b931077

Browse files
authored
bpo-45753: Make recursion checks more efficient. (GH-29524)
* Uses recursion remaining, instead of recursion depth to speed up check against recursion limit.
1 parent 9bf2cbc commit b931077

File tree

10 files changed

+50
-43
lines changed

10 files changed

+50
-43
lines changed

Include/cpython/pystate.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ struct _ts {
7979
struct _ts *next;
8080
PyInterpreterState *interp;
8181

82-
int recursion_depth;
82+
int recursion_remaining;
83+
int recursion_limit;
8384
int recursion_headroom; /* Allow 50 more calls to handle any errors. */
84-
int stackcheck_counter;
8585

8686
/* 'tracing' keeps track of the execution depth when tracing/profiling.
8787
This is to prevent the actual trace/profile code from being recorded in

Include/internal/pycore_ceval.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ extern void _PyEval_DeactivateOpCache(void);
7575
/* With USE_STACKCHECK macro defined, trigger stack checks in
7676
_Py_CheckRecursiveCall() on every 64th call to Py_EnterRecursiveCall. */
7777
static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
78-
return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit
79-
|| ++tstate->stackcheck_counter > 64);
78+
return (tstate->recursion_remaining-- <= 0
79+
|| (tstate->recursion_remaining & 63) == 0);
8080
}
8181
#else
8282
static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
83-
return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit);
83+
return tstate->recursion_remaining-- <= 0;
8484
}
8585
#endif
8686

@@ -101,7 +101,7 @@ static inline int _Py_EnterRecursiveCall_inline(const char *where) {
101101
#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_inline(where)
102102

103103
static inline void _Py_LeaveRecursiveCall(PyThreadState *tstate) {
104-
tstate->recursion_depth--;
104+
tstate->recursion_remaining++;
105105
}
106106

107107
static inline void _Py_LeaveRecursiveCall_inline(void) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make recursion checks a bit more efficient by tracking amount of calls left
2+
before overflow.

Modules/_testinternalcapi.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args))
3737
PyThreadState *tstate = _PyThreadState_GET();
3838

3939
/* subtract one to ignore the frame of the get_recursion_depth() call */
40-
return PyLong_FromLong(tstate->recursion_depth - 1);
40+
41+
return PyLong_FromLong(tstate->recursion_limit - tstate->recursion_remaining - 1);
4142
}
4243

4344

Python/ast.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -933,8 +933,9 @@ _PyAST_Validate(mod_ty mod)
933933
return 0;
934934
}
935935
/* Be careful here to prevent overflow. */
936-
starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
937-
tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
936+
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
937+
starting_recursion_depth = (recursion_depth< INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
938+
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
938939
state.recursion_depth = starting_recursion_depth;
939940
state.recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
940941
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;

Python/ast_opt.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -1098,8 +1098,9 @@ _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
10981098
return 0;
10991099
}
11001100
/* Be careful here to prevent overflow. */
1101-
starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
1102-
tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
1101+
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
1102+
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
1103+
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
11031104
state->recursion_depth = starting_recursion_depth;
11041105
state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
11051106
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;

Python/ceval.c

+24-17
Original file line numberDiff line numberDiff line change
@@ -785,42 +785,49 @@ Py_GetRecursionLimit(void)
785785
void
786786
Py_SetRecursionLimit(int new_limit)
787787
{
788-
PyThreadState *tstate = _PyThreadState_GET();
789-
tstate->interp->ceval.recursion_limit = new_limit;
788+
PyInterpreterState *interp = _PyInterpreterState_GET();
789+
interp->ceval.recursion_limit = new_limit;
790+
for (PyThreadState *p = interp->tstate_head; p != NULL; p = p->next) {
791+
int depth = p->recursion_limit - p->recursion_remaining;
792+
p->recursion_limit = new_limit;
793+
p->recursion_remaining = new_limit - depth;
794+
}
790795
}
791796

792797
/* The function _Py_EnterRecursiveCall() only calls _Py_CheckRecursiveCall()
793-
if the recursion_depth reaches recursion_limit.
794-
If USE_STACKCHECK, the macro decrements recursion_limit
795-
to guarantee that _Py_CheckRecursiveCall() is regularly called.
796-
Without USE_STACKCHECK, there is no need for this. */
798+
if the recursion_depth reaches recursion_limit. */
797799
int
798800
_Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
799801
{
800-
int recursion_limit = tstate->interp->ceval.recursion_limit;
801-
802+
/* Check against global limit first. */
803+
int depth = tstate->recursion_limit - tstate->recursion_remaining;
804+
if (depth < tstate->interp->ceval.recursion_limit) {
805+
tstate->recursion_limit = tstate->interp->ceval.recursion_limit;
806+
tstate->recursion_remaining = tstate->recursion_limit - depth;
807+
assert(tstate->recursion_remaining > 0);
808+
return 0;
809+
}
802810
#ifdef USE_STACKCHECK
803-
tstate->stackcheck_counter = 0;
804811
if (PyOS_CheckStack()) {
805-
--tstate->recursion_depth;
812+
++tstate->recursion_remaining;
806813
_PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow");
807814
return -1;
808815
}
809816
#endif
810817
if (tstate->recursion_headroom) {
811-
if (tstate->recursion_depth > recursion_limit + 50) {
818+
if (tstate->recursion_remaining < -50) {
812819
/* Overflowing while handling an overflow. Give up. */
813820
Py_FatalError("Cannot recover from stack overflow.");
814821
}
815822
}
816823
else {
817-
if (tstate->recursion_depth > recursion_limit) {
824+
if (tstate->recursion_remaining <= 0) {
818825
tstate->recursion_headroom++;
819826
_PyErr_Format(tstate, PyExc_RecursionError,
820827
"maximum recursion depth exceeded%s",
821828
where);
822829
tstate->recursion_headroom--;
823-
--tstate->recursion_depth;
830+
++tstate->recursion_remaining;
824831
return -1;
825832
}
826833
}
@@ -1582,7 +1589,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
15821589

15831590
start_frame:
15841591
if (_Py_EnterRecursiveCall(tstate, "")) {
1585-
tstate->recursion_depth++;
1592+
tstate->recursion_remaining--;
15861593
goto exit_eval_frame;
15871594
}
15881595

@@ -5688,13 +5695,13 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
56885695
static int
56895696
_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
56905697
{
5691-
++tstate->recursion_depth;
5698+
--tstate->recursion_remaining;
56925699
assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
56935700
if (_PyFrame_Clear(frame, 0)) {
5694-
--tstate->recursion_depth;
5701+
++tstate->recursion_remaining;
56955702
return -1;
56965703
}
5697-
--tstate->recursion_depth;
5704+
++tstate->recursion_remaining;
56985705
_PyThreadState_PopFrame(tstate, frame);
56995706
return 0;
57005707
}

Python/pystate.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,9 @@ new_threadstate(PyInterpreterState *interp, int init)
636636

637637
tstate->interp = interp;
638638

639-
tstate->recursion_depth = 0;
639+
tstate->recursion_limit = interp->ceval.recursion_limit;
640+
tstate->recursion_remaining = interp->ceval.recursion_limit;
640641
tstate->recursion_headroom = 0;
641-
tstate->stackcheck_counter = 0;
642642
tstate->tracing = 0;
643643
tstate->root_cframe.use_tracing = 0;
644644
tstate->root_cframe.current_frame = NULL;

Python/symtable.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,9 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
298298
return NULL;
299299
}
300300
/* Be careful here to prevent overflow. */
301-
starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
302-
tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
301+
int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
302+
starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
303+
recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
303304
st->recursion_depth = starting_recursion_depth;
304305
st->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
305306
recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;

Python/sysmodule.c

+5-11
Original file line numberDiff line numberDiff line change
@@ -1187,20 +1187,14 @@ sys_setrecursionlimit_impl(PyObject *module, int new_limit)
11871187
return NULL;
11881188
}
11891189

1190-
/* Issue #25274: When the recursion depth hits the recursion limit in
1191-
_Py_CheckRecursiveCall(), the overflowed flag of the thread state is
1192-
set to 1 and a RecursionError is raised. The overflowed flag is reset
1193-
to 0 when the recursion depth goes below the low-water mark: see
1194-
Py_LeaveRecursiveCall().
1195-
1196-
Reject too low new limit if the current recursion depth is higher than
1197-
the new low-water mark. Otherwise it may not be possible anymore to
1198-
reset the overflowed flag to 0. */
1199-
if (tstate->recursion_depth >= new_limit) {
1190+
/* Reject too low new limit if the current recursion depth is higher than
1191+
the new low-water mark. */
1192+
int depth = tstate->recursion_limit - tstate->recursion_remaining;
1193+
if (depth >= new_limit) {
12001194
_PyErr_Format(tstate, PyExc_RecursionError,
12011195
"cannot set the recursion limit to %i at "
12021196
"the recursion depth %i: the limit is too low",
1203-
new_limit, tstate->recursion_depth);
1197+
new_limit, depth);
12041198
return NULL;
12051199
}
12061200

0 commit comments

Comments
 (0)