Skip to content

Commit 725894d

Browse files
fix JIT invalidation mechanism for FT
1 parent ba9a65a commit 725894d

File tree

5 files changed

+34
-11
lines changed

5 files changed

+34
-11
lines changed

Include/cpython/code.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ typedef struct {
1616
} _PyCoCached;
1717

1818
typedef struct {
19+
uint8_t is_finalizing;
1920
int size;
2021
int capacity;
2122
struct _PyExecutorObject *executors[1];

Include/internal/pycore_optimizer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj);
7777
#define _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS 6
7878

7979
#ifdef _Py_TIER2
80+
PyAPI_FUNC(void) _Py_Executors_InvalidateDependencyWorldStopped(PyInterpreterState *interp, void *obj, int is_invalidation);
8081
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation);
82+
PyAPI_FUNC(void) _Py_Executors_InvalidateAllWorldStopped(PyInterpreterState *interp, int is_invalidation);
8183
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);
8284
PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyThreadState *tstate);
8385

Objects/codeobject.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,6 +2185,13 @@ _PyCode_ReturnsOnlyNone(PyCodeObject *co)
21852185
static void
21862186
clear_executors(PyCodeObject *co)
21872187
{
2188+
#ifdef Py_GIL_DISABLED
2189+
uint8_t expected = 0;
2190+
if (!_Py_atomic_compare_exchange_uint8(&co->co_executors->is_finalizing, &expected, 1)) {
2191+
// Another thread is already finalizing this.
2192+
return;
2193+
}
2194+
#endif
21882195
assert(co->co_executors);
21892196
for (int i = 0; i < co->co_executors->size; i++) {
21902197
if (co->co_executors->executors[i]) {

Python/instrumentation.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,7 +1785,7 @@ force_instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
17851785
if (code->co_executors != NULL) {
17861786
_PyCode_Clear_Executors(code);
17871787
}
1788-
_Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), code, 1);
1788+
_Py_Executors_InvalidateDependencyWorldStopped(_PyInterpreterState_GET(), code, 1);
17891789
#endif
17901790
int code_len = (int)Py_SIZE(code);
17911791
/* Exit early to avoid creating instrumentation
@@ -2028,7 +2028,7 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events)
20282028
}
20292029
set_global_version(tstate, new_version);
20302030
#ifdef _Py_TIER2
2031-
_Py_Executors_InvalidateAll(interp, 1);
2031+
_Py_Executors_InvalidateAllWorldStopped(interp, 1);
20322032
#endif
20332033
return instrument_all_executing_code_objects(interp);
20342034
}

Python/optimizer.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ get_index_for_executor(PyCodeObject *code, _Py_CODEUNIT *instr)
8181
if (new == NULL) {
8282
return -1;
8383
}
84+
new->is_finalizing = 0;
8485
new->capacity = new_capacity;
8586
new->size = size;
8687
code->co_executors = new;
@@ -1698,7 +1699,7 @@ _PyJit_Tracer_InvalidateDependency(PyThreadState *tstate, void *obj)
16981699
* May cause other executors to be invalidated as well
16991700
*/
17001701
void
1701-
_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation)
1702+
_Py_Executors_InvalidateDependencyWorldStopped(PyInterpreterState *interp, void *obj, int is_invalidation)
17021703
{
17031704
_PyBloomFilter obj_filter;
17041705
_Py_BloomFilter_Init(&obj_filter);
@@ -1711,7 +1712,6 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is
17111712
}
17121713
/* Clearing an executor can deallocate others, so we need to make a list of
17131714
* executors to invalidate first */
1714-
_PyEval_StopTheWorldAll(&_PyRuntime);
17151715
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
17161716
_PyJit_Tracer_InvalidateDependency(p, obj);
17171717
for (_PyExecutorObject *exec = ((_PyThreadStateImpl *)p)->jit_executor_state.executor_list_head; exec != NULL;) {
@@ -1728,7 +1728,6 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is
17281728
exec = next;
17291729
}
17301730
}
1731-
_PyEval_StartTheWorldAll(&_PyRuntime);
17321731
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) {
17331732
PyObject *exec = PyList_GET_ITEM(invalidate, i);
17341733
executor_clear(exec);
@@ -1741,13 +1740,19 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is
17411740
error:
17421741
PyErr_Clear();
17431742
Py_XDECREF(invalidate);
1744-
// If we're truly out of memory, wiping out everything is a fine fallback:
1745-
_Py_Executors_InvalidateAll(interp, is_invalidation);
1743+
// If we're truly out of memory, DO NOT wipe everything as the world is stopped, and we might deadlock.
17461744
}
17471745

1748-
/* Invalidate all executors */
17491746
void
1750-
_Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
1747+
_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation)
1748+
{
1749+
_PyEval_StopTheWorld(interp);
1750+
_Py_Executors_InvalidateDependencyWorldStopped(interp, obj, is_invalidation);
1751+
_PyEval_StartTheWorld(interp);
1752+
}
1753+
1754+
void
1755+
_Py_Executors_InvalidateAllWorldStopped(PyInterpreterState *interp, int is_invalidation)
17511756
{
17521757
PyObject *invalidate = PyList_New(0);
17531758
if (invalidate == NULL) {
@@ -1756,7 +1761,6 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
17561761
}
17571762
/* Clearing an executor can deallocate others, so we need to make a list of
17581763
* executors to invalidate first */
1759-
_PyEval_StopTheWorldAll(&_PyRuntime);
17601764
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
17611765
for (_PyExecutorObject *exec = ((_PyThreadStateImpl *)p)->jit_executor_state.executor_list_head; exec != NULL;) {
17621766
assert(exec->vm_data.valid);
@@ -1771,7 +1775,6 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
17711775
exec = next;
17721776
}
17731777
}
1774-
_PyEval_StartTheWorldAll(&_PyRuntime);
17751778
Py_ssize_t list_len = PyList_GET_SIZE(invalidate);
17761779
for (Py_ssize_t i = 0; i < list_len; i++) {
17771780
_PyExecutorObject *executor = (_PyExecutorObject *)PyList_GET_ITEM(invalidate, i);
@@ -1787,6 +1790,16 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
17871790
}
17881791
}
17891792
Py_DECREF(invalidate);
1793+
// If we're truly out of memory, DO NOT wipe everything as the world is stopped, and we might deadlock.
1794+
}
1795+
1796+
/* Invalidate all executors */
1797+
void
1798+
_Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
1799+
{
1800+
_PyEval_StopTheWorld(interp);
1801+
_Py_Executors_InvalidateAllWorldStopped(interp, is_invalidation);
1802+
_PyEval_StartTheWorld(interp);
17901803
}
17911804

17921805

0 commit comments

Comments
 (0)