PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
#ifdef _Py_TIER2
-extern void _Py_ClearExecutorDeletionList(PyInterpreterState *interp);
+PyAPI_FUNC(void) _Py_ClearExecutorDeletionList(PyInterpreterState *interp);
#endif
int _PyJit_translate_single_bytecode_to_trace(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, int stop_tracing_opcode);
_PyJit_TryInitializeTracing(PyThreadState *tstate, _PyInterpreterFrame *frame,
_Py_CODEUNIT *curr_instr, _Py_CODEUNIT *start_instr,
_Py_CODEUNIT *close_loop_instr, int curr_stackdepth, int chain_depth, _PyExitData *exit,
- int oparg);
+ int oparg, _PyExecutorObject *current_executor);
void _PyJit_FinalizeTracing(PyThreadState *tstate);
void _PyJit_TracerFree(_PyThreadStateImpl *_tstate);
struct _PyExitData *exit;
PyCodeObject *code; // Strong
PyFunctionObject *func; // Strong
+ struct _PyExecutorObject *executor; // Strong
_Py_CODEUNIT *start_instr;
_Py_CODEUNIT *close_loop_instr;
_Py_CODEUNIT *jump_backward_instr;
exe = get_first_executor(f)
self.assertIsNone(exe)
+ def test_prev_executor_freed_while_tracing(self):
+ def f(start, end, way):
+ for x in range(start, end):
+ # For the first trace, create a bad branch on purpose to trace into.
+ # A side exit will form from here on the second trace.
+ y = way + way
+ if x >= TIER2_THRESHOLD:
+ # Invalidate the first trace while tracing the second.
+ _testinternalcapi.invalidate_executors(f.__code__)
+ _testinternalcapi.clear_executor_deletion_list()
+ f(0, TIER2_THRESHOLD, 1)
+ f(1, TIER2_THRESHOLD + 1, 1.0)
+
@requires_specialization
@unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds")
--- /dev/null
+Fix a reference counting issue in the JIT tracer where the current executor
+could be prematurely freed during tracing.
Py_RETURN_NONE;
}
+static PyObject *
+clear_executor_deletion_list(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyInterpreterState *interp = PyInterpreterState_Get();
+ _Py_ClearExecutorDeletionList(interp);
+ Py_RETURN_NONE;
+}
+
static PyObject *
get_exit_executor(PyObject *self, PyObject *arg)
{
#ifdef _Py_TIER2
{"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL},
{"invalidate_executors", invalidate_executors, METH_O, NULL},
+ {"clear_executor_deletion_list", clear_executor_deletion_list, METH_NOARGS, NULL},
{"get_exit_executor", get_exit_executor, METH_O, NULL},
#endif
{"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc),
oparg >>= 8;
insert_exec_at--;
}
- int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, next_instr, STACK_LEVEL(), 0, NULL, oparg);
+ int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, next_instr, STACK_LEVEL(), 0, NULL, oparg, NULL);
if (succ) {
ENTER_TRACING();
}
// Note: it's safe to use target->op.arg here instead of the oparg given by EXTENDED_ARG.
// The invariant in the optimizer is the deopt target always points back to the first EXTENDED_ARG.
// So setting it to anything else is wrong.
- int succ = _PyJit_TryInitializeTracing(tstate, frame, target, target, target, STACK_LEVEL(), chain_depth, exit, target->op.arg);
+ int succ = _PyJit_TryInitializeTracing(tstate, frame, target, target, target, STACK_LEVEL(), chain_depth, exit, target->op.arg, previous_executor);
exit->temperature = restart_backoff_counter(exit->temperature);
if (succ) {
GOTO_TIER_ONE_CONTINUE_TRACING(target);
tracer->initial_state.jump_backward_instr[1].counter = initial_jump_backoff_counter(&_tstate->policy);
}
}
- else {
+ else if (tracer->initial_state.executor->vm_data.valid) {
// Likewise, we hold a strong reference to the executor containing this exit, so the exit is guaranteed
// to be valid to access.
if (err <= 0) {
_PyExecutorObject *previous_executor = _PyExecutor_FromExit(exit);
assert(tstate->current_executor == (PyObject *)previous_executor);
int chain_depth = previous_executor->vm_data.chain_depth + !exit->is_control_flow;
- int succ = _PyJit_TryInitializeTracing(tstate, frame, target, target, target, STACK_LEVEL(), chain_depth, exit, target->op.arg);
+ int succ = _PyJit_TryInitializeTracing(tstate, frame, target, target, target, STACK_LEVEL(), chain_depth, exit, target->op.arg, previous_executor);
exit->temperature = restart_backoff_counter(exit->temperature);
if (succ) {
GOTO_TIER_ONE_CONTINUE_TRACING(target);
oparg >>= 8;
insert_exec_at--;
}
- int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, next_instr, STACK_LEVEL(), 0, NULL, oparg);
+ int succ = _PyJit_TryInitializeTracing(tstate, frame, this_instr, insert_exec_at, next_instr, STACK_LEVEL(), 0, NULL, oparg, NULL);
if (succ) {
ENTER_TRACING();
}
// return immediately without optimization.
return 0;
}
+ _PyExecutorObject *prev_executor = _tstate->jit_tracer_state->initial_state.executor;
+ if (prev_executor != NULL && !prev_executor->vm_data.valid) {
+ // gh-143604: If we are a side exit executor and the original executor is no
+ // longer valid, don't compile to prevent a reference leak.
+ return 0;
+ }
assert(!interp->compiling);
assert(_tstate->jit_tracer_state->initial_state.stack_depth >= 0);
#ifndef Py_GIL_DISABLED
_PyJit_TryInitializeTracing(
PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *curr_instr,
_Py_CODEUNIT *start_instr, _Py_CODEUNIT *close_loop_instr, int curr_stackdepth, int chain_depth,
- _PyExitData *exit, int oparg)
+ _PyExitData *exit, int oparg, _PyExecutorObject *current_executor)
{
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate;
if (_tstate->jit_tracer_state == NULL) {
tracer->initial_state.close_loop_instr = close_loop_instr;
tracer->initial_state.code = (PyCodeObject *)Py_NewRef(code);
tracer->initial_state.func = (PyFunctionObject *)Py_NewRef(func);
+ tracer->initial_state.executor = (_PyExecutorObject *)Py_XNewRef(current_executor);
tracer->initial_state.exit = exit;
tracer->initial_state.stack_depth = curr_stackdepth;
tracer->initial_state.chain_depth = chain_depth;
_PyJitTracerState *tracer = _tstate->jit_tracer_state;
Py_CLEAR(tracer->initial_state.code);
Py_CLEAR(tracer->initial_state.func);
+ Py_CLEAR(tracer->initial_state.executor);
Py_CLEAR(tracer->prev_state.instr_code);
tracer->prev_state.code_curr_size = CODE_SIZE_EMPTY;
tracer->prev_state.code_max_size = UOP_MAX_TRACE_LENGTH/2 - 1;