From 65e1f941529187169c94302e93460b011d6a0ad0 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Wed, 18 Mar 2026 17:46:06 +0800 Subject: [PATCH 1/3] Optimize _GUARD_CODE_VERSION+IP via fun version symbols --- Include/internal/pycore_optimizer.h | 2 + Include/internal/pycore_optimizer_types.h | 26 ++- Lib/test/test_capi/test_opt.py | 7 +- Python/optimizer_analysis.c | 2 + Python/optimizer_bytecodes.c | 50 +++++- Python/optimizer_cases.c.h | 39 ++++- Python/optimizer_symbols.c | 192 ++++++++++++++++++++-- 7 files changed, 277 insertions(+), 41 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 8b52d77538abf2..d6032855a18eb0 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -312,6 +312,8 @@ extern PyCodeObject *_Py_uop_sym_get_probable_func_code(JitOptRef sym); extern PyObject *_Py_uop_sym_get_probable_value(JitOptRef sym); extern PyTypeObject *_Py_uop_sym_get_probable_type(JitOptRef sym); extern JitOptRef *_Py_uop_sym_set_stack_depth(JitOptContext *ctx, int stack_depth, JitOptRef *current_sp); +extern uint32_t _Py_uop_sym_get_func_version(JitOptRef ref); +bool _Py_uop_sym_set_func_version(JitOptContext *ctx, JitOptRef ref, uint32_t version); extern void _Py_uop_abstractcontext_init(JitOptContext *ctx, _PyBloomFilter *dependencies); extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx); diff --git a/Include/internal/pycore_optimizer_types.h b/Include/internal/pycore_optimizer_types.h index 2958db5b787975..fe45775201b098 100644 --- a/Include/internal/pycore_optimizer_types.h +++ b/Include/internal/pycore_optimizer_types.h @@ -36,15 +36,16 @@ typedef enum _JitSymType { JIT_SYM_NON_NULL_TAG = 3, JIT_SYM_BOTTOM_TAG = 4, JIT_SYM_TYPE_VERSION_TAG = 5, - JIT_SYM_KNOWN_CLASS_TAG = 6, - JIT_SYM_KNOWN_VALUE_TAG = 7, - JIT_SYM_TUPLE_TAG = 8, - JIT_SYM_TRUTHINESS_TAG = 9, - JIT_SYM_COMPACT_INT = 10, - JIT_SYM_PREDICATE_TAG = 11, - JIT_SYM_RECORDED_VALUE_TAG = 12, - JIT_SYM_RECORDED_TYPE_TAG = 13, - JIT_SYM_RECORDED_GEN_FUNC_TAG = 14, + JIT_SYM_FUNC_VERSION_TAG = 6, + JIT_SYM_KNOWN_CLASS_TAG = 7, + JIT_SYM_KNOWN_VALUE_TAG = 8, + JIT_SYM_TUPLE_TAG = 9, + JIT_SYM_TRUTHINESS_TAG = 10, + JIT_SYM_COMPACT_INT = 11, + JIT_SYM_PREDICATE_TAG = 12, + JIT_SYM_RECORDED_VALUE_TAG = 13, + JIT_SYM_RECORDED_TYPE_TAG = 14, + JIT_SYM_RECORDED_GEN_FUNC_TAG = 15, } JitSymType; typedef struct _jit_opt_known_class { @@ -58,6 +59,11 @@ typedef struct _jit_opt_known_version { uint32_t version; } JitOptKnownVersion; +typedef struct _jit_opt_known_func_version { + uint8_t tag; + uint32_t func_version; +} JitOptKnownFuncVersion; + typedef struct _jit_opt_known_value { uint8_t tag; PyObject *value; @@ -118,6 +124,7 @@ typedef union _jit_opt_symbol { JitOptKnownClass cls; JitOptKnownValue value; JitOptKnownVersion version; + JitOptKnownFuncVersion func_version; JitOptTuple tuple; JitOptTruthiness truthiness; JitOptCompactInt compact; @@ -140,6 +147,7 @@ typedef struct _Py_UOpsAbstractFrame { int stack_len; int locals_len; bool caller; // We have made a call from this frame during the trace + JitOptRef callable; PyFunctionObject *func; PyCodeObject *code; diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2a126a7b29cc73..9182bf52b71a39 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -630,6 +630,8 @@ def dummy(x): self.assertIn("_PUSH_FRAME", uops) self.assertIn("_BINARY_OP_ADD_INT", uops) self.assertNotIn("_CHECK_PEP_523", uops) + self.assertNotIn("_GUARD_CODE_VERSION__PUSH_FRAME", uops) + self.assertNotIn("_GUARD_IP__PUSH_FRAME", uops) def test_int_type_propagate_through_range(self): def testfunc(n): @@ -1540,8 +1542,9 @@ def testfunc(n): self.assertIsNotNone(ex) uops = get_opnames(ex) self.assertIn("_PUSH_FRAME", uops) - # Strength reduced version - self.assertIn("_CHECK_FUNCTION_VERSION_INLINE", uops) + # Both should be not present, as this is a call + # to a simple function with a known function version. + self.assertNotIn("_CHECK_FUNCTION_VERSION_INLINE", uops) self.assertNotIn("_CHECK_FUNCTION_VERSION", uops) # Removed guard self.assertNotIn("_CHECK_FUNCTION_EXACT_ARGS", uops) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index a5ed5953ec082c..520420e9878575 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -291,6 +291,8 @@ add_op(JitOptContext *ctx, _PyUOpInstruction *this_instr, #define sym_get_probable_func_code _Py_uop_sym_get_probable_func_code #define sym_get_probable_value _Py_uop_sym_get_probable_value #define sym_set_stack_depth(DEPTH, SP) _Py_uop_sym_set_stack_depth(ctx, DEPTH, SP) +#define sym_get_func_version _Py_uop_sym_get_func_version +#define sym_set_func_version _Py_uop_sym_set_func_version /* Comparison oparg masks */ #define COMPARE_LT_MASK 2 diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 7ca33e9e77fb74..ee51851ffc86e0 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -857,12 +857,12 @@ dummy_func(void) { } op(_CHECK_FUNCTION_VERSION, (func_version/2, callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) { - if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) { - assert(PyFunction_Check(sym_get_const(ctx, callable))); - ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version); - uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)sym_get_const(ctx, callable); + if (sym_get_func_version(callable) == func_version) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + else { + sym_set_func_version(ctx, callable, func_version); } - sym_set_type(callable, &PyFunction_Type); } op(_CHECK_METHOD_VERSION, (func_version/2, callable, null, unused[oparg] -- callable, null, unused[oparg])) { @@ -1767,12 +1767,46 @@ dummy_func(void) { sym_set_recorded_gen_func(nos, func); } + op(_GUARD_CODE_VERSION__PUSH_FRAME, (version/2 -- )) { + PyCodeObject *co = get_current_code_object(ctx); + if (co->co_version == version) { + _Py_BloomFilter_Add(dependencies, co); + if (sym_get_func_version(ctx->frame->callable) != 0) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + } + else { + ctx->done = true; + } + } + + op(_GUARD_CODE_VERSION_RETURN_VALUE, (version/2 -- )) { + if (ctx->frame->caller) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + } + + op(_GUARD_CODE_VERSION_YIELD_VALUE, (version/2 -- )) { + if (ctx->frame->caller) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + } + + op(_GUARD_CODE_VERSION_RETURN_GENERATOR, (version/2 -- )) { + if (ctx->frame->caller) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + } + op(_GUARD_IP__PUSH_FRAME, (ip/4 --)) { (void)ip; stack_pointer = sym_set_stack_depth((int)this_instr->operand1, stack_pointer); - // TO DO - // Normal function calls to known functions - // do not need an IP guard. + if (sym_get_func_version(ctx->frame->callable) != 0 && + // We can remove this guard for simple function call targets. + (((PyCodeObject *)ctx->frame->func->func_code)->co_flags & + (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) == 0) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } } op(_GUARD_IP_YIELD_VALUE, (ip/4 --)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index df6368ca8ce011..10bbab4a9070ba 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3038,12 +3038,12 @@ JitOptRef callable; callable = stack_pointer[-2 - oparg]; uint32_t func_version = (uint32_t)this_instr->operand0; - if (sym_is_const(ctx, callable) && sym_matches_type(callable, &PyFunction_Type)) { - assert(PyFunction_Check(sym_get_const(ctx, callable))); - ADD_OP(_CHECK_FUNCTION_VERSION_INLINE, 0, func_version); - uop_buffer_last(&ctx->out_buffer)->operand1 = (uintptr_t)sym_get_const(ctx, callable); + if (sym_get_func_version(callable) == func_version) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + else { + sym_set_func_version(ctx, callable, func_version); } - sym_set_type(callable, &PyFunction_Type); break; } @@ -4300,18 +4300,41 @@ } case _GUARD_CODE_VERSION__PUSH_FRAME: { + uint32_t version = (uint32_t)this_instr->operand0; + PyCodeObject *co = get_current_code_object(ctx); + if (co->co_version == version) { + _Py_BloomFilter_Add(dependencies, co); + if (sym_get_func_version(ctx->frame->callable) != 0) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } + } + else { + ctx->done = true; + } break; } case _GUARD_CODE_VERSION_YIELD_VALUE: { + uint32_t version = (uint32_t)this_instr->operand0; + if (ctx->frame->caller) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } break; } case _GUARD_CODE_VERSION_RETURN_VALUE: { + uint32_t version = (uint32_t)this_instr->operand0; + if (ctx->frame->caller) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } break; } case _GUARD_CODE_VERSION_RETURN_GENERATOR: { + uint32_t version = (uint32_t)this_instr->operand0; + if (ctx->frame->caller) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } break; } @@ -4319,6 +4342,12 @@ PyObject *ip = (PyObject *)this_instr->operand0; (void)ip; stack_pointer = sym_set_stack_depth((int)this_instr->operand1, stack_pointer); + if (sym_get_func_version(ctx->frame->callable) != 0 && + // We can remove this guard for simple function call targets. + (((PyCodeObject *)ctx->frame->func->func_code)->co_flags & + (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) == 0) { + REPLACE_OP(this_instr, _NOP, 0, 0); + } break; } diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index fd784aa11e4b89..d7e14422a38439 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -26,26 +26,25 @@ state represents no information, and the BOTTOM state represents contradictory information. Though symbols logically progress through all intermediate nodes, we often skip in-between states for convenience: - UNKNOWN-------------------+------+ - | | | | -NULL | | RECORDED_VALUE* -| | | | <- Anything below this level is an object. -| NON_NULL-+ | | -| | | | | <- Anything below this level has a known type version. -| TYPE_VERSION | | | -| | | | | <- Anything below this level has a known type. -| KNOWN_CLASS | | | -| | | | | | PREDICATE RECORDED_VALUE(known type) -| | | INT* | | | | -| | | | | | | | <- Anything below this level has a known truthiness. -| TUPLE | | | TRUTHINESS | | -| | | | | | | | <- Anything below this level is a known constant. -| KNOWN_VALUE--+----------+------+ -| | <- Anything below this level is unreachable. + UNKNOWN---------------------+------+ + | | | | +NULL | | RECORDED_VALUE* +| | | | <- Anything below this level is an object. +| NON_NULL---------+ | | +| | | | | <- Anything below this level has a known type version. +| TYPE_VERSION | | | +| | | | | <- Anything below this level has a known type. +| KNOWN_CLASS--+ | | | +| | | | | | PREDICATE RECORDED_VALUE(known type) +| | | INT* | | | | +| | | | FUNC_VERSION | | | <- Anything below this level has a known truthiness. +| TUPLE | | | TRUTHINESS | | +| | | | | | | | <- Anything below this level is a known constant. +| KNOWN_VALUE--+-------+----+------+ +| | <- Anything below this level is unreachable. BOTTOM - For example, after guarding that the type of an UNKNOWN local is int, we can narrow the symbol to KNOWN_CLASS (logically progressing though NON_NULL and TYPE_VERSION to get there). Later, we may learn that it is falsey based on the @@ -100,6 +99,9 @@ _PyUOpSymPrint(JitOptRef ref) case JIT_SYM_TYPE_VERSION_TAG: printf("", sym->version.version, (void *)sym); break; + case JIT_SYM_FUNC_VERSION_TAG: + printf("", sym->func_version.func_version, (void *)sym); + break; case JIT_SYM_KNOWN_CLASS_TAG: printf("<%s at %p>", sym->cls.type->tp_name, (void *)sym); break; @@ -305,6 +307,11 @@ _Py_uop_sym_set_type(JitOptContext *ctx, JitOptRef ref, PyTypeObject *typ) sym_set_bottom(ctx, sym); } return; + case JIT_SYM_FUNC_VERSION_TAG: + if (typ != &PyFunction_Type) { + sym_set_bottom(ctx, sym); + } + return; case JIT_SYM_KNOWN_VALUE_TAG: if (Py_TYPE(sym->value.value) != typ) { Py_CLEAR(sym->value.value); @@ -408,6 +415,12 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptRef ref, unsigned int ver return false; } return true; + case JIT_SYM_FUNC_VERSION_TAG: + if (version != PyFunction_Type.tp_version_tag) { + sym_set_bottom(ctx, sym); + return false; + } + return true; case JIT_SYM_BOTTOM_TAG: return false; case JIT_SYM_NON_NULL_TAG: @@ -455,6 +468,87 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptRef ref, unsigned int ver Py_UNREACHABLE(); } +bool +_Py_uop_sym_set_func_version(JitOptContext *ctx, JitOptRef ref, uint32_t version) +{ + JitOptSymbol *sym = PyJitRef_Unwrap(ref); + JitSymType tag = sym->tag; + switch(tag) { + case JIT_SYM_NULL_TAG: + sym_set_bottom(ctx, sym); + return false; + case JIT_SYM_KNOWN_CLASS_TAG: + if (sym->cls.type != &PyFunction_Type) { + sym_set_bottom(ctx, sym); + return false; + } + sym->tag = JIT_SYM_FUNC_VERSION_TAG; + sym->version.version = version; + return true; + case JIT_SYM_KNOWN_VALUE_TAG: + if (Py_TYPE(sym->value.value) != &PyFunction_Type || + ((PyFunctionObject *)sym->value.value)->func_version != version) { + Py_CLEAR(sym->value.value); + sym_set_bottom(ctx, sym); + return false; + } + return true; + case JIT_SYM_TYPE_VERSION_TAG: + if (sym->version.version != PyFunction_Type.tp_version_tag) { + sym_set_bottom(ctx, sym); + return false; + } + sym->tag = JIT_SYM_FUNC_VERSION_TAG; + sym->version.version = version; + return true; + case JIT_SYM_FUNC_VERSION_TAG: + if (sym->func_version.func_version != version) { + sym_set_bottom(ctx, sym); + return false; + } + return true; + case JIT_SYM_BOTTOM_TAG: + return false; + case JIT_SYM_NON_NULL_TAG: + case JIT_SYM_UNKNOWN_TAG: + sym->tag = JIT_SYM_FUNC_VERSION_TAG; + sym->func_version.func_version = version; + return true; + case JIT_SYM_RECORDED_GEN_FUNC_TAG: + case JIT_SYM_COMPACT_INT: + case JIT_SYM_TUPLE_TAG: + case JIT_SYM_PREDICATE_TAG: + case JIT_SYM_TRUTHINESS_TAG: + sym_set_bottom(ctx, sym); + return true; + case JIT_SYM_RECORDED_VALUE_TAG: + PyObject *val = sym->recorded_value.value; + if (Py_TYPE(val) != &PyFunction_Type || + ((PyFunctionObject *)sym->recorded_value.value)->func_version != version) { + Py_CLEAR(sym->recorded_value.value); + sym_set_bottom(ctx, sym); + return false; + } + // Promote to known value, as we have guarded/checked on it. + sym->tag = JIT_SYM_KNOWN_VALUE_TAG; + // New ownership. We need to NewRef here, as + // it's originally kept alive by the trace buffer. + sym->value.value = Py_NewRef(val); + return true; + case JIT_SYM_RECORDED_TYPE_TAG: + if (sym->recorded_type.type == &PyFunction_Type) { + sym->tag = JIT_SYM_FUNC_VERSION_TAG; + sym->func_version.func_version = version; + return true; + } + else { + sym_set_bottom(ctx, sym); + return false; + } + } + Py_UNREACHABLE(); +} + void _Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef ref, PyObject *const_val) { @@ -500,6 +594,14 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef ref, PyObject *const_val) } make_const(sym, const_val); return; + case JIT_SYM_FUNC_VERSION_TAG: + if (Py_TYPE(const_val) != &PyFunction_Type || + ((PyFunctionObject *)const_val)->func_version != sym->func_version.func_version) { + sym_set_bottom(ctx, sym); + return; + } + make_const(sym, const_val); + return; case JIT_SYM_BOTTOM_TAG: return; case JIT_SYM_RECORDED_VALUE_TAG: @@ -675,6 +777,8 @@ _Py_uop_sym_get_type(JitOptRef ref) return Py_TYPE(sym->value.value); case JIT_SYM_TYPE_VERSION_TAG: return _PyType_LookupByVersion(sym->version.version); + case JIT_SYM_FUNC_VERSION_TAG: + return &PyFunction_Type; case JIT_SYM_TUPLE_TAG: return &PyTuple_Type; case JIT_SYM_PREDICATE_TAG: @@ -699,6 +803,7 @@ _Py_uop_sym_get_probable_type(JitOptRef ref) case JIT_SYM_NON_NULL_TAG: case JIT_SYM_UNKNOWN_TAG: case JIT_SYM_TYPE_VERSION_TAG: + case JIT_SYM_FUNC_VERSION_TAG: case JIT_SYM_TUPLE_TAG: case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: @@ -731,6 +836,8 @@ _Py_uop_sym_get_type_version(JitOptRef ref) return 0; case JIT_SYM_TYPE_VERSION_TAG: return sym->version.version; + case JIT_SYM_FUNC_VERSION_TAG: + return PyFunction_Type.tp_version_tag; case JIT_SYM_KNOWN_CLASS_TAG: return sym->cls.version; case JIT_SYM_KNOWN_VALUE_TAG: @@ -748,6 +855,38 @@ _Py_uop_sym_get_type_version(JitOptRef ref) Py_UNREACHABLE(); } +uint32_t +_Py_uop_sym_get_func_version(JitOptRef ref) +{ + JitOptSymbol *sym = PyJitRef_Unwrap(ref); + JitSymType tag = sym->tag; + switch(tag) { + case JIT_SYM_NULL_TAG: + case JIT_SYM_BOTTOM_TAG: + case JIT_SYM_NON_NULL_TAG: + case JIT_SYM_UNKNOWN_TAG: + case JIT_SYM_RECORDED_VALUE_TAG: + case JIT_SYM_RECORDED_TYPE_TAG: + case JIT_SYM_TYPE_VERSION_TAG: + case JIT_SYM_KNOWN_CLASS_TAG: + case JIT_SYM_TUPLE_TAG: + case JIT_SYM_PREDICATE_TAG: + case JIT_SYM_TRUTHINESS_TAG: + case JIT_SYM_COMPACT_INT: + case JIT_SYM_RECORDED_GEN_FUNC_TAG: + return 0; + case JIT_SYM_FUNC_VERSION_TAG: + return sym->func_version.func_version; + case JIT_SYM_KNOWN_VALUE_TAG: + if (Py_TYPE(sym->value.value) == &PyFunction_Type) { + return ((PyFunctionObject *)sym->value.value)->func_version; + } + return 0; + } + Py_UNREACHABLE(); +} + + bool _Py_uop_sym_has_type(JitOptRef sym) { @@ -779,6 +918,7 @@ _Py_uop_sym_get_probable_value(JitOptRef ref) case JIT_SYM_UNKNOWN_TAG: case JIT_SYM_RECORDED_TYPE_TAG: case JIT_SYM_TYPE_VERSION_TAG: + case JIT_SYM_FUNC_VERSION_TAG: case JIT_SYM_TUPLE_TAG: case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: @@ -848,6 +988,8 @@ _Py_uop_sym_truthiness(JitOptContext *ctx, JitOptRef ref) return -1; case JIT_SYM_KNOWN_VALUE_TAG: break; + case JIT_SYM_FUNC_VERSION_TAG: + return 1; case JIT_SYM_TUPLE_TAG: return sym->tuple.length != 0; case JIT_SYM_TRUTHINESS_TAG: @@ -996,6 +1138,7 @@ _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef ref) sym_set_bottom(ctx, sym); } return; + case JIT_SYM_FUNC_VERSION_TAG: case JIT_SYM_TUPLE_TAG: case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: @@ -1183,6 +1326,7 @@ _Py_uop_sym_set_recorded_value(JitOptContext *ctx, JitOptRef ref, PyObject *valu case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: case JIT_SYM_COMPACT_INT: + case JIT_SYM_FUNC_VERSION_TAG: return; } Py_UNREACHABLE(); @@ -1206,6 +1350,7 @@ _Py_uop_sym_set_recorded_gen_func(JitOptContext *ctx, JitOptRef ref, PyFunctionO case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: case JIT_SYM_COMPACT_INT: + case JIT_SYM_FUNC_VERSION_TAG: sym_set_bottom(ctx, sym); return; case JIT_SYM_BOTTOM_TAG: @@ -1302,6 +1447,7 @@ _Py_uop_sym_set_recorded_type(JitOptContext *ctx, JitOptRef ref, PyTypeObject *t case JIT_SYM_TRUTHINESS_TAG: case JIT_SYM_COMPACT_INT: case JIT_SYM_RECORDED_GEN_FUNC_TAG: + case JIT_SYM_FUNC_VERSION_TAG: return; } Py_UNREACHABLE(); @@ -1330,6 +1476,7 @@ _Py_uop_frame_new_from_symbol( frame->func = func; } assert(frame->stack_pointer != NULL); + frame->callable = callable; return frame; } @@ -1384,6 +1531,8 @@ _Py_uop_frame_new( frame->locals[i] = local; } + frame->callable = _Py_uop_sym_new_not_null(ctx); + /* Most optimizations rely on code objects being immutable (including sys._getframe modifications), * and up to date for instrumentation. */ _Py_BloomFilter_Add(ctx->dependencies, co); @@ -1958,6 +2107,15 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) TEST_PREDICATE(_Py_uop_sym_matches_type(ref_int, &PyLong_Type), "43 is not an int"); TEST_PREDICATE(_Py_uop_sym_get_const(ctx, ref_int) == val_43, "43 isn't 43"); + // Test func version's important transitions. + JitOptRef func_version = _Py_uop_sym_new_not_null(ctx); + TEST_PREDICATE(_Py_uop_sym_get_func_version(func_version) == 0, "func version should be unset"); + _Py_uop_sym_set_func_version(ctx, func_version, 172); + TEST_PREDICATE(_Py_uop_sym_get_func_version(func_version) == 172, "func version should be set"); + func_version = _Py_uop_sym_new_type(ctx, &PyFunction_Type); + _Py_uop_sym_set_func_version(ctx, func_version, 192); + TEST_PREDICATE(_Py_uop_sym_get_func_version(func_version) == 192, "func version should be set"); + // Test recorded values /* Test that recorded values aren't treated as known values*/ From 15961e81417d0c10356dd2991c5394d8bee256b7 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Wed, 18 Mar 2026 18:10:59 +0800 Subject: [PATCH 2/3] try removing buggy thing --- Python/optimizer_symbols.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index d7e14422a38439..e328e4525904c2 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -525,7 +525,6 @@ _Py_uop_sym_set_func_version(JitOptContext *ctx, JitOptRef ref, uint32_t version PyObject *val = sym->recorded_value.value; if (Py_TYPE(val) != &PyFunction_Type || ((PyFunctionObject *)sym->recorded_value.value)->func_version != version) { - Py_CLEAR(sym->recorded_value.value); sym_set_bottom(ctx, sym); return false; } From 0b05457bce5b32408de1e23170999e685f80aac7 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Wed, 18 Mar 2026 18:46:35 +0800 Subject: [PATCH 3/3] fix macOS builds --- Python/optimizer_symbols.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index e328e4525904c2..26aa6027bcd416 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -521,19 +521,20 @@ _Py_uop_sym_set_func_version(JitOptContext *ctx, JitOptRef ref, uint32_t version case JIT_SYM_TRUTHINESS_TAG: sym_set_bottom(ctx, sym); return true; - case JIT_SYM_RECORDED_VALUE_TAG: + case JIT_SYM_RECORDED_VALUE_TAG: { PyObject *val = sym->recorded_value.value; if (Py_TYPE(val) != &PyFunction_Type || ((PyFunctionObject *)sym->recorded_value.value)->func_version != version) { sym_set_bottom(ctx, sym); return false; - } + } // Promote to known value, as we have guarded/checked on it. sym->tag = JIT_SYM_KNOWN_VALUE_TAG; // New ownership. We need to NewRef here, as // it's originally kept alive by the trace buffer. sym->value.value = Py_NewRef(val); return true; + } case JIT_SYM_RECORDED_TYPE_TAG: if (sym->recorded_type.type == &PyFunction_Type) { sym->tag = JIT_SYM_FUNC_VERSION_TAG;