diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 3a5ae0a054ed3c..01c98c36f052a9 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -251,6 +251,12 @@ PyJitRef_Wrap(JitOptSymbol *sym) return (JitOptRef){.bits=(uintptr_t)sym}; } +static inline JitOptRef +PyJitRef_StripReferenceInfo(JitOptRef ref) +{ + return PyJitRef_Wrap(PyJitRef_Unwrap(ref)); +} + static inline JitOptRef PyJitRef_Borrow(JitOptRef ref) { diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index c796b0dd4b5b7e..ffd65dbb1464f8 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2501,6 +2501,22 @@ def testfunc(n): # For now... until we constant propagate it away. self.assertIn("_BINARY_OP", uops) + def test_reference_tracking_across_call_doesnt_crash(self): + + def f1(): + for _ in range(TIER2_THRESHOLD + 1): + # Choose a value that won't occur elsewhere to avoid sharing + str("value that won't occur elsewhere to avoid sharing") + + f1() + + def f2(): + for _ in range(TIER2_THRESHOLD + 1): + # Choose a value that won't occur elsewhere to avoid sharing + tuple((31, -17, 25, "won't occur elsewhere")) + + f2() + def global_identity(x): return x diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-03-15-35-34.gh-issue-138431.EUsrtA.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-03-15-35-34.gh-issue-138431.EUsrtA.rst new file mode 100644 index 00000000000000..11fc3c05d5aa7a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-03-15-35-34.gh-issue-138431.EUsrtA.rst @@ -0,0 +1 @@ +Fix a bug in the JIT optimizer when round-tripping strings and tuples. diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 781296dc7f48fc..eccbddf0546ab3 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -762,9 +762,8 @@ dummy_func(void) { } op(_RETURN_VALUE, (retval -- res)) { - // We wrap and unwrap the value to mimic PyStackRef_MakeHeapSafe - // in bytecodes.c - JitOptRef temp = PyJitRef_Wrap(PyJitRef_Unwrap(retval)); + // Mimics PyStackRef_MakeHeapSafe in the interpreter. + JitOptRef temp = PyJitRef_StripReferenceInfo(retval); DEAD(retval); SAVE_STACK(); ctx->frame->stack_pointer = stack_pointer; @@ -925,7 +924,9 @@ dummy_func(void) { op(_CALL_STR_1, (unused, unused, arg -- res)) { if (sym_matches_type(arg, &PyUnicode_Type)) { // e.g. str('foo') or str(foo) where foo is known to be a string - res = arg; + // Note: we must strip the reference information because it goes + // through str() which strips the reference information from it. + res = PyJitRef_StripReferenceInfo(arg); } else { res = sym_new_type(ctx, &PyUnicode_Type); @@ -1065,7 +1066,9 @@ dummy_func(void) { op(_CALL_TUPLE_1, (callable, null, arg -- res)) { if (sym_matches_type(arg, &PyTuple_Type)) { // e.g. tuple((1, 2)) or tuple(foo) where foo is known to be a tuple - res = arg; + // Note: we must strip the reference information because it goes + // through tuple() which strips the reference information from it. + res = PyJitRef_StripReferenceInfo(arg); } else { res = sym_new_type(ctx, &PyTuple_Type); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 14e985b42ea0ce..8617355e25f418 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1060,7 +1060,7 @@ JitOptRef retval; JitOptRef res; retval = stack_pointer[-1]; - JitOptRef temp = PyJitRef_Wrap(PyJitRef_Unwrap(retval)); + JitOptRef temp = PyJitRef_StripReferenceInfo(retval); stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); ctx->frame->stack_pointer = stack_pointer; @@ -2496,7 +2496,7 @@ JitOptRef res; arg = stack_pointer[-1]; if (sym_matches_type(arg, &PyUnicode_Type)) { - res = arg; + res = PyJitRef_StripReferenceInfo(arg); } else { res = sym_new_type(ctx, &PyUnicode_Type); @@ -2522,7 +2522,7 @@ JitOptRef res; arg = stack_pointer[-1]; if (sym_matches_type(arg, &PyTuple_Type)) { - res = arg; + res = PyJitRef_StripReferenceInfo(arg); } else { res = sym_new_type(ctx, &PyTuple_Type);