diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 79643587a60002..7118dfeed9faee 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3919,6 +3919,38 @@ def testfunc(args): expected = TIER2_THRESHOLD * (5.0 / Fraction(4)) self.assertAlmostEqual(res, float(expected)) + def test_float_truediv_partial_float_no_stack_underflow(self): + # gh-149049: a speculative _GUARD_*_FLOAT for a partially-float + # truediv/remainder must not drop the original _BINARY_OP. + def truediv(args): + n, = args + nan = float("nan") + def victim(a=0, b=nan, c=2): + return (a + b) / c + for _ in range(n): + victim() + + def remainder(args): + n, = args + nan = float("nan") + def victim(a=0, b=nan, c=2): + return (a + b) % c + for _ in range(n): + victim() + + for testfunc in (truediv, remainder): + with self.subTest(op=testfunc.__name__): + # Iterations must be high enough that the buggy trace + # is not only built but executed (where it underflows). + _, ex = self._run_with_optimizer( + testfunc, (TIER2_THRESHOLD * 10,)) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + self.assertTrue( + "_GUARD_TOS_FLOAT" in uops or "_GUARD_NOS_FLOAT" in uops, + uops, + ) + def test_int_add_inplace_unique_lhs(self): # a * b produces a unique compact int; adding c reuses it in place def testfunc(args): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-21-19-21.gh-issue-149049.98u2Ib.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-21-19-21.gh-issue-149049.98u2Ib.rst new file mode 100644 index 00000000000000..4c8f7e08a44859 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-21-19-21.gh-issue-149049.98u2Ib.rst @@ -0,0 +1 @@ +Fix stack underflow for ``BINARY_OP`` in tier 2. diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index ae9e19341441ea..15d4d0bc1818f5 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -293,6 +293,7 @@ dummy_func(void) { || oparg == NB_INPLACE_TRUE_DIVIDE); bool is_remainder = (oparg == NB_REMAINDER || oparg == NB_INPLACE_REMAINDER); + int emit_op = _BINARY_OP; // Promote probable-float operands to known floats via speculative // guards. _RECORD_TOS_TYPE / _RECORD_NOS_TYPE in the BINARY_OP macro // record the observed operand type during tracing, which @@ -318,17 +319,17 @@ dummy_func(void) { } if (is_truediv && lhs_float && rhs_float) { if (PyJitRef_IsUnique(lhs)) { - ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE, 0, 0); + emit_op = _BINARY_OP_TRUEDIV_FLOAT_INPLACE; l = sym_new_null(ctx); r = rhs; } else if (PyJitRef_IsUnique(rhs)) { - ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT, 0, 0); + emit_op = _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT; l = lhs; r = sym_new_null(ctx); } else { - ADD_OP(_BINARY_OP_TRUEDIV_FLOAT, 0, 0); + emit_op = _BINARY_OP_TRUEDIV_FLOAT; l = lhs; r = rhs; } @@ -382,6 +383,7 @@ dummy_func(void) { else { res = PyJitRef_MakeUnique(sym_new_type(ctx, &PyFloat_Type)); } + ADD_OP(emit_op, oparg, 0); } op(_BINARY_OP_ADD_INT, (left, right -- res, l, r)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index d48f38a95f7b16..b09aca910fc1ee 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -5242,6 +5242,7 @@ || oparg == NB_INPLACE_TRUE_DIVIDE); bool is_remainder = (oparg == NB_REMAINDER || oparg == NB_INPLACE_REMAINDER); + int emit_op = _BINARY_OP; if (is_truediv || is_remainder) { if (!sym_has_type(rhs) && sym_get_probable_type(rhs) == &PyFloat_Type) { @@ -5258,17 +5259,17 @@ } if (is_truediv && lhs_float && rhs_float) { if (PyJitRef_IsUnique(lhs)) { - ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE, 0, 0); + emit_op = _BINARY_OP_TRUEDIV_FLOAT_INPLACE; l = sym_new_null(ctx); r = rhs; } else if (PyJitRef_IsUnique(rhs)) { - ADD_OP(_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT, 0, 0); + emit_op = _BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT; l = lhs; r = sym_new_null(ctx); } else { - ADD_OP(_BINARY_OP_TRUEDIV_FLOAT, 0, 0); + emit_op = _BINARY_OP_TRUEDIV_FLOAT; l = lhs; r = rhs; } @@ -5304,6 +5305,7 @@ else { res = PyJitRef_MakeUnique(sym_new_type(ctx, &PyFloat_Type)); } + ADD_OP(emit_op, oparg, 0); CHECK_STACK_BOUNDS(1); stack_pointer[-2] = res; stack_pointer[-1] = l;