Skip to content

Commit db344b2

Browse files
committed
Fix memory leak in JIT executor on compilation failure
The executor object wasn't decremented if Tier 2 code returned with an exception set but not a fatal error. This change moves the Py_DECREF call inside the TIER1_TO_TIER2 macro. This ensures the executor's reference count is always decremented.
1 parent 963bcf0 commit db344b2

File tree

7 files changed

+28
-5
lines changed

7 files changed

+28
-5
lines changed
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
Fix a memory leak of a JIT executor that occurred when JIT compilation failed.
1+
Fix memory leak in JIT executor on compilation failure
2+
3+
The executor object passed to the TIER1_TO_TIER2 macro was not
4+
decremented if the Tier 2 code returned with an exception set but
5+
without a fatal error (i.e., not returning NULL).
6+
7+
This change moves the Py_DECREF call inside the TIER1_TO_TIER2
8+
macro to ensure the executor's reference count is always decremented
9+
after its execution, regardless of the outcome.

Python/bytecodes.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2972,6 +2972,7 @@ dummy_func(
29722972
assert(tstate->current_executor == NULL);
29732973
assert(executor != tstate->interp->cold_executor);
29742974
tstate->jit_exit = NULL;
2975+
tstate->current_executor = (PyObject *)executor;
29752976
TIER1_TO_TIER2(executor);
29762977
}
29772978
}

Python/ceval.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
11461146
assert(frame->owner == FRAME_OWNED_BY_INTERPRETER);
11471147
/* Restore previous frame and exit */
11481148
tstate->current_frame = frame->previous;
1149+
#ifdef _Py_TIER2
1150+
if (tstate->current_executor != NULL) {
1151+
tstate->current_executor = NULL;
1152+
}
1153+
#endif
11491154
return NULL;
11501155
}
11511156
#ifdef _Py_TIER2

Python/ceval_macros.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,12 @@ _PyFrame_SetStackPointer(frame, stack_pointer)
351351

352352
/* Tier-switching macros. */
353353

354-
#define TIER1_TO_TIER2(EXECUTOR) \
354+
#define TIER1_TO_TIER2(EXECUTOR) \
355355
do { \
356-
OPT_STAT_INC(traces_executed); \
357356
next_instr = _Py_jit_entry((EXECUTOR), frame, stack_pointer, tstate); \
358357
frame = tstate->current_frame; \
359358
stack_pointer = _PyFrame_GetStackPointer(frame); \
359+
Py_DECREF(EXECUTOR); \
360360
if (next_instr == NULL) { \
361361
next_instr = frame->instr_ptr; \
362362
JUMP_TO_LABEL(error); \
@@ -413,4 +413,3 @@ check_periodics(PyThreadState *tstate) {
413413
}
414414
return 0;
415415
}
416-

Python/generated_cases.c.h

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/optimizer.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,7 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil
12021202
if (executor == NULL) {
12031203
return NULL;
12041204
}
1205+
_PyObject_GC_TRACK(executor);
12051206

12061207
/* Initialize exits */
12071208
_PyExecutorObject *cold = _PyExecutor_GetColdExecutor();
@@ -1246,7 +1247,6 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil
12461247
}
12471248
sanity_check(executor);
12481249
#endif
1249-
_PyObject_GC_TRACK(executor);
12501250
#ifdef _Py_JIT
12511251
executor->jit_code = NULL;
12521252
executor->jit_size = 0;
@@ -1507,6 +1507,7 @@ _PyExecutor_GetColdExecutor(void)
15071507
if (cold == NULL) {
15081508
Py_FatalError("Cannot allocate core JIT code");
15091509
}
1510+
_PyObject_GC_TRACK(cold);
15101511
((_PyUOpInstruction *)cold->trace)->opcode = _COLD_EXIT;
15111512
#ifdef _Py_JIT
15121513
cold->jit_code = NULL;

Python/pystate.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,9 @@ PyThreadState_Clear(PyThreadState *tstate)
17171717
Py_CLEAR(tstate->async_gen_finalizer);
17181718

17191719
Py_CLEAR(tstate->context);
1720+
#ifdef _Py_TIER2
1721+
Py_CLEAR(tstate->current_executor);
1722+
#endif
17201723

17211724
#ifdef Py_GIL_DISABLED
17221725
// Each thread should clear own freelists in free-threading builds.

0 commit comments

Comments
 (0)