diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-11-16-14-36.gh-issue-139549.9BGTLP.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-11-16-14-36.gh-issue-139549.9BGTLP.rst new file mode 100644 index 00000000000000..70e42165ef4b94 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-11-16-14-36.gh-issue-139549.9BGTLP.rst @@ -0,0 +1,9 @@ +Fix memory leak in JIT executor on compilation failure + +The executor object passed to the TIER1_TO_TIER2 macro was not +decremented if the Tier 2 code returned with an exception set but +without a fatal error (i.e., not returning NULL). + +This change moves the Py_DECREF call inside the TIER1_TO_TIER2 +macro to ensure the executor's reference count is always decremented +after its execution, regardless of the outcome. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index f9f14322df0a5e..e5fea02448b767 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2972,6 +2972,7 @@ dummy_func( assert(tstate->current_executor == NULL); assert(executor != tstate->interp->cold_executor); tstate->jit_exit = NULL; + tstate->current_executor = (PyObject *)executor; TIER1_TO_TIER2(executor); } } diff --git a/Python/ceval.c b/Python/ceval.c index 1b52128c858ecb..ab77ebace4d901 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1146,6 +1146,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int assert(frame->owner == FRAME_OWNED_BY_INTERPRETER); /* Restore previous frame and exit */ tstate->current_frame = frame->previous; +#ifdef _Py_TIER2 + if (tstate->current_executor != NULL) { + tstate->current_executor = NULL; + } +#endif return NULL; } #ifdef _Py_TIER2 diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 4ed03b7fb01bdf..1d7b704c8ce5d2 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -351,12 +351,12 @@ _PyFrame_SetStackPointer(frame, stack_pointer) /* Tier-switching macros. */ -#define TIER1_TO_TIER2(EXECUTOR) \ +#define TIER1_TO_TIER2(EXECUTOR) \ do { \ - OPT_STAT_INC(traces_executed); \ next_instr = _Py_jit_entry((EXECUTOR), frame, stack_pointer, tstate); \ frame = tstate->current_frame; \ stack_pointer = _PyFrame_GetStackPointer(frame); \ + Py_DECREF(EXECUTOR); \ if (next_instr == NULL) { \ next_instr = frame->instr_ptr; \ JUMP_TO_LABEL(error); \ @@ -413,4 +413,3 @@ check_periodics(PyThreadState *tstate) { } return 0; } - diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 79328a7b725613..6ca7aae1caa618 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -7668,7 +7668,13 @@ assert(tstate->current_executor == NULL); assert(executor != tstate->interp->cold_executor); tstate->jit_exit = NULL; + tstate->current_executor = (PyObject *)executor; + _PyFrame_SetStackPointer(frame, stack_pointer); + stack_pointer = _PyFrame_GetStackPointer(frame); TIER1_TO_TIER2(executor); + _PyFrame_SetStackPointer(frame, stack_pointer); + Py_DECREF(executor); + stack_pointer = _PyFrame_GetStackPointer(frame); } } else { diff --git a/Python/optimizer.c b/Python/optimizer.c index 7b76cddeabff44..97cde1c1fdf8b3 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1202,6 +1202,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil if (executor == NULL) { return NULL; } + _PyObject_GC_TRACK(executor); /* Initialize exits */ _PyExecutorObject *cold = _PyExecutor_GetColdExecutor(); @@ -1257,7 +1258,6 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil return NULL; } #endif - _PyObject_GC_TRACK(executor); return executor; } @@ -1507,6 +1507,7 @@ _PyExecutor_GetColdExecutor(void) if (cold == NULL) { Py_FatalError("Cannot allocate core JIT code"); } + _PyObject_GC_TRACK(cold); ((_PyUOpInstruction *)cold->trace)->opcode = _COLD_EXIT; #ifdef _Py_JIT cold->jit_code = NULL; diff --git a/Python/pystate.c b/Python/pystate.c index dbed609f29aa07..5076f11575c61c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1717,6 +1717,9 @@ PyThreadState_Clear(PyThreadState *tstate) Py_CLEAR(tstate->async_gen_finalizer); Py_CLEAR(tstate->context); +#ifdef _Py_TIER2 + Py_CLEAR(tstate->current_executor); +#endif #ifdef Py_GIL_DISABLED // Each thread should clear own freelists in free-threading builds.