https://github.com/python/cpython/commit/89e6607e051ad4ffae8878cee1e0e6d72c618568
commit: 89e6607e051ad4ffae8878cee1e0e6d72c618568
branch: main
author: Hai Zhu <[email protected]>
committer: markshannon <[email protected]>
date: 2026-02-02T17:12:01Z
summary:

gh-139109: Replace `_CHECK_STACK_SPACE` with `_CHECK_STACK_SPACE_OPERAND` in 
JIT optiimizer (GH-144394)

files:
M Lib/test/test_capi/test_opt.py
M Python/bytecodes.c
M Python/executor_cases.c.h
M Python/optimizer_bytecodes.c
M Python/optimizer_cases.c.h

diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index a379d1be2f9bd3..437cc340fc90e3 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -1022,7 +1022,6 @@ def return_hello():
         # Constant narrowing allows constant folding for second comparison
         self.assertLessEqual(count_ops(ex, "_COMPARE_OP_STR"), 1)
 
-    @unittest.skip("gh-139109 WIP")
     def test_combine_stack_space_checks_sequential(self):
         def dummy12(x):
             return x - 1
@@ -1046,12 +1045,14 @@ def testfunc(n):
         self.assertEqual(uop_names.count("_PUSH_FRAME"), 2)
         self.assertEqual(uop_names.count("_RETURN_VALUE"), 2)
         self.assertEqual(uop_names.count("_CHECK_STACK_SPACE"), 0)
-        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 1)
-        # sequential calls: max(12, 13) == 13
-        largest_stack = _testinternalcapi.get_co_framesize(dummy13.__code__)
-        self.assertIn(("_CHECK_STACK_SPACE_OPERAND", largest_stack), 
uops_and_operands)
+        # Each call gets its own _CHECK_STACK_SPACE_OPERAND
+        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 2)
+        # Each _CHECK_STACK_SPACE_OPERAND has the framesize of its function
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy12.__code__)), 
uops_and_operands)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy13.__code__)), 
uops_and_operands)
 
-    @unittest.skip("gh-139109 WIP")
     def test_combine_stack_space_checks_nested(self):
         def dummy12(x):
             return x + 3
@@ -1074,15 +1075,12 @@ def testfunc(n):
         self.assertEqual(uop_names.count("_PUSH_FRAME"), 2)
         self.assertEqual(uop_names.count("_RETURN_VALUE"), 2)
         self.assertEqual(uop_names.count("_CHECK_STACK_SPACE"), 0)
-        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 1)
-        # nested calls: 15 + 12 == 27
-        largest_stack = (
-            _testinternalcapi.get_co_framesize(dummy15.__code__) +
-            _testinternalcapi.get_co_framesize(dummy12.__code__)
-        )
-        self.assertIn(("_CHECK_STACK_SPACE_OPERAND", largest_stack), 
uops_and_operands)
+        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 2)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy15.__code__)), 
uops_and_operands)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy12.__code__)), 
uops_and_operands)
 
-    @unittest.skip("gh-139109 WIP")
     def test_combine_stack_space_checks_several_calls(self):
         def dummy12(x):
             return x + 3
@@ -1110,15 +1108,14 @@ def testfunc(n):
         self.assertEqual(uop_names.count("_PUSH_FRAME"), 4)
         self.assertEqual(uop_names.count("_RETURN_VALUE"), 4)
         self.assertEqual(uop_names.count("_CHECK_STACK_SPACE"), 0)
-        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 1)
-        # max(12, 18 + max(12, 13)) == 31
-        largest_stack = (
-            _testinternalcapi.get_co_framesize(dummy18.__code__) +
-            _testinternalcapi.get_co_framesize(dummy13.__code__)
-        )
-        self.assertIn(("_CHECK_STACK_SPACE_OPERAND", largest_stack), 
uops_and_operands)
+        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 4)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy12.__code__)), 
uops_and_operands)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy13.__code__)), 
uops_and_operands)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy18.__code__)), 
uops_and_operands)
 
-    @unittest.skip("gh-139109 WIP")
     def test_combine_stack_space_checks_several_calls_different_order(self):
         # same as `several_calls` but with top-level calls reversed
         def dummy12(x):
@@ -1147,15 +1144,15 @@ def testfunc(n):
         self.assertEqual(uop_names.count("_PUSH_FRAME"), 4)
         self.assertEqual(uop_names.count("_RETURN_VALUE"), 4)
         self.assertEqual(uop_names.count("_CHECK_STACK_SPACE"), 0)
-        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 1)
-        # max(18 + max(12, 13), 12) == 31
-        largest_stack = (
-            _testinternalcapi.get_co_framesize(dummy18.__code__) +
-            _testinternalcapi.get_co_framesize(dummy13.__code__)
-        )
-        self.assertIn(("_CHECK_STACK_SPACE_OPERAND", largest_stack), 
uops_and_operands)
-
-    @unittest.skip("gh-139109 WIP")
+        self.assertEqual(uop_names.count("_CHECK_STACK_SPACE_OPERAND"), 4)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy12.__code__)), 
uops_and_operands)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy13.__code__)), 
uops_and_operands)
+        self.assertIn(("_CHECK_STACK_SPACE_OPERAND",
+                       _testinternalcapi.get_co_framesize(dummy18.__code__)), 
uops_and_operands)
+
+    @unittest.skip("reopen when we combine multiple stack space checks into 
one")
     def test_combine_stack_space_complex(self):
         def dummy0(x):
             return x
@@ -1205,7 +1202,7 @@ def testfunc(n):
             ("_CHECK_STACK_SPACE_OPERAND", largest_stack), uops_and_operands
         )
 
-    @unittest.skip("gh-139109 WIP")
+    @unittest.skip("reopen when we combine multiple stack space checks into 
one")
     def test_combine_stack_space_checks_large_framesize(self):
         # Create a function with a large framesize. This ensures 
_CHECK_STACK_SPACE is
         # actually doing its job. Note that the resulting trace hits
@@ -1267,7 +1264,7 @@ def testfunc(n):
             ("_CHECK_STACK_SPACE_OPERAND", largest_stack), uops_and_operands
         )
 
-    @unittest.skip("gh-139109 WIP")
+    @unittest.skip("reopen when we combine multiple stack space checks into 
one")
     def test_combine_stack_space_checks_recursion(self):
         def dummy15(x):
             while x > 0:
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index 9e41382240ea5d..a990ab28577c73 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -5372,7 +5372,6 @@ dummy_func(
         tier2 op(_CHECK_STACK_SPACE_OPERAND, (framesize/2 --)) {
             assert(framesize <= INT_MAX);
             DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, framesize));
-            DEOPT_IF(tstate->py_recursion_remaining <= 1);
         }
 
         op(_SAVE_RETURN_OFFSET, (--)) {
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index 339789b61d86aa..9c82f1acdef493 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -18265,11 +18265,6 @@
                 SET_CURRENT_CACHED_VALUES(0);
                 JUMP_TO_JUMP_TARGET();
             }
-            if (tstate->py_recursion_remaining <= 1) {
-                UOP_STAT_INC(uopcode, miss);
-                SET_CURRENT_CACHED_VALUES(0);
-                JUMP_TO_JUMP_TARGET();
-            }
             SET_CURRENT_CACHED_VALUES(0);
             assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
             break;
@@ -18287,12 +18282,6 @@
                 SET_CURRENT_CACHED_VALUES(1);
                 JUMP_TO_JUMP_TARGET();
             }
-            if (tstate->py_recursion_remaining <= 1) {
-                UOP_STAT_INC(uopcode, miss);
-                _tos_cache0 = _stack_item_0;
-                SET_CURRENT_CACHED_VALUES(1);
-                JUMP_TO_JUMP_TARGET();
-            }
             _tos_cache0 = _stack_item_0;
             SET_CURRENT_CACHED_VALUES(1);
             assert(WITHIN_STACK_BOUNDS_IGNORING_CACHE());
@@ -18313,13 +18302,6 @@
                 SET_CURRENT_CACHED_VALUES(2);
                 JUMP_TO_JUMP_TARGET();
             }
-            if (tstate->py_recursion_remaining <= 1) {
-                UOP_STAT_INC(uopcode, miss);
-                _tos_cache1 = _stack_item_1;
-                _tos_cache0 = _stack_item_0;
-                SET_CURRENT_CACHED_VALUES(2);
-                JUMP_TO_JUMP_TARGET();
-            }
             _tos_cache1 = _stack_item_1;
             _tos_cache0 = _stack_item_0;
             SET_CURRENT_CACHED_VALUES(2);
@@ -18343,14 +18325,6 @@
                 SET_CURRENT_CACHED_VALUES(3);
                 JUMP_TO_JUMP_TARGET();
             }
-            if (tstate->py_recursion_remaining <= 1) {
-                UOP_STAT_INC(uopcode, miss);
-                _tos_cache2 = _stack_item_2;
-                _tos_cache1 = _stack_item_1;
-                _tos_cache0 = _stack_item_0;
-                SET_CURRENT_CACHED_VALUES(3);
-                JUMP_TO_JUMP_TARGET();
-            }
             _tos_cache2 = _stack_item_2;
             _tos_cache1 = _stack_item_1;
             _tos_cache0 = _stack_item_0;
diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c
index 1f6a64026d8e15..89c6707160326c 100644
--- a/Python/optimizer_bytecodes.c
+++ b/Python/optimizer_bytecodes.c
@@ -1068,13 +1068,17 @@ dummy_func(void) {
     }
 
     op(_CHECK_STACK_SPACE, (unused, unused, unused[oparg] -- unused, unused, 
unused[oparg])) {
+        assert((this_instr + 4)->opcode == _PUSH_FRAME);
+        PyCodeObject *co = get_code_with_logging((this_instr + 4));
+        if (co == NULL) {
+            ctx->done = true;
+            break;
+        }
+        ADD_OP(_CHECK_STACK_SPACE_OPERAND, 0, co->co_framesize);
     }
 
     op (_CHECK_STACK_SPACE_OPERAND, (framesize/2 -- )) {
         (void)framesize;
-        /* We should never see _CHECK_STACK_SPACE_OPERANDs.
-        * They are only created at the end of this pass. */
-        Py_UNREACHABLE();
     }
 
     op(_PUSH_FRAME, (new_frame -- )) {
diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h
index e7b31ad6ac02c3..61a30314c21789 100644
--- a/Python/optimizer_cases.c.h
+++ b/Python/optimizer_cases.c.h
@@ -2996,6 +2996,13 @@
         }
 
         case _CHECK_STACK_SPACE: {
+            assert((this_instr + 4)->opcode == _PUSH_FRAME);
+            PyCodeObject *co = get_code_with_logging((this_instr + 4));
+            if (co == NULL) {
+                ctx->done = true;
+                break;
+            }
+            ADD_OP(_CHECK_STACK_SPACE_OPERAND, 0, co->co_framesize);
             break;
         }
 
@@ -3899,7 +3906,6 @@
         case _CHECK_STACK_SPACE_OPERAND: {
             uint32_t framesize = (uint32_t)this_instr->operand0;
             (void)framesize;
-            Py_UNREACHABLE();
             break;
         }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to