Skip to content

Commit ac7d5ba

Browse files
authored
GH-133231: Changes to executor management to support proposed sys._jit module (GH-133287)
* Track the current executor, not the previous one, on the thread-state. * Batch executors for deallocation to avoid having to constantly incref executors; this is an ad-hoc form of deferred reference counting.
1 parent 1d9406e commit ac7d5ba

13 files changed

+176
-54
lines changed

Include/cpython/pystate.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ struct _ts {
194194
/* The thread's exception stack entry. (Always the last entry.) */
195195
_PyErr_StackItem exc_state;
196196

197-
PyObject *previous_executor;
197+
PyObject *current_executor;
198198

199199
uint64_t dict_global_version;
200200

Include/internal/pycore_interp_structs.h

+2
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,8 @@ struct _is {
923923
PyObject *common_consts[NUM_COMMON_CONSTANTS];
924924
bool jit;
925925
struct _PyExecutorObject *executor_list_head;
926+
struct _PyExecutorObject *executor_deletion_list_head;
927+
int executor_deletion_list_remaining_capacity;
926928
size_t trace_run_counter;
927929
_rare_events rare_events;
928930
PyDict_WatchCallback builtins_dict_watcher;

Include/internal/pycore_opcode_metadata.h

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_optimizer.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ typedef struct {
6969
typedef struct {
7070
uint32_t target;
7171
_Py_BackoffCounter temperature;
72-
const struct _PyExecutorObject *executor;
72+
struct _PyExecutorObject *executor;
7373
} _PyExitData;
7474

7575
typedef struct _PyExecutorObject {
@@ -84,6 +84,10 @@ typedef struct _PyExecutorObject {
8484
_PyExitData exits[1];
8585
} _PyExecutorObject;
8686

87+
/* If pending deletion list gets large enough, then scan,
88+
* and free any executors that aren't executing
89+
* i.e. any that aren't a thread's current_executor. */
90+
#define EXECUTOR_DELETE_LIST_MAX 100
8791

8892
// Export for '_opcode' shared extension (JIT compiler).
8993
PyAPI_FUNC(_PyExecutorObject*) _Py_GetExecutor(PyCodeObject *code, int offset);
@@ -304,6 +308,9 @@ static inline int is_terminator(const _PyUOpInstruction *uop)
304308
}
305309

306310
PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
311+
#ifdef _Py_TIER2
312+
extern void _Py_ClearExecutorDeletionList(PyInterpreterState *interp);
313+
#endif
307314

308315
#ifdef __cplusplus
309316
}

Include/internal/pycore_uop_metadata.h

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/bytecodes.c

+24-10
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,17 @@ dummy_func(
11691169
tstate->current_frame = frame->previous;
11701170
assert(!_PyErr_Occurred(tstate));
11711171
PyObject *result = PyStackRef_AsPyObjectSteal(retval);
1172+
#if !Py_TAIL_CALL_INTERP
1173+
assert(frame == &entry.frame);
1174+
#endif
1175+
#ifdef _Py_TIER2
1176+
_PyStackRef executor = frame->localsplus[0];
1177+
assert(tstate->current_executor == NULL);
1178+
if (!PyStackRef_IsNull(executor)) {
1179+
tstate->current_executor = PyStackRef_AsPyObjectBorrow(executor);
1180+
PyStackRef_CLOSE(executor);
1181+
}
1182+
#endif
11721183
LLTRACE_RESUME_FRAME();
11731184
return result;
11741185
}
@@ -2912,8 +2923,7 @@ dummy_func(
29122923
}
29132924
else {
29142925
this_instr[1].counter = initial_jump_backoff_counter();
2915-
assert(tstate->previous_executor == NULL);
2916-
tstate->previous_executor = Py_None;
2926+
assert(tstate->current_executor == NULL);
29172927
GOTO_TIER_TWO(executor);
29182928
}
29192929
}
@@ -2965,7 +2975,7 @@ dummy_func(
29652975
assert(executor->vm_data.index == INSTR_OFFSET() - 1);
29662976
assert(executor->vm_data.code == code);
29672977
assert(executor->vm_data.valid);
2968-
assert(tstate->previous_executor == NULL);
2978+
assert(tstate->current_executor == NULL);
29692979
/* If the eval breaker is set then stay in tier 1.
29702980
* This avoids any potentially infinite loops
29712981
* involving _RESUME_CHECK */
@@ -2978,8 +2988,6 @@ dummy_func(
29782988
}
29792989
DISPATCH_GOTO();
29802990
}
2981-
tstate->previous_executor = Py_None;
2982-
Py_INCREF(executor);
29832991
GOTO_TIER_TWO(executor);
29842992
#else
29852993
Py_FatalError("ENTER_EXECUTOR is not supported in this build");
@@ -5254,7 +5262,6 @@ dummy_func(
52545262
exit->temperature = initial_temperature_backoff_counter();
52555263
Py_CLEAR(exit->executor);
52565264
}
5257-
tstate->previous_executor = (PyObject *)current_executor;
52585265
if (exit->executor == NULL) {
52595266
_Py_BackoffCounter temperature = exit->temperature;
52605267
if (!backoff_counter_triggers(temperature)) {
@@ -5277,7 +5284,6 @@ dummy_func(
52775284
}
52785285
exit->executor = executor;
52795286
}
5280-
Py_INCREF(exit->executor);
52815287
GOTO_TIER_TWO(exit->executor);
52825288
}
52835289

@@ -5316,7 +5322,6 @@ dummy_func(
53165322
}
53175323

53185324
tier2 op(_START_EXECUTOR, (executor/4 --)) {
5319-
Py_CLEAR(tstate->previous_executor);
53205325
#ifndef _Py_JIT
53215326
current_executor = (_PyExecutorObject*)executor;
53225327
#endif
@@ -5337,12 +5342,10 @@ dummy_func(
53375342
}
53385343

53395344
tier2 op(_DEOPT, (--)) {
5340-
tstate->previous_executor = (PyObject *)current_executor;
53415345
GOTO_TIER_ONE(_PyFrame_GetBytecode(frame) + CURRENT_TARGET());
53425346
}
53435347

53445348
tier2 op(_ERROR_POP_N, (target/2 --)) {
5345-
tstate->previous_executor = (PyObject *)current_executor;
53465349
assert(oparg == 0);
53475350
frame->instr_ptr = _PyFrame_GetBytecode(frame) + target;
53485351
SYNC_SP();
@@ -5463,6 +5466,17 @@ dummy_func(
54635466
if (frame->owner == FRAME_OWNED_BY_INTERPRETER) {
54645467
/* Restore previous frame and exit */
54655468
tstate->current_frame = frame->previous;
5469+
#if !Py_TAIL_CALL_INTERP
5470+
assert(frame == &entry.frame);
5471+
#endif
5472+
#ifdef _Py_TIER2
5473+
_PyStackRef executor = frame->localsplus[0];
5474+
assert(tstate->current_executor == NULL);
5475+
if (!PyStackRef_IsNull(executor)) {
5476+
tstate->current_executor = PyStackRef_AsPyObjectBorrow(executor);
5477+
PyStackRef_CLOSE(executor);
5478+
}
5479+
#endif
54665480
return NULL;
54675481
}
54685482
next_instr = frame->instr_ptr;

Python/ceval.c

+31-19
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,11 @@ _PyObjectArray_Free(PyObject **array, PyObject **scratch)
990990
#define DONT_SLP_VECTORIZE
991991
#endif
992992

993+
typedef struct {
994+
_PyInterpreterFrame frame;
995+
_PyStackRef stack[1];
996+
} _PyEntryFrame;
997+
993998
PyObject* _Py_HOT_FUNCTION DONT_SLP_VECTORIZE
994999
_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
9951000
{
@@ -1009,7 +1014,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
10091014
int oparg; /* Current opcode argument, if any */
10101015
assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL);
10111016
#endif
1012-
_PyInterpreterFrame entry_frame;
1017+
_PyEntryFrame entry;
10131018

10141019
if (_Py_EnterRecursiveCallTstate(tstate, "")) {
10151020
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
@@ -1021,30 +1026,37 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
10211026
* These are cached values from the frame and code object. */
10221027
_Py_CODEUNIT *next_instr;
10231028
_PyStackRef *stack_pointer;
1024-
entry_frame.localsplus[0] = PyStackRef_NULL;
1029+
entry.stack[0] = PyStackRef_NULL;
10251030
#ifdef Py_STACKREF_DEBUG
1026-
entry_frame.f_funcobj = PyStackRef_None;
1031+
entry.frame.f_funcobj = PyStackRef_None;
10271032
#elif defined(Py_DEBUG)
10281033
/* Set these to invalid but identifiable values for debugging. */
1029-
entry_frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0};
1030-
entry_frame.f_locals = (PyObject*)0xaaa1;
1031-
entry_frame.frame_obj = (PyFrameObject*)0xaaa2;
1032-
entry_frame.f_globals = (PyObject*)0xaaa3;
1033-
entry_frame.f_builtins = (PyObject*)0xaaa4;
1034+
entry.frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0};
1035+
entry.frame.f_locals = (PyObject*)0xaaa1;
1036+
entry.frame.frame_obj = (PyFrameObject*)0xaaa2;
1037+
entry.frame.f_globals = (PyObject*)0xaaa3;
1038+
entry.frame.f_builtins = (PyObject*)0xaaa4;
10341039
#endif
1035-
entry_frame.f_executable = PyStackRef_None;
1036-
entry_frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
1037-
entry_frame.stackpointer = entry_frame.localsplus;
1038-
entry_frame.owner = FRAME_OWNED_BY_INTERPRETER;
1039-
entry_frame.visited = 0;
1040-
entry_frame.return_offset = 0;
1040+
entry.frame.f_executable = PyStackRef_None;
1041+
entry.frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
1042+
entry.frame.stackpointer = entry.stack;
1043+
entry.frame.owner = FRAME_OWNED_BY_INTERPRETER;
1044+
entry.frame.visited = 0;
1045+
entry.frame.return_offset = 0;
10411046
#ifdef Py_DEBUG
1042-
entry_frame.lltrace = 0;
1047+
entry.frame.lltrace = 0;
10431048
#endif
10441049
/* Push frame */
1045-
entry_frame.previous = tstate->current_frame;
1046-
frame->previous = &entry_frame;
1050+
entry.frame.previous = tstate->current_frame;
1051+
frame->previous = &entry.frame;
10471052
tstate->current_frame = frame;
1053+
entry.frame.localsplus[0] = PyStackRef_NULL;
1054+
#ifdef _Py_TIER2
1055+
if (tstate->current_executor != NULL) {
1056+
entry.frame.localsplus[0] = PyStackRef_FromPyObjectNew(tstate->current_executor);
1057+
tstate->current_executor = NULL;
1058+
}
1059+
#endif
10481060

10491061
/* support for generator.throw() */
10501062
if (throwflag) {
@@ -1071,9 +1083,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
10711083
stack_pointer = _PyFrame_GetStackPointer(frame);
10721084
#if Py_TAIL_CALL_INTERP
10731085
# if Py_STATS
1074-
return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0, lastopcode);
1086+
return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0, lastopcode);
10751087
# else
1076-
return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0);
1088+
return _TAIL_CALL_error(frame, stack_pointer, tstate, next_instr, 0);
10771089
# endif
10781090
#else
10791091
goto error;

Python/ceval_macros.h

+6-3
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,12 @@ _PyFrame_SetStackPointer(frame, stack_pointer)
359359
do { \
360360
OPT_STAT_INC(traces_executed); \
361361
_PyExecutorObject *_executor = (EXECUTOR); \
362+
tstate->current_executor = (PyObject *)_executor; \
362363
jit_func jitted = _executor->jit_code; \
363364
/* Keep the shim frame alive via the executor: */ \
364365
Py_INCREF(_executor); \
365366
next_instr = jitted(frame, stack_pointer, tstate); \
366367
Py_DECREF(_executor); \
367-
Py_CLEAR(tstate->previous_executor); \
368368
frame = tstate->current_frame; \
369369
stack_pointer = _PyFrame_GetStackPointer(frame); \
370370
if (next_instr == NULL) { \
@@ -377,7 +377,9 @@ do { \
377377
#define GOTO_TIER_TWO(EXECUTOR) \
378378
do { \
379379
OPT_STAT_INC(traces_executed); \
380-
next_uop = (EXECUTOR)->trace; \
380+
_PyExecutorObject *_executor = (EXECUTOR); \
381+
tstate->current_executor = (PyObject *)_executor; \
382+
next_uop = _executor->trace; \
381383
assert(next_uop->opcode == _START_EXECUTOR); \
382384
goto enter_tier_two; \
383385
} while (0)
@@ -386,10 +388,11 @@ do { \
386388
#define GOTO_TIER_ONE(TARGET) \
387389
do \
388390
{ \
391+
tstate->current_executor = NULL; \
389392
next_instr = (TARGET); \
393+
assert(tstate->current_executor == NULL); \
390394
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist); \
391395
_PyFrame_SetStackPointer(frame, stack_pointer); \
392-
Py_CLEAR(tstate->previous_executor); \
393396
stack_pointer = _PyFrame_GetStackPointer(frame); \
394397
if (next_instr == NULL) \
395398
{ \

Python/executor_cases.c.h

-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)