mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	bpo-45753: Make recursion checks more efficient. (GH-29524)
* Uses recursion remaining, instead of recursion depth to speed up check against recursion limit.
This commit is contained in:
		
							parent
							
								
									9bf2cbc4c4
								
							
						
					
					
						commit
						b931077375
					
				
					 10 changed files with 50 additions and 43 deletions
				
			
		| 
						 | 
					@ -79,9 +79,9 @@ struct _ts {
 | 
				
			||||||
    struct _ts *next;
 | 
					    struct _ts *next;
 | 
				
			||||||
    PyInterpreterState *interp;
 | 
					    PyInterpreterState *interp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int recursion_depth;
 | 
					    int recursion_remaining;
 | 
				
			||||||
 | 
					    int recursion_limit;
 | 
				
			||||||
    int recursion_headroom; /* Allow 50 more calls to handle any errors. */
 | 
					    int recursion_headroom; /* Allow 50 more calls to handle any errors. */
 | 
				
			||||||
    int stackcheck_counter;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* 'tracing' keeps track of the execution depth when tracing/profiling.
 | 
					    /* 'tracing' keeps track of the execution depth when tracing/profiling.
 | 
				
			||||||
       This is to prevent the actual trace/profile code from being recorded in
 | 
					       This is to prevent the actual trace/profile code from being recorded in
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,12 +75,12 @@ extern void _PyEval_DeactivateOpCache(void);
 | 
				
			||||||
/* With USE_STACKCHECK macro defined, trigger stack checks in
 | 
					/* With USE_STACKCHECK macro defined, trigger stack checks in
 | 
				
			||||||
   _Py_CheckRecursiveCall() on every 64th call to Py_EnterRecursiveCall. */
 | 
					   _Py_CheckRecursiveCall() on every 64th call to Py_EnterRecursiveCall. */
 | 
				
			||||||
static inline int _Py_MakeRecCheck(PyThreadState *tstate)  {
 | 
					static inline int _Py_MakeRecCheck(PyThreadState *tstate)  {
 | 
				
			||||||
    return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit
 | 
					    return (tstate->recursion_remaining-- <= 0
 | 
				
			||||||
            || ++tstate->stackcheck_counter > 64);
 | 
					            || (tstate->recursion_remaining & 63) == 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
 | 
					static inline int _Py_MakeRecCheck(PyThreadState *tstate) {
 | 
				
			||||||
    return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit);
 | 
					    return tstate->recursion_remaining-- <= 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,7 +101,7 @@ static inline int _Py_EnterRecursiveCall_inline(const char *where) {
 | 
				
			||||||
#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_inline(where)
 | 
					#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_inline(where)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline void _Py_LeaveRecursiveCall(PyThreadState *tstate)  {
 | 
					static inline void _Py_LeaveRecursiveCall(PyThreadState *tstate)  {
 | 
				
			||||||
    tstate->recursion_depth--;
 | 
					    tstate->recursion_remaining++;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline void _Py_LeaveRecursiveCall_inline(void)  {
 | 
					static inline void _Py_LeaveRecursiveCall_inline(void)  {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					Make recursion checks a bit more efficient by tracking amount of calls left
 | 
				
			||||||
 | 
					before overflow.
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,8 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args))
 | 
				
			||||||
    PyThreadState *tstate = _PyThreadState_GET();
 | 
					    PyThreadState *tstate = _PyThreadState_GET();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* subtract one to ignore the frame of the get_recursion_depth() call */
 | 
					    /* subtract one to ignore the frame of the get_recursion_depth() call */
 | 
				
			||||||
    return PyLong_FromLong(tstate->recursion_depth - 1);
 | 
					
 | 
				
			||||||
 | 
					    return PyLong_FromLong(tstate->recursion_limit - tstate->recursion_remaining - 1);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -933,8 +933,9 @@ _PyAST_Validate(mod_ty mod)
 | 
				
			||||||
        return 0;
 | 
					        return 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /* Be careful here to prevent overflow. */
 | 
					    /* Be careful here to prevent overflow. */
 | 
				
			||||||
    starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
					    int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
 | 
				
			||||||
        tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
 | 
					    starting_recursion_depth = (recursion_depth< INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
				
			||||||
 | 
					        recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
 | 
				
			||||||
    state.recursion_depth = starting_recursion_depth;
 | 
					    state.recursion_depth = starting_recursion_depth;
 | 
				
			||||||
    state.recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
					    state.recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
				
			||||||
        recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
 | 
					        recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1098,8 +1098,9 @@ _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
 | 
				
			||||||
        return 0;
 | 
					        return 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /* Be careful here to prevent overflow. */
 | 
					    /* Be careful here to prevent overflow. */
 | 
				
			||||||
    starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
					    int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
 | 
				
			||||||
        tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
 | 
					    starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
				
			||||||
 | 
					        recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
 | 
				
			||||||
    state->recursion_depth = starting_recursion_depth;
 | 
					    state->recursion_depth = starting_recursion_depth;
 | 
				
			||||||
    state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
					    state->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
				
			||||||
        recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
 | 
					        recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -785,42 +785,49 @@ Py_GetRecursionLimit(void)
 | 
				
			||||||
void
 | 
					void
 | 
				
			||||||
Py_SetRecursionLimit(int new_limit)
 | 
					Py_SetRecursionLimit(int new_limit)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    PyThreadState *tstate = _PyThreadState_GET();
 | 
					    PyInterpreterState *interp = _PyInterpreterState_GET();
 | 
				
			||||||
    tstate->interp->ceval.recursion_limit = new_limit;
 | 
					    interp->ceval.recursion_limit = new_limit;
 | 
				
			||||||
 | 
					    for (PyThreadState *p = interp->tstate_head; p != NULL; p = p->next) {
 | 
				
			||||||
 | 
					        int depth = p->recursion_limit - p->recursion_remaining;
 | 
				
			||||||
 | 
					        p->recursion_limit = new_limit;
 | 
				
			||||||
 | 
					        p->recursion_remaining = new_limit - depth;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* The function _Py_EnterRecursiveCall() only calls _Py_CheckRecursiveCall()
 | 
					/* The function _Py_EnterRecursiveCall() only calls _Py_CheckRecursiveCall()
 | 
				
			||||||
   if the recursion_depth reaches recursion_limit.
 | 
					   if the recursion_depth reaches recursion_limit. */
 | 
				
			||||||
   If USE_STACKCHECK, the macro decrements recursion_limit
 | 
					 | 
				
			||||||
   to guarantee that _Py_CheckRecursiveCall() is regularly called.
 | 
					 | 
				
			||||||
   Without USE_STACKCHECK, there is no need for this. */
 | 
					 | 
				
			||||||
int
 | 
					int
 | 
				
			||||||
_Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
 | 
					_Py_CheckRecursiveCall(PyThreadState *tstate, const char *where)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    int recursion_limit = tstate->interp->ceval.recursion_limit;
 | 
					    /* Check against global limit first. */
 | 
				
			||||||
 | 
					    int depth = tstate->recursion_limit - tstate->recursion_remaining;
 | 
				
			||||||
 | 
					    if (depth < tstate->interp->ceval.recursion_limit) {
 | 
				
			||||||
 | 
					        tstate->recursion_limit = tstate->interp->ceval.recursion_limit;
 | 
				
			||||||
 | 
					        tstate->recursion_remaining = tstate->recursion_limit - depth;
 | 
				
			||||||
 | 
					        assert(tstate->recursion_remaining > 0);
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
#ifdef USE_STACKCHECK
 | 
					#ifdef USE_STACKCHECK
 | 
				
			||||||
    tstate->stackcheck_counter = 0;
 | 
					 | 
				
			||||||
    if (PyOS_CheckStack()) {
 | 
					    if (PyOS_CheckStack()) {
 | 
				
			||||||
        --tstate->recursion_depth;
 | 
					        ++tstate->recursion_remaining;
 | 
				
			||||||
        _PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow");
 | 
					        _PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow");
 | 
				
			||||||
        return -1;
 | 
					        return -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    if (tstate->recursion_headroom) {
 | 
					    if (tstate->recursion_headroom) {
 | 
				
			||||||
        if (tstate->recursion_depth > recursion_limit + 50) {
 | 
					        if (tstate->recursion_remaining < -50) {
 | 
				
			||||||
            /* Overflowing while handling an overflow. Give up. */
 | 
					            /* Overflowing while handling an overflow. Give up. */
 | 
				
			||||||
            Py_FatalError("Cannot recover from stack overflow.");
 | 
					            Py_FatalError("Cannot recover from stack overflow.");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
        if (tstate->recursion_depth > recursion_limit) {
 | 
					        if (tstate->recursion_remaining <= 0) {
 | 
				
			||||||
            tstate->recursion_headroom++;
 | 
					            tstate->recursion_headroom++;
 | 
				
			||||||
            _PyErr_Format(tstate, PyExc_RecursionError,
 | 
					            _PyErr_Format(tstate, PyExc_RecursionError,
 | 
				
			||||||
                        "maximum recursion depth exceeded%s",
 | 
					                        "maximum recursion depth exceeded%s",
 | 
				
			||||||
                        where);
 | 
					                        where);
 | 
				
			||||||
            tstate->recursion_headroom--;
 | 
					            tstate->recursion_headroom--;
 | 
				
			||||||
            --tstate->recursion_depth;
 | 
					            ++tstate->recursion_remaining;
 | 
				
			||||||
            return -1;
 | 
					            return -1;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -1582,7 +1589,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
start_frame:
 | 
					start_frame:
 | 
				
			||||||
    if (_Py_EnterRecursiveCall(tstate, "")) {
 | 
					    if (_Py_EnterRecursiveCall(tstate, "")) {
 | 
				
			||||||
        tstate->recursion_depth++;
 | 
					        tstate->recursion_remaining--;
 | 
				
			||||||
        goto exit_eval_frame;
 | 
					        goto exit_eval_frame;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5688,13 +5695,13 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
 | 
				
			||||||
static int
 | 
					static int
 | 
				
			||||||
_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
 | 
					_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    ++tstate->recursion_depth;
 | 
					    --tstate->recursion_remaining;
 | 
				
			||||||
    assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
 | 
					    assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
 | 
				
			||||||
    if (_PyFrame_Clear(frame, 0)) {
 | 
					    if (_PyFrame_Clear(frame, 0)) {
 | 
				
			||||||
        --tstate->recursion_depth;
 | 
					        ++tstate->recursion_remaining;
 | 
				
			||||||
        return -1;
 | 
					        return -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    --tstate->recursion_depth;
 | 
					    ++tstate->recursion_remaining;
 | 
				
			||||||
    _PyThreadState_PopFrame(tstate, frame);
 | 
					    _PyThreadState_PopFrame(tstate, frame);
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -636,9 +636,9 @@ new_threadstate(PyInterpreterState *interp, int init)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tstate->interp = interp;
 | 
					    tstate->interp = interp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tstate->recursion_depth = 0;
 | 
					    tstate->recursion_limit = interp->ceval.recursion_limit;
 | 
				
			||||||
 | 
					    tstate->recursion_remaining = interp->ceval.recursion_limit;
 | 
				
			||||||
    tstate->recursion_headroom = 0;
 | 
					    tstate->recursion_headroom = 0;
 | 
				
			||||||
    tstate->stackcheck_counter = 0;
 | 
					 | 
				
			||||||
    tstate->tracing = 0;
 | 
					    tstate->tracing = 0;
 | 
				
			||||||
    tstate->root_cframe.use_tracing = 0;
 | 
					    tstate->root_cframe.use_tracing = 0;
 | 
				
			||||||
    tstate->root_cframe.current_frame = NULL;
 | 
					    tstate->root_cframe.current_frame = NULL;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -298,8 +298,9 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
 | 
				
			||||||
        return NULL;
 | 
					        return NULL;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /* Be careful here to prevent overflow. */
 | 
					    /* Be careful here to prevent overflow. */
 | 
				
			||||||
    starting_recursion_depth = (tstate->recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
					    int recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
 | 
				
			||||||
        tstate->recursion_depth * COMPILER_STACK_FRAME_SCALE : tstate->recursion_depth;
 | 
					    starting_recursion_depth = (recursion_depth < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
				
			||||||
 | 
					        recursion_depth * COMPILER_STACK_FRAME_SCALE : recursion_depth;
 | 
				
			||||||
    st->recursion_depth = starting_recursion_depth;
 | 
					    st->recursion_depth = starting_recursion_depth;
 | 
				
			||||||
    st->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
					    st->recursion_limit = (recursion_limit < INT_MAX / COMPILER_STACK_FRAME_SCALE) ?
 | 
				
			||||||
        recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
 | 
					        recursion_limit * COMPILER_STACK_FRAME_SCALE : recursion_limit;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1187,20 +1187,14 @@ sys_setrecursionlimit_impl(PyObject *module, int new_limit)
 | 
				
			||||||
        return NULL;
 | 
					        return NULL;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Issue #25274: When the recursion depth hits the recursion limit in
 | 
					    /* Reject too low new limit if the current recursion depth is higher than
 | 
				
			||||||
       _Py_CheckRecursiveCall(), the overflowed flag of the thread state is
 | 
					       the new low-water mark. */
 | 
				
			||||||
       set to 1 and a RecursionError is raised. The overflowed flag is reset
 | 
					    int depth = tstate->recursion_limit - tstate->recursion_remaining;
 | 
				
			||||||
       to 0 when the recursion depth goes below the low-water mark: see
 | 
					    if (depth >= new_limit) {
 | 
				
			||||||
       Py_LeaveRecursiveCall().
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
       Reject too low new limit if the current recursion depth is higher than
 | 
					 | 
				
			||||||
       the new low-water mark. Otherwise it may not be possible anymore to
 | 
					 | 
				
			||||||
       reset the overflowed flag to 0. */
 | 
					 | 
				
			||||||
    if (tstate->recursion_depth >= new_limit) {
 | 
					 | 
				
			||||||
        _PyErr_Format(tstate, PyExc_RecursionError,
 | 
					        _PyErr_Format(tstate, PyExc_RecursionError,
 | 
				
			||||||
                      "cannot set the recursion limit to %i at "
 | 
					                      "cannot set the recursion limit to %i at "
 | 
				
			||||||
                      "the recursion depth %i: the limit is too low",
 | 
					                      "the recursion depth %i: the limit is too low",
 | 
				
			||||||
                      new_limit, tstate->recursion_depth);
 | 
					                      new_limit, depth);
 | 
				
			||||||
        return NULL;
 | 
					        return NULL;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue