Skip to content

Commit e4058d7

Browse files
authored
GH-142513: Reimplement executor management (GH-142931)
* Invalidating an executor does not cause arbitrary code to run * Executors are only freed at safe points
1 parent 14f0b51 commit e4058d7

File tree

5 files changed

+90
-106
lines changed

5 files changed

+90
-106
lines changed

Include/internal/pycore_interp_structs.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,6 @@ struct _is {
947947
struct _PyExecutorObject *executor_deletion_list_head;
948948
struct _PyExecutorObject *cold_executor;
949949
struct _PyExecutorObject *cold_dynamic_executor;
950-
int executor_deletion_list_remaining_capacity;
951950
size_t executor_creation_counter;
952951
_rare_events rare_events;
953952
PyDict_WatchCallback builtins_dict_watcher;

Include/internal/pycore_optimizer.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ typedef struct {
2525
uint8_t opcode;
2626
uint8_t oparg;
2727
uint8_t valid;
28-
uint8_t linked;
2928
uint8_t chain_depth; // Must be big enough for MAX_CHAIN_DEPTH - 1.
3029
bool warm;
3130
int32_t index; // Index of ENTER_EXECUTOR (if code isn't NULL, below).
@@ -55,11 +54,6 @@ typedef struct _PyExecutorObject {
5554
_PyExitData exits[1];
5655
} _PyExecutorObject;
5756

58-
/* If pending deletion list gets large enough, then scan,
59-
* and free any executors that aren't executing
60-
* i.e. any that aren't a thread's current_executor. */
61-
#define EXECUTOR_DELETE_LIST_MAX 100
62-
6357
// Export for '_opcode' shared extension (JIT compiler).
6458
PyAPI_FUNC(_PyExecutorObject*) _Py_GetExecutor(PyCodeObject *code, int offset);
6559

@@ -80,7 +74,6 @@ PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp);
8074
#else
8175
# define _Py_Executors_InvalidateDependency(A, B, C) ((void)0)
8276
# define _Py_Executors_InvalidateAll(A, B) ((void)0)
83-
# define _Py_Executors_InvalidateCold(A) ((void)0)
8477

8578
#endif
8679

Python/ceval_gil.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,13 +1397,19 @@ _Py_HandlePending(PyThreadState *tstate)
13971397
if ((breaker & _PY_GC_SCHEDULED_BIT) != 0) {
13981398
_Py_unset_eval_breaker_bit(tstate, _PY_GC_SCHEDULED_BIT);
13991399
_Py_RunGC(tstate);
1400+
#ifdef _Py_TIER2
1401+
_Py_ClearExecutorDeletionList(tstate->interp);
1402+
#endif
14001403
}
14011404

1405+
#ifdef _Py_TIER2
14021406
if ((breaker & _PY_EVAL_JIT_INVALIDATE_COLD_BIT) != 0) {
14031407
_Py_unset_eval_breaker_bit(tstate, _PY_EVAL_JIT_INVALIDATE_COLD_BIT);
14041408
_Py_Executors_InvalidateCold(tstate->interp);
14051409
tstate->interp->executor_creation_counter = JIT_CLEANUP_THRESHOLD;
1410+
_Py_ClearExecutorDeletionList(tstate->interp);
14061411
}
1412+
#endif
14071413

14081414
/* GIL drop request */
14091415
if ((breaker & _PY_GIL_DROP_REQUEST_BIT) != 0) {

Python/optimizer.c

Lines changed: 84 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,6 @@ get_oparg(PyObject *self, PyObject *Py_UNUSED(ignored))
246246
///////////////////// Experimental UOp Optimizer /////////////////////
247247

248248
static int executor_clear(PyObject *executor);
249-
static void unlink_executor(_PyExecutorObject *executor);
250-
251249

252250
void
253251
_PyExecutor_Free(_PyExecutorObject *self)
@@ -258,63 +256,76 @@ _PyExecutor_Free(_PyExecutorObject *self)
258256
PyObject_GC_Del(self);
259257
}
260258

259+
static void executor_invalidate(PyObject *op);
260+
261+
static void
262+
executor_clear_exits(_PyExecutorObject *executor)
263+
{
264+
_PyExecutorObject *cold = _PyExecutor_GetColdExecutor();
265+
_PyExecutorObject *cold_dynamic = _PyExecutor_GetColdDynamicExecutor();
266+
for (uint32_t i = 0; i < executor->exit_count; i++) {
267+
_PyExitData *exit = &executor->exits[i];
268+
exit->temperature = initial_unreachable_backoff_counter();
269+
_PyExecutorObject *old = executor->exits[i].executor;
270+
exit->executor = exit->is_dynamic ? cold_dynamic : cold;
271+
Py_DECREF(old);
272+
}
273+
}
274+
275+
261276
void
262277
_Py_ClearExecutorDeletionList(PyInterpreterState *interp)
263278
{
279+
if (interp->executor_deletion_list_head == NULL) {
280+
return;
281+
}
264282
_PyRuntimeState *runtime = &_PyRuntime;
265283
HEAD_LOCK(runtime);
266284
PyThreadState* ts = PyInterpreterState_ThreadHead(interp);
285+
while (ts) {
286+
_PyExecutorObject *current = (_PyExecutorObject *)ts->current_executor;
287+
Py_XINCREF(current);
288+
ts = ts->next;
289+
}
267290
HEAD_UNLOCK(runtime);
291+
_PyExecutorObject *keep_list = NULL;
292+
do {
293+
_PyExecutorObject *exec = interp->executor_deletion_list_head;
294+
interp->executor_deletion_list_head = exec->vm_data.links.next;
295+
if (Py_REFCNT(exec) == 0) {
296+
_PyExecutor_Free(exec);
297+
} else {
298+
exec->vm_data.links.next = keep_list;
299+
keep_list = exec;
300+
}
301+
} while (interp->executor_deletion_list_head != NULL);
302+
interp->executor_deletion_list_head = keep_list;
303+
HEAD_LOCK(runtime);
304+
ts = PyInterpreterState_ThreadHead(interp);
268305
while (ts) {
269306
_PyExecutorObject *current = (_PyExecutorObject *)ts->current_executor;
270307
if (current != NULL) {
271-
/* Anything in this list will be unlinked, so we can reuse the
272-
* linked field as a reachability marker. */
273-
current->vm_data.linked = 1;
308+
_Py_DECREF_NO_DEALLOC((PyObject *)current);
274309
}
275-
HEAD_LOCK(runtime);
276-
ts = PyThreadState_Next(ts);
277-
HEAD_UNLOCK(runtime);
278-
}
279-
_PyExecutorObject **prev_to_next_ptr = &interp->executor_deletion_list_head;
280-
_PyExecutorObject *exec = *prev_to_next_ptr;
281-
while (exec != NULL) {
282-
if (exec->vm_data.linked) {
283-
// This executor is currently executing
284-
exec->vm_data.linked = 0;
285-
prev_to_next_ptr = &exec->vm_data.links.next;
286-
}
287-
else {
288-
*prev_to_next_ptr = exec->vm_data.links.next;
289-
_PyExecutor_Free(exec);
290-
}
291-
exec = *prev_to_next_ptr;
310+
ts = ts->next;
292311
}
293-
interp->executor_deletion_list_remaining_capacity = EXECUTOR_DELETE_LIST_MAX;
312+
HEAD_UNLOCK(runtime);
294313
}
295314

296315
static void
297316
add_to_pending_deletion_list(_PyExecutorObject *self)
298317
{
299318
PyInterpreterState *interp = PyInterpreterState_Get();
319+
self->vm_data.links.previous = NULL;
300320
self->vm_data.links.next = interp->executor_deletion_list_head;
301321
interp->executor_deletion_list_head = self;
302-
if (interp->executor_deletion_list_remaining_capacity > 0) {
303-
interp->executor_deletion_list_remaining_capacity--;
304-
}
305-
else {
306-
_Py_ClearExecutorDeletionList(interp);
307-
}
308322
}
309323

310324
static void
311325
uop_dealloc(PyObject *op) {
312326
_PyExecutorObject *self = _PyExecutorObject_CAST(op);
313-
_PyObject_GC_UNTRACK(self);
327+
executor_invalidate(op);
314328
assert(self->vm_data.code == NULL);
315-
unlink_executor(self);
316-
// Once unlinked it becomes impossible to invalidate an executor, so do it here.
317-
self->vm_data.valid = 0;
318329
add_to_pending_deletion_list(self);
319330
}
320331

@@ -1621,19 +1632,14 @@ link_executor(_PyExecutorObject *executor)
16211632
head->vm_data.links.previous = executor;
16221633
interp->executor_list_head = executor;
16231634
}
1624-
executor->vm_data.linked = true;
16251635
/* executor_list_head must be first in list */
16261636
assert(interp->executor_list_head->vm_data.links.previous == NULL);
16271637
}
16281638

16291639
static void
16301640
unlink_executor(_PyExecutorObject *executor)
16311641
{
1632-
if (!executor->vm_data.linked) {
1633-
return;
1634-
}
16351642
_PyExecutorLinkListNode *links = &executor->vm_data.links;
1636-
assert(executor->vm_data.valid);
16371643
_PyExecutorObject *next = links->next;
16381644
_PyExecutorObject *prev = links->previous;
16391645
if (next != NULL) {
@@ -1648,7 +1654,6 @@ unlink_executor(_PyExecutorObject *executor)
16481654
assert(interp->executor_list_head == executor);
16491655
interp->executor_list_head = next;
16501656
}
1651-
executor->vm_data.linked = false;
16521657
}
16531658

16541659
/* This must be called by optimizers before using the executor */
@@ -1662,61 +1667,47 @@ _Py_ExecutorInit(_PyExecutorObject *executor, const _PyBloomFilter *dependency_s
16621667
link_executor(executor);
16631668
}
16641669

1665-
_PyExecutorObject *
1666-
_PyExecutor_GetColdExecutor(void)
1670+
static _PyExecutorObject *
1671+
make_cold_executor(uint16_t opcode)
16671672
{
1668-
PyInterpreterState *interp = _PyInterpreterState_GET();
1669-
if (interp->cold_executor != NULL) {
1670-
return interp->cold_executor;
1671-
}
16721673
_PyExecutorObject *cold = allocate_executor(0, 1);
16731674
if (cold == NULL) {
16741675
Py_FatalError("Cannot allocate core JIT code");
16751676
}
1676-
((_PyUOpInstruction *)cold->trace)->opcode = _COLD_EXIT_r00;
1677-
#ifdef _Py_JIT
1678-
cold->jit_code = NULL;
1679-
cold->jit_size = 0;
1677+
((_PyUOpInstruction *)cold->trace)->opcode = opcode;
16801678
// This is initialized to true so we can prevent the executor
16811679
// from being immediately detected as cold and invalidated.
16821680
cold->vm_data.warm = true;
1681+
#ifdef _Py_JIT
1682+
cold->jit_code = NULL;
1683+
cold->jit_size = 0;
16831684
if (_PyJIT_Compile(cold, cold->trace, 1)) {
16841685
Py_DECREF(cold);
16851686
Py_FatalError("Cannot allocate core JIT code");
16861687
}
16871688
#endif
16881689
_Py_SetImmortal((PyObject *)cold);
1689-
interp->cold_executor = cold;
16901690
return cold;
16911691
}
16921692

16931693
_PyExecutorObject *
1694-
_PyExecutor_GetColdDynamicExecutor(void)
1694+
_PyExecutor_GetColdExecutor(void)
16951695
{
16961696
PyInterpreterState *interp = _PyInterpreterState_GET();
1697-
if (interp->cold_dynamic_executor != NULL) {
1698-
assert(interp->cold_dynamic_executor->trace[0].opcode == _COLD_DYNAMIC_EXIT_r00);
1699-
return interp->cold_dynamic_executor;
1700-
}
1701-
_PyExecutorObject *cold = allocate_executor(0, 1);
1702-
if (cold == NULL) {
1703-
Py_FatalError("Cannot allocate core JIT code");
1697+
if (interp->cold_executor == NULL) {
1698+
return interp->cold_executor = make_cold_executor(_COLD_EXIT_r00);;
17041699
}
1705-
((_PyUOpInstruction *)cold->trace)->opcode = _COLD_DYNAMIC_EXIT_r00;
1706-
#ifdef _Py_JIT
1707-
cold->jit_code = NULL;
1708-
cold->jit_size = 0;
1709-
// This is initialized to true so we can prevent the executor
1710-
// from being immediately detected as cold and invalidated.
1711-
cold->vm_data.warm = true;
1712-
if (_PyJIT_Compile(cold, cold->trace, 1)) {
1713-
Py_DECREF(cold);
1714-
Py_FatalError("Cannot allocate core JIT code");
1700+
return interp->cold_executor;
1701+
}
1702+
1703+
_PyExecutorObject *
1704+
_PyExecutor_GetColdDynamicExecutor(void)
1705+
{
1706+
PyInterpreterState *interp = _PyInterpreterState_GET();
1707+
if (interp->cold_dynamic_executor == NULL) {
1708+
interp->cold_dynamic_executor = make_cold_executor(_COLD_DYNAMIC_EXIT_r00);
17151709
}
1716-
#endif
1717-
_Py_SetImmortal((PyObject *)cold);
1718-
interp->cold_dynamic_executor = cold;
1719-
return cold;
1710+
return interp->cold_dynamic_executor;
17201711
}
17211712

17221713
void
@@ -1755,32 +1746,28 @@ _Py_ExecutorDetach(_PyExecutorObject *executor)
17551746
Py_DECREF(executor);
17561747
}
17571748

1758-
static int
1759-
executor_clear(PyObject *op)
1749+
/* Executors can be invalidated at any time,
1750+
even with a stop-the-world lock held.
1751+
Consequently it must not run arbitrary code,
1752+
including Py_DECREF with a non-executor. */
1753+
static void
1754+
executor_invalidate(PyObject *op)
17601755
{
17611756
_PyExecutorObject *executor = _PyExecutorObject_CAST(op);
17621757
if (!executor->vm_data.valid) {
1763-
return 0;
1758+
return;
17641759
}
1765-
assert(executor->vm_data.valid == 1);
1766-
unlink_executor(executor);
17671760
executor->vm_data.valid = 0;
1768-
1769-
/* It is possible for an executor to form a reference
1770-
* cycle with itself, so decref'ing a side exit could
1771-
* free the executor unless we hold a strong reference to it
1772-
*/
1773-
_PyExecutorObject *cold = _PyExecutor_GetColdExecutor();
1774-
Py_INCREF(executor);
1775-
for (uint32_t i = 0; i < executor->exit_count; i++) {
1776-
executor->exits[i].temperature = initial_unreachable_backoff_counter();
1777-
_PyExecutorObject *e = executor->exits[i].executor;
1778-
executor->exits[i].executor = cold;
1779-
Py_DECREF(e);
1780-
}
1761+
unlink_executor(executor);
1762+
executor_clear_exits(executor);
17811763
_Py_ExecutorDetach(executor);
1782-
Py_DECREF(executor);
1783-
return 0;
1764+
_PyObject_GC_UNTRACK(op);
1765+
}
1766+
1767+
static int
1768+
executor_clear(PyObject *op)
1769+
{
1770+
executor_invalidate(op);
17841771
}
17851772

17861773
void
@@ -1805,7 +1792,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is
18051792
if (invalidate == NULL) {
18061793
goto error;
18071794
}
1808-
/* Clearing an executor can deallocate others, so we need to make a list of
1795+
/* Clearing an executor can clear others, so we need to make a list of
18091796
* executors to invalidate first */
18101797
for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
18111798
assert(exec->vm_data.valid);
@@ -1819,7 +1806,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is
18191806
}
18201807
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) {
18211808
PyObject *exec = PyList_GET_ITEM(invalidate, i);
1822-
executor_clear(exec);
1809+
executor_invalidate(exec);
18231810
if (is_invalidation) {
18241811
OPT_STAT_INC(executors_invalidated);
18251812
}
@@ -1851,13 +1838,13 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
18511838
{
18521839
while (interp->executor_list_head) {
18531840
_PyExecutorObject *executor = interp->executor_list_head;
1854-
assert(executor->vm_data.valid == 1 && executor->vm_data.linked == 1);
1841+
assert(executor->vm_data.valid);
18551842
if (executor->vm_data.code) {
18561843
// Clear the entire code object so its co_executors array be freed:
18571844
_PyCode_Clear_Executors(executor->vm_data.code);
18581845
}
18591846
else {
1860-
executor_clear((PyObject *)executor);
1847+
executor_invalidate((PyObject *)executor);
18611848
}
18621849
if (is_invalidation) {
18631850
OPT_STAT_INC(executors_invalidated);
@@ -1892,7 +1879,7 @@ _Py_Executors_InvalidateCold(PyInterpreterState *interp)
18921879
}
18931880
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) {
18941881
PyObject *exec = PyList_GET_ITEM(invalidate, i);
1895-
executor_clear(exec);
1882+
executor_invalidate(exec);
18961883
}
18971884
Py_DECREF(invalidate);
18981885
return;

Python/pystate.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,6 @@ init_interpreter(PyInterpreterState *interp,
570570
interp->compiling = false;
571571
interp->executor_list_head = NULL;
572572
interp->executor_deletion_list_head = NULL;
573-
interp->executor_deletion_list_remaining_capacity = 0;
574573
interp->executor_creation_counter = JIT_CLEANUP_THRESHOLD;
575574
if (interp != &runtime->_main_interpreter) {
576575
/* Fix the self-referential, statically initialized fields. */

0 commit comments

Comments
 (0)