]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-149049: Fix jit binary op stack underflow (GH-149076)
authorPieter Eendebak <pieter.eendebak@gmail.com>
Thu, 30 Apr 2026 09:36:04 +0000 (11:36 +0200)
committerGitHub <noreply@github.com>
Thu, 30 Apr 2026 09:36:04 +0000 (10:36 +0100)
Lib/test/test_capi/test_opt.py
Misc/NEWS.d/next/Core_and_Builtins/2026-04-28-21-19-21.gh-issue-149049.98u2Ib.rst [new file with mode: 0644]
Python/optimizer_bytecodes.c
Python/optimizer_cases.c.h

index 79643587a600027cf18df4ff462b5ac77a9a513e..7118dfeed9faeed34b6d5b5534b58875b68ab8ec 100644 (file)
@@ -3919,6 +3919,38 @@ class TestUopsOptimization(unittest.TestCase):
         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 (file)
index 0000000..4c8f7e0
--- /dev/null
@@ -0,0 +1 @@
+Fix stack underflow for ``BINARY_OP`` in tier 2.
index ae9e19341441ea8f488f88acf7658dde02ffc3f6..15d4d0bc1818f56a3d906edc899085be75cc30eb 100644 (file)
@@ -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)) {
index d48f38a95f7b165293d6f9f3a35511b9bee6c028..b09aca910fc1eeba07b5c3af79a88f2aa41e3713 100644 (file)
                            || 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) {
             }
             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;
                 }
             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;