From 181d0965eca3888318933e6b719e171dc22bad47 Mon Sep 17 00:00:00 2001 From: reidenong Date: Tue, 13 Jan 2026 22:04:56 +0100 Subject: [PATCH 1/8] Add failing regression tests --- Lib/test/test_capi/test_opt.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 757e5e6ef53574..d677ee8e8c4f7d 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3534,6 +3534,40 @@ def test_is_none(n): self.assertIn("_POP_TOP_NOP", uops) self.assertLessEqual(count_ops(ex, "_POP_TOP"), 2) + def test_is_true_narrows_to_constant(self): + def f(n): + hits = 0 + for i in range(n): + v = True if i != TIER2_THRESHOLD else -1 + if v is True: + # Redundant after narrowing + if v is True: + hits += 1 + return hits + + res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + + self.assertLessEqual(count_ops(ex, "_IS_OP"), 1) + + def test_is_false_narrows_to_constant(self): + def f(n): + hits = 0 + for i in range(n): + v = False if i != TIER2_THRESHOLD else -1 + if v is False: + # Redundant after narrowing + if v is False: + hits += 1 + return hits + + res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + + self.assertLessEqual(count_ops(ex, "_IS_OP"), 1) + def test_for_iter_gen_frame(self): def f(n): for i in range(n): From e30a46b43535a1ea7f915a8b57119dd964f0ad90 Mon Sep 17 00:00:00 2001 From: reidenong Date: Thu, 15 Jan 2026 23:13:01 +0100 Subject: [PATCH 2/8] Refine failing regression tests --- Lib/test/test_capi/test_opt.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index d677ee8e8c4f7d..ab5ead37c4b16f 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3536,37 +3536,43 @@ def test_is_none(n): def test_is_true_narrows_to_constant(self): def f(n): + def return_true(): + return True + hits = 0 + v = return_true() for i in range(n): - v = True if i != TIER2_THRESHOLD else -1 if v is True: - # Redundant after narrowing - if v is True: - hits += 1 + hits += v + 1 return hits res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) - self.assertEqual(res, TIER2_THRESHOLD) + self.assertEqual(res, TIER2_THRESHOLD * 2) self.assertIsNotNone(ex) + uops = get_opnames(ex) - self.assertLessEqual(count_ops(ex, "_IS_OP"), 1) + # v + 1 should be constant folded + self.assertNotIn("_BINARY_OP", uops) def test_is_false_narrows_to_constant(self): def f(n): + def return_false(): + return False + hits = 0 + v = return_false() for i in range(n): - v = False if i != TIER2_THRESHOLD else -1 if v is False: - # Redundant after narrowing - if v is False: - hits += 1 + hits += v + 1 return hits res, ex = self._run_with_optimizer(f, TIER2_THRESHOLD) self.assertEqual(res, TIER2_THRESHOLD) self.assertIsNotNone(ex) + uops = get_opnames(ex) - self.assertLessEqual(count_ops(ex, "_IS_OP"), 1) + # v + 1 should be constant folded + self.assertNotIn("_BINARY_OP", uops) def test_for_iter_gen_frame(self): def f(n): From eba715ed82ad8f91044eea64eba915c46023c819 Mon Sep 17 00:00:00 2001 From: reidenong Date: Thu, 15 Jan 2026 23:36:25 +0100 Subject: [PATCH 3/8] Add new predicate symbol and IS_OP constant narrowing --- Include/internal/pycore_optimizer.h | 3 + Include/internal/pycore_optimizer_types.h | 15 +++++ Python/optimizer_analysis.c | 3 + Python/optimizer_bytecodes.c | 16 ++++- Python/optimizer_cases.c.h | 13 +++- Python/optimizer_symbols.c | 73 +++++++++++++++++++++++ 6 files changed, 121 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index a2d9d2d4dfc86f..f857465b781cf1 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -191,6 +191,9 @@ extern JitOptRef _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptRef value, extern bool _Py_uop_sym_is_compact_int(JitOptRef sym); extern JitOptRef _Py_uop_sym_new_compact_int(JitOptContext *ctx); extern void _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef sym); +extern JitOptRef _Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef subject_ref, JitOptRef constant_ref, JitOptPredicateKind kind, bool invert); +extern bool _Py_uop_sym_is_known_singleton(JitOptContext *ctx, JitOptRef sym); +extern void _Py_uop_sym_apply_predicate_narrowing(JitOptContext *ctx, JitOptRef sym, bool branch_is_true); extern void _Py_uop_abstractcontext_init(JitOptContext *ctx); 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 0a193268c4d618..a70fa6d6605d63 100644 --- a/Include/internal/pycore_optimizer_types.h +++ b/Include/internal/pycore_optimizer_types.h @@ -39,6 +39,7 @@ typedef enum _JitSymType { JIT_SYM_TUPLE_TAG = 8, JIT_SYM_TRUTHINESS_TAG = 9, JIT_SYM_COMPACT_INT = 10, + JIT_SYM_PREDICATE_TAG = 11, } JitSymType; typedef struct _jit_opt_known_class { @@ -71,6 +72,19 @@ typedef struct { uint16_t value; } JitOptTruthiness; +typedef enum { + JIT_PRED_IS, + // JIT_PRED_EQ, +} JitOptPredicateKind; + +typedef struct { + uint8_t tag; + uint8_t kind; + bool invert; + uint16_t subject; + uint16_t constant; +} JitOptPredicate; + typedef struct { uint8_t tag; } JitOptCompactInt; @@ -83,6 +97,7 @@ typedef union _jit_opt_symbol { JitOptTuple tuple; JitOptTruthiness truthiness; JitOptCompactInt compact; + JitOptPredicate predicate; } JitOptSymbol; // This mimics the _PyStackRef API diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index e855df4977acf8..14cfa5500325d8 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -186,6 +186,9 @@ incorrect_keys(PyObject *obj, uint32_t version) #define sym_is_compact_int _Py_uop_sym_is_compact_int #define sym_new_compact_int _Py_uop_sym_new_compact_int #define sym_new_truthiness _Py_uop_sym_new_truthiness +#define sym_new_predicate _Py_uop_sym_new_predicate +#define sym_is_known_singleton _Py_uop_sym_is_known_singleton +#define sym_apply_predicate_narrowing _Py_uop_sym_apply_predicate_narrowing #define JUMP_TO_LABEL(label) goto label; diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 1a64810b50a3a4..8b4a2ef7de5f29 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -38,6 +38,9 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define sym_new_compact_int _Py_uop_sym_new_compact_int #define sym_is_compact_int _Py_uop_sym_is_compact_int #define sym_new_truthiness _Py_uop_sym_new_truthiness +#define sym_new_predicate _Py_uop_sym_new_predicate +#define sym_is_known_singleton _Py_uop_sym_is_known_singleton +#define sym_apply_predicate_narrowing _Py_uop_sym_apply_predicate_narrowing extern int optimize_to_bool( @@ -533,7 +536,16 @@ dummy_func(void) { } op(_IS_OP, (left, right -- b, l, r)) { - b = sym_new_type(ctx, &PyBool_Type); + bool invert = (oparg != 0); + if (sym_is_known_singleton(ctx, left)) { + b = sym_new_predicate(ctx, right, left, JIT_PRED_IS ,invert); + } + else if (sym_is_known_singleton(ctx, right)) { + b = sym_new_predicate(ctx, left, right, JIT_PRED_IS, invert); + } + else { + b = sym_new_type(ctx, &PyBool_Type); + } l = left; r = right; } @@ -1142,6 +1154,7 @@ dummy_func(void) { assert(value != NULL); eliminate_pop_guard(this_instr, value != Py_True); } + sym_apply_predicate_narrowing(ctx, flag, true); sym_set_const(flag, Py_True); } @@ -1187,6 +1200,7 @@ dummy_func(void) { assert(value != NULL); eliminate_pop_guard(this_instr, value != Py_False); } + sym_apply_predicate_narrowing(ctx, flag, false); sym_set_const(flag, Py_False); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index f3bc7213fcce3f..bc448d5a5b80fa 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2293,7 +2293,16 @@ JitOptRef r; right = stack_pointer[-1]; left = stack_pointer[-2]; - b = sym_new_type(ctx, &PyBool_Type); + bool invert = (oparg != 0); + if (sym_is_known_singleton(ctx, left)) { + b = sym_new_predicate(ctx, right, left, JIT_PRED_IS ,invert); + } + else if (sym_is_known_singleton(ctx, right)) { + b = sym_new_predicate(ctx, left, right, JIT_PRED_IS, invert); + } + else { + b = sym_new_type(ctx, &PyBool_Type); + } l = left; r = right; CHECK_STACK_BOUNDS(1); @@ -3720,6 +3729,7 @@ assert(value != NULL); eliminate_pop_guard(this_instr, value != Py_True); } + sym_apply_predicate_narrowing(ctx, flag, true); sym_set_const(flag, Py_True); CHECK_STACK_BOUNDS(-1); stack_pointer += -1; @@ -3735,6 +3745,7 @@ assert(value != NULL); eliminate_pop_guard(this_instr, value != Py_False); } + sym_apply_predicate_narrowing(ctx, flag, false); sym_set_const(flag, Py_False); CHECK_STACK_BOUNDS(-1); stack_pointer += -1; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index c6b54b9b58b795..100cc2fa448d06 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -258,6 +258,7 @@ _Py_uop_sym_set_type(JitOptContext *ctx, JitOptRef ref, PyTypeObject *typ) sym->cls.version = 0; sym->cls.type = typ; return; + case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: if (typ != &PyBool_Type) { sym_set_bottom(ctx, sym); @@ -319,6 +320,7 @@ _Py_uop_sym_set_type_version(JitOptContext *ctx, JitOptRef ref, unsigned int ver sym->tag = JIT_SYM_TYPE_VERSION_TAG; sym->version.version = version; return true; + case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: if (version != PyBool_Type.tp_version_tag) { sym_set_bottom(ctx, sym); @@ -385,6 +387,16 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef ref, PyObject *const_val) case JIT_SYM_UNKNOWN_TAG: make_const(sym, const_val); return; + case JIT_SYM_PREDICATE_TAG: + if (!PyBool_Check(const_val) || + (_Py_uop_sym_is_const(ctx, ref) && + _Py_uop_sym_get_const(ctx, ref) != const_val)) + { + sym_set_bottom(ctx, sym); + return; + } + make_const(sym, const_val); + return; case JIT_SYM_TRUTHINESS_TAG: if (!PyBool_Check(const_val) || (_Py_uop_sym_is_const(ctx, ref) && @@ -538,6 +550,7 @@ _Py_uop_sym_get_type(JitOptRef ref) return _PyType_LookupByVersion(sym->version.version); case JIT_SYM_TUPLE_TAG: return &PyTuple_Type; + case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: return &PyBool_Type; case JIT_SYM_COMPACT_INT: @@ -566,6 +579,7 @@ _Py_uop_sym_get_type_version(JitOptRef ref) return Py_TYPE(sym->value.value)->tp_version_tag; case JIT_SYM_TUPLE_TAG: return PyTuple_Type.tp_version_tag; + case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: return PyBool_Type.tp_version_tag; case JIT_SYM_COMPACT_INT: @@ -759,6 +773,7 @@ _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef ref) } return; case JIT_SYM_TUPLE_TAG: + case JIT_SYM_PREDICATE_TAG: case JIT_SYM_TRUTHINESS_TAG: sym_set_bottom(ctx, sym); return; @@ -772,6 +787,64 @@ _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef ref) } } +JitOptRef +_Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef subject_ref, JitOptRef constant_ref, JitOptPredicateKind kind, bool invert) +{ + assert(_Py_uop_sym_is_const(ctx, constant_ref)); + + JitOptSymbol *subject = PyJitRef_Unwrap(subject_ref); + JitOptSymbol *constant = PyJitRef_Unwrap(constant_ref); + + JitOptSymbol *res = sym_new(ctx); + if (res == NULL) { + return out_of_space_ref(ctx); + } + + res->tag = JIT_SYM_PREDICATE_TAG; + res->predicate.invert = invert; + res->predicate.kind = kind; + res->predicate.subject = (uint16_t)(subject - allocation_base(ctx)); + res->predicate.constant = (uint16_t)(constant - allocation_base(ctx)); + + return PyJitRef_Wrap(res); +} + +bool +_Py_uop_sym_is_known_singleton(JitOptContext *ctx, JitOptRef ref) +{ + if (_Py_uop_sym_is_safe_const(ctx, ref)) { + PyObject *value = _Py_uop_sym_get_const(ctx, ref); + return value == Py_None || value == Py_True || value == Py_False; + } + return false; +} + +void +_Py_uop_sym_apply_predicate_narrowing(JitOptContext *ctx, JitOptRef ref, bool branch_is_true) +{ + JitOptSymbol *sym = PyJitRef_Unwrap(ref); + if (sym->tag != JIT_SYM_PREDICATE_TAG) { + return; + } + + JitOptPredicate pred = sym->predicate; + bool narrow = (branch_is_true && !pred.invert) || (!branch_is_true && pred.invert); + if (!narrow) { + return; + } + + if (pred.kind == JIT_PRED_IS) { + JitOptRef subject_ref = PyJitRef_Wrap(allocation_base(ctx) + pred.subject); + JitOptRef constant_ref = PyJitRef_Wrap(allocation_base(ctx) + pred.constant); + PyObject *const_val = _Py_uop_sym_get_const(ctx, constant_ref); + if (const_val == NULL) { + return; + } + _Py_uop_sym_set_const(ctx, subject_ref, const_val); + assert(_Py_uop_sym_is_safe_const(ctx, subject_ref)); + } +} + JitOptRef _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptRef ref, bool truthy) { From 8834e120e9427a0599fb16f6e6be751b3463a2e2 Mon Sep 17 00:00:00 2001 From: reidenong Date: Fri, 16 Jan 2026 16:14:47 +0100 Subject: [PATCH 4/8] Remove comment --- Include/internal/pycore_optimizer_types.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Include/internal/pycore_optimizer_types.h b/Include/internal/pycore_optimizer_types.h index a70fa6d6605d63..e3925faf6f8310 100644 --- a/Include/internal/pycore_optimizer_types.h +++ b/Include/internal/pycore_optimizer_types.h @@ -74,7 +74,6 @@ typedef struct { typedef enum { JIT_PRED_IS, - // JIT_PRED_EQ, } JitOptPredicateKind; typedef struct { From d828f8c2c79f881e86d9d59f65c74af20be0f25a Mon Sep 17 00:00:00 2001 From: reidenong Date: Fri, 16 Jan 2026 21:27:51 +0100 Subject: [PATCH 5/8] Refactor predicate optimizer symbol - Delay analysis until predicate resolves - Generalize predicate to lhs, rhs - Expand to accept all safe constants (not just singletons) --- Include/internal/pycore_optimizer.h | 3 +- Include/internal/pycore_optimizer_types.h | 4 +-- Python/optimizer_analysis.c | 1 - Python/optimizer_bytecodes.c | 12 +------- Python/optimizer_cases.c.h | 11 +------ Python/optimizer_symbols.c | 37 ++++++++++------------- 6 files changed, 21 insertions(+), 47 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index f857465b781cf1..7c7a472635078e 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -191,8 +191,7 @@ extern JitOptRef _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptRef value, extern bool _Py_uop_sym_is_compact_int(JitOptRef sym); extern JitOptRef _Py_uop_sym_new_compact_int(JitOptContext *ctx); extern void _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef sym); -extern JitOptRef _Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef subject_ref, JitOptRef constant_ref, JitOptPredicateKind kind, bool invert); -extern bool _Py_uop_sym_is_known_singleton(JitOptContext *ctx, JitOptRef sym); +extern JitOptRef _Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef lhs_ref, JitOptRef rhs_ref, JitOptPredicateKind kind, bool invert); extern void _Py_uop_sym_apply_predicate_narrowing(JitOptContext *ctx, JitOptRef sym, bool branch_is_true); extern void _Py_uop_abstractcontext_init(JitOptContext *ctx); diff --git a/Include/internal/pycore_optimizer_types.h b/Include/internal/pycore_optimizer_types.h index e3925faf6f8310..e953ab9b6aff37 100644 --- a/Include/internal/pycore_optimizer_types.h +++ b/Include/internal/pycore_optimizer_types.h @@ -80,8 +80,8 @@ typedef struct { uint8_t tag; uint8_t kind; bool invert; - uint16_t subject; - uint16_t constant; + uint16_t lhs; + uint16_t rhs; } JitOptPredicate; typedef struct { diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index 14cfa5500325d8..5c4506b3526306 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -187,7 +187,6 @@ incorrect_keys(PyObject *obj, uint32_t version) #define sym_new_compact_int _Py_uop_sym_new_compact_int #define sym_new_truthiness _Py_uop_sym_new_truthiness #define sym_new_predicate _Py_uop_sym_new_predicate -#define sym_is_known_singleton _Py_uop_sym_is_known_singleton #define sym_apply_predicate_narrowing _Py_uop_sym_apply_predicate_narrowing #define JUMP_TO_LABEL(label) goto label; diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 8b4a2ef7de5f29..d5f25a2e5fc3ba 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -39,7 +39,6 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame; #define sym_is_compact_int _Py_uop_sym_is_compact_int #define sym_new_truthiness _Py_uop_sym_new_truthiness #define sym_new_predicate _Py_uop_sym_new_predicate -#define sym_is_known_singleton _Py_uop_sym_is_known_singleton #define sym_apply_predicate_narrowing _Py_uop_sym_apply_predicate_narrowing extern int @@ -536,16 +535,7 @@ dummy_func(void) { } op(_IS_OP, (left, right -- b, l, r)) { - bool invert = (oparg != 0); - if (sym_is_known_singleton(ctx, left)) { - b = sym_new_predicate(ctx, right, left, JIT_PRED_IS ,invert); - } - else if (sym_is_known_singleton(ctx, right)) { - b = sym_new_predicate(ctx, left, right, JIT_PRED_IS, invert); - } - else { - b = sym_new_type(ctx, &PyBool_Type); - } + b = sym_new_predicate(ctx, left, right, JIT_PRED_IS, oparg != 0); l = left; r = right; } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index bc448d5a5b80fa..280787d84e2009 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2293,16 +2293,7 @@ JitOptRef r; right = stack_pointer[-1]; left = stack_pointer[-2]; - bool invert = (oparg != 0); - if (sym_is_known_singleton(ctx, left)) { - b = sym_new_predicate(ctx, right, left, JIT_PRED_IS ,invert); - } - else if (sym_is_known_singleton(ctx, right)) { - b = sym_new_predicate(ctx, left, right, JIT_PRED_IS, invert); - } - else { - b = sym_new_type(ctx, &PyBool_Type); - } + b = sym_new_predicate(ctx, left, right, JIT_PRED_IS, oparg != 0); l = left; r = right; CHECK_STACK_BOUNDS(1); diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 100cc2fa448d06..44fdb97acf3550 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -788,12 +788,10 @@ _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef ref) } JitOptRef -_Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef subject_ref, JitOptRef constant_ref, JitOptPredicateKind kind, bool invert) +_Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef lhs_ref, JitOptRef rhs_ref, JitOptPredicateKind kind, bool invert) { - assert(_Py_uop_sym_is_const(ctx, constant_ref)); - - JitOptSymbol *subject = PyJitRef_Unwrap(subject_ref); - JitOptSymbol *constant = PyJitRef_Unwrap(constant_ref); + JitOptSymbol *lhs = PyJitRef_Unwrap(lhs_ref); + JitOptSymbol *rhs = PyJitRef_Unwrap(rhs_ref); JitOptSymbol *res = sym_new(ctx); if (res == NULL) { @@ -803,22 +801,12 @@ _Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef subject_ref, JitOptRef c res->tag = JIT_SYM_PREDICATE_TAG; res->predicate.invert = invert; res->predicate.kind = kind; - res->predicate.subject = (uint16_t)(subject - allocation_base(ctx)); - res->predicate.constant = (uint16_t)(constant - allocation_base(ctx)); + res->predicate.lhs = (uint16_t)(lhs - allocation_base(ctx)); + res->predicate.rhs = (uint16_t)(rhs - allocation_base(ctx)); return PyJitRef_Wrap(res); } -bool -_Py_uop_sym_is_known_singleton(JitOptContext *ctx, JitOptRef ref) -{ - if (_Py_uop_sym_is_safe_const(ctx, ref)) { - PyObject *value = _Py_uop_sym_get_const(ctx, ref); - return value == Py_None || value == Py_True || value == Py_False; - } - return false; -} - void _Py_uop_sym_apply_predicate_narrowing(JitOptContext *ctx, JitOptRef ref, bool branch_is_true) { @@ -833,10 +821,17 @@ _Py_uop_sym_apply_predicate_narrowing(JitOptContext *ctx, JitOptRef ref, bool br return; } - if (pred.kind == JIT_PRED_IS) { - JitOptRef subject_ref = PyJitRef_Wrap(allocation_base(ctx) + pred.subject); - JitOptRef constant_ref = PyJitRef_Wrap(allocation_base(ctx) + pred.constant); - PyObject *const_val = _Py_uop_sym_get_const(ctx, constant_ref); + JitOptRef lhs_ref = PyJitRef_Wrap(allocation_base(ctx) + pred.lhs); + JitOptRef rhs_ref = PyJitRef_Wrap(allocation_base(ctx) + pred.rhs); + + bool lhs_is_const = _Py_uop_sym_is_safe_const(ctx, lhs_ref); + bool rhs_is_const = _Py_uop_sym_is_safe_const(ctx, rhs_ref); + + if (pred.kind == JIT_PRED_IS && (lhs_is_const || rhs_is_const)) { + JitOptRef subject_ref = lhs_is_const ? rhs_ref : lhs_ref; + JitOptRef const_ref = lhs_is_const ? lhs_ref : rhs_ref; + + PyObject *const_val = _Py_uop_sym_get_const(ctx, const_ref); if (const_val == NULL) { return; } From 24ac326c4442ceaa81dda1f6247dc30f48c843b8 Mon Sep 17 00:00:00 2001 From: reidenong Date: Fri, 16 Jan 2026 21:47:55 +0100 Subject: [PATCH 6/8] Remove unnecessary checks in sym_set_const --- Python/optimizer_symbols.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 44fdb97acf3550..629559d11109e4 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -388,10 +388,7 @@ _Py_uop_sym_set_const(JitOptContext *ctx, JitOptRef ref, PyObject *const_val) make_const(sym, const_val); return; case JIT_SYM_PREDICATE_TAG: - if (!PyBool_Check(const_val) || - (_Py_uop_sym_is_const(ctx, ref) && - _Py_uop_sym_get_const(ctx, ref) != const_val)) - { + if (!PyBool_Check(const_val)) { sym_set_bottom(ctx, sym); return; } From 33b892054d74d6f91af07679cc8465e1b6ffed76 Mon Sep 17 00:00:00 2001 From: reidenong Date: Sat, 17 Jan 2026 21:51:34 +0100 Subject: [PATCH 7/8] Add predicate symbol tests --- Python/optimizer_symbols.c | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 35d107a116c2b4..361c70d1214363 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -1224,6 +1224,70 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) TEST_PREDICATE(_Py_uop_sym_is_const(ctx, value) == true, "value is not constant"); TEST_PREDICATE(_Py_uop_sym_get_const(ctx, value) == Py_True, "value is not True"); + // Resolving predicate result to True should narrow subject to True + JitOptRef subject = _Py_uop_sym_new_unknown(ctx); + JitOptRef const_true = _Py_uop_sym_new_const(ctx, Py_True); + if (PyJitRef_IsNull(subject) || PyJitRef_IsNull(const_true)) { + goto fail; + } + ref = _Py_uop_sym_new_predicate(ctx, subject, const_true, JIT_PRED_IS, false); + if (PyJitRef_IsNull(ref)) { + goto fail; + } + _Py_uop_sym_apply_predicate_narrowing(ctx, ref, true); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, subject), "predicate narrowing did not const-narrow subject"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, subject) == Py_True, "predicate narrowing did not narrow subject to True"); + + // Resolving predicate result to False should not narrow subject + subject = _Py_uop_sym_new_unknown(ctx); + if (PyJitRef_IsNull(subject)) { + goto fail; + } + ref = _Py_uop_sym_new_predicate(ctx, subject, const_true, JIT_PRED_IS, false); + if (PyJitRef_IsNull(ref)) { + goto fail; + } + _Py_uop_sym_apply_predicate_narrowing(ctx, ref, false); + TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, subject), "predicate narrowing incorrectly narrowed subject"); + + // Resolving inverted predicate to False should narrow subject to True + subject = _Py_uop_sym_new_unknown(ctx); + if (PyJitRef_IsNull(subject)) { + goto fail; + } + ref = _Py_uop_sym_new_predicate(ctx, subject, const_true, JIT_PRED_IS, true); + if (PyJitRef_IsNull(ref)) { + goto fail; + } + _Py_uop_sym_apply_predicate_narrowing(ctx, ref, false); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, subject), "predicate narrowing (inverted) did not const-narrow subject"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, subject) == Py_True, "predicate narrowing (inverted) did not narrow subject to True"); + + // Resolving inverted predicate to True should not narrow subject + subject = _Py_uop_sym_new_unknown(ctx); + if (PyJitRef_IsNull(subject)) { + goto fail; + } + ref = _Py_uop_sym_new_predicate(ctx, subject, const_true, JIT_PRED_IS, true); + if (PyJitRef_IsNull(ref)) { + goto fail; + } + _Py_uop_sym_apply_predicate_narrowing(ctx, ref, true); + TEST_PREDICATE(!_Py_uop_sym_is_const(ctx, subject), "predicate narrowing incorrectly narrowed subject (inverted/true)"); + + // Test narrowing subject to None + subject = _Py_uop_sym_new_unknown(ctx); + JitOptRef const_none = _Py_uop_sym_new_const(ctx, Py_None); + if (PyJitRef_IsNull(subject) || PyJitRef_IsNull(const_none)) { + goto fail; + } + ref = _Py_uop_sym_new_predicate(ctx, subject, const_none, JIT_PRED_IS, false); + if (PyJitRef_IsNull(ref)) { + goto fail; + } + _Py_uop_sym_apply_predicate_narrowing(ctx, ref, true); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, subject), "predicate narrowing did not const-narrow subject (None)"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, subject) == Py_None, "predicate narrowing did not narrow subject to None"); val_big = PyNumber_Lshift(_PyLong_GetOne(), PyLong_FromLong(66)); if (val_big == NULL) { From 156b0df1f1375bd99fa06650c85ca7e23ab817b6 Mon Sep 17 00:00:00 2001 From: reidenong Date: Sun, 18 Jan 2026 23:59:58 +0800 Subject: [PATCH 8/8] Add optimizer symbol unit tests --- Python/optimizer_symbols.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 361c70d1214363..90399870393242 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -1289,6 +1289,21 @@ _Py_uop_symbols_test(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(ignored)) TEST_PREDICATE(_Py_uop_sym_is_const(ctx, subject), "predicate narrowing did not const-narrow subject (None)"); TEST_PREDICATE(_Py_uop_sym_get_const(ctx, subject) == Py_None, "predicate narrowing did not narrow subject to None"); + // Test narrowing subject to numerical constant + subject = _Py_uop_sym_new_unknown(ctx); + PyObject *one_obj = PyLong_FromLong(1); + JitOptRef const_one = _Py_uop_sym_new_const(ctx, one_obj); + if (PyJitRef_IsNull(subject) || PyJitRef_IsNull(const_one)) { + goto fail; + } + ref = _Py_uop_sym_new_predicate(ctx, subject, const_one, JIT_PRED_IS, false); + if (PyJitRef_IsNull(ref)) { + goto fail; + } + _Py_uop_sym_apply_predicate_narrowing(ctx, ref, true); + TEST_PREDICATE(_Py_uop_sym_is_const(ctx, subject), "predicate narrowing did not const-narrow subject (1)"); + TEST_PREDICATE(_Py_uop_sym_get_const(ctx, subject) == one_obj, "predicate narrowing did not narrow subject to 1"); + val_big = PyNumber_Lshift(_PyLong_GetOne(), PyLong_FromLong(66)); if (val_big == NULL) { goto fail;