Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6a3bd75
Move all JIT fields to thread state
Fidget-Spinner Nov 13, 2025
ba9a65a
fix a bug with traversing states
Fidget-Spinner Nov 13, 2025
725894d
fix JIT invalidation mechanism for FT
Fidget-Spinner Nov 14, 2025
1e5713d
fix re-entrant finalizers
Fidget-Spinner Nov 14, 2025
f0d4c57
Re-enable the JIT
Fidget-Spinner Nov 14, 2025
67de7d6
cleanup a little
Fidget-Spinner Nov 14, 2025
7f0bc57
fix weird GC bugs
Fidget-Spinner Nov 14, 2025
f05e61c
re-enable jit on some stuff
Fidget-Spinner Nov 14, 2025
f78e8c8
fix test, more locks!
Fidget-Spinner Nov 14, 2025
e1f1b30
fix JIT builds
Fidget-Spinner Nov 14, 2025
53c5e1d
only clear at end
Fidget-Spinner Nov 14, 2025
cc38ee4
remove locks in JIT code
Fidget-Spinner Nov 14, 2025
f8fefb3
fix a few bugs
Fidget-Spinner Nov 14, 2025
d76a24b
Improve tracer thread safety
Fidget-Spinner Nov 15, 2025
fa99108
set immortal before GC
Fidget-Spinner Nov 15, 2025
fcfed96
📜🤖 Added by blurb_it.
blurb-it[bot] Nov 15, 2025
ba67ab7
fix default builkd
Fidget-Spinner Nov 15, 2025
b46385b
Merge branch 'jit_ft' of github.com:Fidget-Spinner/cpython into jit_ft
Fidget-Spinner Nov 15, 2025
46285be
Merge remote-tracking branch 'upstream/main' into jit_ft
Fidget-Spinner Nov 15, 2025
8ce83cd
fix comment
Fidget-Spinner Nov 15, 2025
fa55643
lint
Fidget-Spinner Nov 15, 2025
92f3dbf
fix nojit builds
Fidget-Spinner Nov 15, 2025
7240b15
Remove heavywieght locking--not needed
Fidget-Spinner Nov 15, 2025
cb87676
address review, fix bug
Fidget-Spinner Nov 15, 2025
f972637
GC executors
Fidget-Spinner Nov 15, 2025
3ef237b
Change the name
Fidget-Spinner Nov 16, 2025
44356d6
Address review, remove more refcounting
Fidget-Spinner Nov 16, 2025
b819053
Fix TSAN races
Fidget-Spinner Nov 16, 2025
527aac1
Remove atomics from _CHECK_VALIDITY
Fidget-Spinner Nov 16, 2025
b80c02e
fix comment
Fidget-Spinner Nov 16, 2025
4278c9d
fix typo
Fidget-Spinner Nov 16, 2025
d84215d
Re-enable the type/function reverse cache
Fidget-Spinner Nov 16, 2025
b08ef60
allow allow deferred things to be borrrowed too
Fidget-Spinner Nov 16, 2025
c2c8fbe
fix warnings
Fidget-Spinner Nov 16, 2025
162b1ec
skip bad tests
Fidget-Spinner Nov 16, 2025
e0890ff
skip bad tests
Fidget-Spinner Nov 16, 2025
197e9f4
reduce diff
Fidget-Spinner Nov 17, 2025
46413cf
Reduce diff to minmal
Fidget-Spinner Nov 17, 2025
b5d6571
reduce diff further
Fidget-Spinner Nov 17, 2025
a93c26c
Reduce diff to near minimal
Fidget-Spinner Nov 17, 2025
97d5f2b
typo fix
Fidget-Spinner Nov 17, 2025
c381903
Address review
Fidget-Spinner Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/cpython/code.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ typedef struct {
} _PyCoCached;

typedef struct {
uint8_t is_finalizing;
int size;
int capacity;
struct _PyExecutorObject *executors[1];
Expand Down
8 changes: 0 additions & 8 deletions Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -930,14 +930,6 @@ struct _is {
struct types_state types;
struct callable_cache callable_cache;
PyObject *common_consts[NUM_COMMON_CONSTANTS];
bool jit;
bool compiling;
struct _PyExecutorObject *executor_list_head;
struct _PyExecutorObject *executor_deletion_list_head;
struct _PyExecutorObject *cold_executor;
struct _PyExecutorObject *cold_dynamic_executor;
int executor_deletion_list_remaining_capacity;
size_t executor_creation_counter;
_rare_events rare_events;
PyDict_WatchCallback builtins_dict_watcher;

Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ typedef struct _PyExecutorLinkListNode {
typedef struct {
uint8_t opcode;
uint8_t oparg;
#ifdef Py_GIL_DISABLED
uint8_t valid;
#else
uint8_t valid:1;
#endif
uint8_t linked:1;
uint8_t chain_depth:6; // Must be big enough for MAX_CHAIN_DEPTH - 1.
bool warm;
Expand All @@ -45,6 +49,7 @@ typedef struct _PyExitData {

typedef struct _PyExecutorObject {
PyObject_VAR_HEAD
PyThreadState *tstate;
const _PyUOpInstruction *trace;
_PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */
uint32_t exit_count;
Expand Down Expand Up @@ -73,14 +78,16 @@ PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj);

#ifdef _Py_TIER2
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateDependencyLockHeld(PyInterpreterState *interp, void *obj, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp);

PyAPI_FUNC(void) _Py_Executors_InvalidateAllLockHeld(PyInterpreterState *interp, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyThreadState *tstate);
PyAPI_FUNC(void) _Py_Executors_InvalidateColdGC(PyInterpreterState *interp);
#else
# define _Py_Executors_InvalidateDependency(A, B, C) ((void)0)
# define _Py_Executors_InvalidateAll(A, B) ((void)0)
# define _Py_Executors_InvalidateCold(A) ((void)0)

# define _Py_Executors_InvalidateColdGC(A) ((void)0)
#endif

// Used as the threshold to trigger executor invalidation when
Expand Down Expand Up @@ -359,7 +366,7 @@ extern void _PyExecutor_Free(_PyExecutorObject *self);

PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
#ifdef _Py_TIER2
extern void _Py_ClearExecutorDeletionList(PyInterpreterState *interp);
extern void _Py_ClearExecutorDeletionList(PyThreadState *tstate);
#endif

int _PyJit_translate_single_bytecode_to_trace(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, int stop_tracing_opcode);
Expand All @@ -372,8 +379,6 @@ _PyJit_TryInitializeTracing(PyThreadState *tstate, _PyInterpreterFrame *frame,

void _PyJit_FinalizeTracing(PyThreadState *tstate);

void _PyJit_Tracer_InvalidateDependency(PyThreadState *old_tstate, void *obj);

#ifdef __cplusplus
}
#endif
Expand Down
17 changes: 16 additions & 1 deletion Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ typedef struct _PyJitTracerInitialState {
} _PyJitTracerInitialState;

typedef struct _PyJitTracerPreviousState {
bool dependencies_still_valid;
uint8_t dependencies_still_valid;
bool instr_is_super;
int code_max_size;
int code_curr_size;
Expand All @@ -48,11 +48,25 @@ typedef struct _PyJitTracerPreviousState {
} _PyJitTracerPreviousState;

typedef struct _PyJitTracerState {
PyMutex lock;
_PyUOpInstruction *code_buffer;
_PyJitTracerInitialState initial_state;
_PyJitTracerPreviousState prev_state;
} _PyJitTracerState;

#endif

typedef struct _PyJitExecutorState {
char jit;
#if _Py_TIER2
struct _PyExecutorObject *executor_list_head;
struct _PyExecutorObject *executor_deletion_list_head;
struct _PyExecutorObject *cold_executor;
struct _PyExecutorObject *cold_dynamic_executor;
int executor_deletion_list_remaining_capacity;
size_t executor_creation_counter;
#endif
} _PyJitExecutorState;

// Every PyThreadState is actually allocated as a _PyThreadStateImpl. The
// PyThreadState fields are exposed as part of the C API, although most fields
Expand Down Expand Up @@ -121,6 +135,7 @@ typedef struct _PyThreadStateImpl {
#if _Py_TIER2
_PyJitTracerState jit_tracer_state;
#endif
_PyJitExecutorState jit_executor_state;
} _PyThreadStateImpl;

#ifdef __cplusplus
Expand Down
30 changes: 19 additions & 11 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@

import _opcode

from test.support import (script_helper, requires_specialization,
import_helper, Py_GIL_DISABLED, requires_jit_enabled,
reset_code)
from test.support import (
script_helper,
import_helper,
Py_GIL_DISABLED,
requires_jit_enabled,
reset_code
)

_testinternalcapi = import_helper.import_module("_testinternalcapi")

Expand Down Expand Up @@ -60,9 +64,8 @@ def iter_opnames(ex):
def get_opnames(ex):
return list(iter_opnames(ex))

FT_TEST_DISABLED_REVERSE_TYPE_CACHE = "FT build reverse type cache is disabled for now"

@requires_specialization
@unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds")
@requires_jit_enabled
class TestExecutorInvalidation(unittest.TestCase):

Expand Down Expand Up @@ -126,12 +129,8 @@ def f():
self.assertTrue(exe.is_valid())
sys._clear_internal_caches()
self.assertFalse(exe.is_valid())
exe = get_first_executor(f)
self.assertIsNone(exe)


@requires_specialization
@unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds")
@requires_jit_enabled
@unittest.skipIf(os.getenv("PYTHON_UOPS_OPTIMIZE") == "0", "Needs uop optimizer to run.")
class TestUops(unittest.TestCase):
Expand Down Expand Up @@ -434,8 +433,6 @@ def testfunc(n, m):
self.assertIn("_FOR_ITER_TIER_TWO", uops)


@requires_specialization
@unittest.skipIf(Py_GIL_DISABLED, "optimizer not yet supported in free-threaded builds")
@requires_jit_enabled
@unittest.skipIf(os.getenv("PYTHON_UOPS_OPTIMIZE") == "0", "Needs uop optimizer to run.")
class TestUopsOptimization(unittest.TestCase):
Expand Down Expand Up @@ -1186,6 +1183,7 @@ def test_modified_local_is_seen_by_optimized_code(self):
self.assertIs(type(s), float)
self.assertEqual(s, 1024.0)

@unittest.skipIf(Py_GIL_DISABLED, FT_TEST_DISABLED_REVERSE_TYPE_CACHE)
def test_guard_type_version_removed(self):
def thing(a):
x = 0
Expand All @@ -1204,6 +1202,7 @@ class Foo:
guard_type_version_count = opnames.count("_GUARD_TYPE_VERSION")
self.assertEqual(guard_type_version_count, 1)

@unittest.skipIf(Py_GIL_DISABLED, FT_TEST_DISABLED_REVERSE_TYPE_CACHE)
def test_guard_type_version_removed_inlined(self):
"""
Verify that the guard type version if we have an inlined function
Expand All @@ -1230,6 +1229,7 @@ class Foo:
guard_type_version_count = opnames.count("_GUARD_TYPE_VERSION")
self.assertEqual(guard_type_version_count, 1)

@unittest.skipIf(Py_GIL_DISABLED, FT_TEST_DISABLED_REVERSE_TYPE_CACHE)
def test_guard_type_version_removed_invalidation(self):

def thing(a):
Expand Down Expand Up @@ -1260,6 +1260,7 @@ class Bar:
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)

@unittest.skipIf(Py_GIL_DISABLED, FT_TEST_DISABLED_REVERSE_TYPE_CACHE)
def test_guard_type_version_removed_escaping(self):

def thing(a):
Expand All @@ -1283,6 +1284,7 @@ class Foo:
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)

@unittest.skipIf(Py_GIL_DISABLED, "FT build only specializes on deferred methods/functions/classes")
def test_guard_type_version_executor_invalidated(self):
"""
Verify that the executor is invalided on a type change.
Expand Down Expand Up @@ -2052,6 +2054,7 @@ def testfunc(n):
self.assertNotIn("_GUARD_NOS_INT", uops)
self.assertNotIn("_GUARD_TOS_INT", uops)

@unittest.skipIf(Py_GIL_DISABLED, "FT build immortalizes constants")
def test_call_len_known_length_small_int(self):
# Make sure that len(t) is optimized for a tuple of length 5.
# See https://github.com/python/cpython/issues/139393.
Expand All @@ -2076,6 +2079,7 @@ def testfunc(n):
self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)

@unittest.skipIf(Py_GIL_DISABLED, "FT build immortalizes constants")
def test_call_len_known_length(self):
# Make sure that len(t) is not optimized for a tuple of length 2048.
# See https://github.com/python/cpython/issues/139393.
Expand Down Expand Up @@ -2327,6 +2331,7 @@ def testfunc(n):
self.assertNotIn("_TO_BOOL_BOOL", uops)
self.assertIn("_GUARD_IS_TRUE_POP", uops)

@unittest.skipIf(Py_GIL_DISABLED, "FT build only specializes on deferred methods/functions/classes")
def test_set_type_version_sets_type(self):
class C:
A = 1
Expand Down Expand Up @@ -2359,6 +2364,7 @@ def testfunc(n):
self.assertNotIn("_LOAD_SMALL_INT", uops)
self.assertIn("_LOAD_CONST_INLINE_BORROW", uops)

@unittest.skipIf(Py_GIL_DISABLED, FT_TEST_DISABLED_REVERSE_TYPE_CACHE)
def test_cached_attributes(self):
class C:
A = 1
Expand Down Expand Up @@ -2497,6 +2503,7 @@ def testfunc(n):

self.assertIn("_POP_TOP_NOP", uops)

@unittest.skipIf(Py_GIL_DISABLED, "FT build immortalizes constants")
def test_pop_top_specialize_int(self):
def testfunc(n):
for _ in range(n):
Expand All @@ -2510,6 +2517,7 @@ def testfunc(n):

self.assertIn("_POP_TOP_INT", uops)

@unittest.skipIf(Py_GIL_DISABLED, "FT build immortalizes constants")
def test_pop_top_specialize_float(self):
def testfunc(n):
for _ in range(n):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add free-threading support to the JIT. Patch by Ken Jin.
3 changes: 1 addition & 2 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1240,8 +1240,7 @@ add_executor_dependency(PyObject *self, PyObject *args)
static PyObject *
invalidate_executors(PyObject *self, PyObject *obj)
{
PyInterpreterState *interp = PyInterpreterState_Get();
_Py_Executors_InvalidateDependency(interp, obj, 1);
_Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), obj, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be reverted too.

Py_RETURN_NONE;
}

Expand Down
19 changes: 16 additions & 3 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2185,6 +2185,13 @@ _PyCode_ReturnsOnlyNone(PyCodeObject *co)
static void
clear_executors(PyCodeObject *co)
{
#ifdef Py_GIL_DISABLED
uint8_t expected = 0;
if (!_Py_atomic_compare_exchange_uint8(&co->co_executors->is_finalizing, &expected, 1)) {
// Another thread is already finalizing this.
return;
}
#endif
assert(co->co_executors);
for (int i = 0; i < co->co_executors->size; i++) {
if (co->co_executors->executors[i]) {
Expand Down Expand Up @@ -2432,9 +2439,11 @@ code_dealloc(PyObject *self)
PyMem_Free(co_extra);
}
#ifdef _Py_TIER2
_PyJit_Tracer_InvalidateDependency(tstate, self);
_Py_Executors_InvalidateDependency(tstate->interp, self, 1);
if (co->co_executors != NULL) {
Py_BEGIN_CRITICAL_SECTION(co);
clear_executors(co);
Py_END_CRITICAL_SECTION();
}
#endif

Expand Down Expand Up @@ -3363,8 +3372,12 @@ deopt_code_unit(PyCodeObject *code, int i)
inst.op.code = _PyOpcode_Deopt[opcode];
assert(inst.op.code < MIN_SPECIALIZED_OPCODE);
}
// JIT should not be enabled with free-threading
assert(inst.op.code != ENTER_EXECUTOR);
if (inst.op.code == ENTER_EXECUTOR) {
_PyExecutorObject *exec = code->co_executors->executors[inst.op.arg];
assert(exec != NULL);
inst.op.code = exec->vm_data.opcode;
inst.op.arg = exec->vm_data.oparg;
}
return inst;
}

Expand Down
1 change: 0 additions & 1 deletion Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,6 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)

#if _Py_TIER2
_Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), co, 1);
_PyJit_Tracer_InvalidateDependency(_PyThreadState_GET(), co);
#endif

_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
Expand Down
3 changes: 1 addition & 2 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "pycore_setobject.h" // _PySet_NextEntry()
#include "pycore_stats.h"
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
#include "pycore_optimizer.h" // _PyJit_Tracer_InvalidateDependency
#include "pycore_optimizer.h" // _Py_Executors_InvalidateDependency

static const char *
func_event_name(PyFunction_WatchEvent event) {
Expand Down Expand Up @@ -1153,7 +1153,6 @@ func_dealloc(PyObject *self)
}
#if _Py_TIER2
_Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), self, 1);
_PyJit_Tracer_InvalidateDependency(_PyThreadState_GET(), self);
#endif
_PyObject_GC_UNTRACK(op);
FT_CLEAR_WEAKREFS(self, op->func_weakreflist);
Expand Down
4 changes: 3 additions & 1 deletion Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ ensure_shared_on_resize(PyListObject *self)
// We can't use _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED here because
// the `CALL_LIST_APPEND` bytecode handler may lock the list without
// a critical section.
assert(Py_REFCNT(self) == 1 || PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex));
assert(Py_REFCNT(self) == 1 ||
(_Py_IsOwnedByCurrentThread((PyObject *)self) && !_PyObject_GC_IS_SHARED(self)) ||
PyMutex_IsLocked(&_PyObject_CAST(self)->ob_mutex));

// Ensure that the list array is freed using QSBR if we are not the
// owning thread.
Expand Down
Loading
Loading