https://github.com/python/cpython/commit/4af1265767043e438f6eed8352038eb493198604
commit: 4af1265767043e438f6eed8352038eb493198604
branch: main
author: Kumar Aditya <[email protected]>
committer: markshannon <[email protected]>
date: 2026-10-22T16:58:29-07:00
summary:

gh-159493: insert super instructions after borrow analysis in compiler 
(GH-160032)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-14-00-00.gh-issue-159493.sfLf1b.rst
M Lib/test/test_dis.py
M Lib/test/test_peepholer.py
M Python/flowgraph.c

diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index 9879a665bc0500..709fa374704f1f 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -1817,26 +1817,25 @@ def _prepare_test_cases():
   make_inst(opname='MAKE_CELL', arg=1, argval='b', argrepr='b', offset=2, 
start_offset=2, starts_line=False, line_number=None),
   make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=4, 
start_offset=4, starts_line=True, line_number=1, cache_info=[('counter', 1, 
b'\x00\x00')]),
   make_inst(opname='LOAD_CONST', arg=3, argval=(3, 4), argrepr='(3, 4)', 
offset=8, start_offset=8, starts_line=True, line_number=2),
-  make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='a', argrepr='a', 
offset=10, start_offset=10, starts_line=False, line_number=2),
-  make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='b', argrepr='b', 
offset=12, start_offset=12, starts_line=False, line_number=2),
-  make_inst(opname='BUILD_TUPLE', arg=2, argval=2, argrepr='', offset=14, 
start_offset=14, starts_line=False, line_number=2),
-  make_inst(opname='LOAD_CONST', arg=1, argval=code_object_f, 
argrepr=repr(code_object_f), offset=16, start_offset=16, starts_line=False, 
line_number=2),
-  make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', 
offset=18, start_offset=18, starts_line=False, line_number=2),
-  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, 
argrepr='closure', offset=20, start_offset=20, starts_line=False, 
line_number=2),
-  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, 
argrepr='defaults', offset=22, start_offset=22, starts_line=False, 
line_number=2),
-  make_inst(opname='STORE_FAST', arg=2, argval='f', argrepr='f', offset=24, 
start_offset=24, starts_line=False, line_number=2),
-  make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + 
NULL', offset=26, start_offset=26, starts_line=True, line_number=7, 
cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), 
('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, 
b'\x00\x00')]),
-  make_inst(opname='LOAD_DEREF', arg=0, argval='a', argrepr='a', offset=36, 
start_offset=36, starts_line=False, line_number=7),
-  make_inst(opname='LOAD_DEREF', arg=1, argval='b', argrepr='b', offset=38, 
start_offset=38, starts_line=False, line_number=7),
-  make_inst(opname='LOAD_COMMON_CONSTANT', arg=8, argval='', argrepr="''", 
offset=40, start_offset=40, starts_line=False, line_number=7),
-  make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=42, 
start_offset=42, starts_line=False, line_number=7),
-  make_inst(opname='BUILD_LIST', arg=0, argval=0, argrepr='', offset=44, 
start_offset=44, starts_line=False, line_number=7),
-  make_inst(opname='BUILD_MAP', arg=0, argval=0, argrepr='', offset=46, 
start_offset=46, starts_line=False, line_number=7),
-  make_inst(opname='LOAD_CONST', arg=2, argval='Hello world!', argrepr="'Hello 
world!'", offset=48, start_offset=48, starts_line=False, line_number=7),
-  make_inst(opname='CALL', arg=7, argval=7, argrepr='', offset=50, 
start_offset=50, starts_line=False, line_number=7, cache_info=[('counter', 1, 
b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
-  make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=58, 
start_offset=58, starts_line=False, line_number=7),
-  make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='f', argrepr='f', 
offset=60, start_offset=60, starts_line=True, line_number=8),
-  make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', 
offset=62, start_offset=62, starts_line=False, line_number=8),
+  make_inst(opname='LOAD_FAST_BORROW_LOAD_FAST_BORROW', arg=1, argval=('a', 
'b'), argrepr='a, b', offset=10, start_offset=10, starts_line=False, 
line_number=2),
+  make_inst(opname='BUILD_TUPLE', arg=2, argval=2, argrepr='', offset=12, 
start_offset=12, starts_line=False, line_number=2),
+  make_inst(opname='LOAD_CONST', arg=1, argval=code_object_f, 
argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=False, 
line_number=2),
+  make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', 
offset=16, start_offset=16, starts_line=False, line_number=2),
+  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, 
argrepr='closure', offset=18, start_offset=18, starts_line=False, 
line_number=2),
+  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, 
argrepr='defaults', offset=20, start_offset=20, starts_line=False, 
line_number=2),
+  make_inst(opname='STORE_FAST', arg=2, argval='f', argrepr='f', offset=22, 
start_offset=22, starts_line=False, line_number=2),
+  make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + 
NULL', offset=24, start_offset=24, starts_line=True, line_number=7, 
cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), 
('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, 
b'\x00\x00')]),
+  make_inst(opname='LOAD_DEREF', arg=0, argval='a', argrepr='a', offset=34, 
start_offset=34, starts_line=False, line_number=7),
+  make_inst(opname='LOAD_DEREF', arg=1, argval='b', argrepr='b', offset=36, 
start_offset=36, starts_line=False, line_number=7),
+  make_inst(opname='LOAD_COMMON_CONSTANT', arg=8, argval='', argrepr="''", 
offset=38, start_offset=38, starts_line=False, line_number=7),
+  make_inst(opname='LOAD_SMALL_INT', arg=1, argval=1, argrepr='', offset=40, 
start_offset=40, starts_line=False, line_number=7),
+  make_inst(opname='BUILD_LIST', arg=0, argval=0, argrepr='', offset=42, 
start_offset=42, starts_line=False, line_number=7),
+  make_inst(opname='BUILD_MAP', arg=0, argval=0, argrepr='', offset=44, 
start_offset=44, starts_line=False, line_number=7),
+  make_inst(opname='LOAD_CONST', arg=2, argval='Hello world!', argrepr="'Hello 
world!'", offset=46, start_offset=46, starts_line=False, line_number=7),
+  make_inst(opname='CALL', arg=7, argval=7, argrepr='', offset=48, 
start_offset=48, starts_line=False, line_number=7, cache_info=[('counter', 1, 
b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
+  make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=56, 
start_offset=56, starts_line=False, line_number=7),
+  make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='f', argrepr='f', 
offset=58, start_offset=58, starts_line=True, line_number=8),
+  make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', 
offset=60, start_offset=60, starts_line=False, line_number=8),
 ]
 
 expected_opinfo_f = [
@@ -1845,25 +1844,23 @@ def _prepare_test_cases():
   make_inst(opname='MAKE_CELL', arg=1, argval='d', argrepr='d', offset=4, 
start_offset=4, starts_line=False, line_number=None),
   make_inst(opname='RESUME', arg=0, argval=0, argrepr='', offset=6, 
start_offset=6, starts_line=True, line_number=2, cache_info=[('counter', 1, 
b'\x00\x00')]),
   make_inst(opname='LOAD_CONST', arg=2, argval=(5, 6), argrepr='(5, 6)', 
offset=10, start_offset=10, starts_line=True, line_number=3),
-  make_inst(opname='LOAD_FAST_BORROW', arg=3, argval='a', argrepr='a', 
offset=12, start_offset=12, starts_line=False, line_number=3),
-  make_inst(opname='LOAD_FAST_BORROW', arg=4, argval='b', argrepr='b', 
offset=14, start_offset=14, starts_line=False, line_number=3),
-  make_inst(opname='LOAD_FAST_BORROW', arg=0, argval='c', argrepr='c', 
offset=16, start_offset=16, starts_line=False, line_number=3),
-  make_inst(opname='LOAD_FAST_BORROW', arg=1, argval='d', argrepr='d', 
offset=18, start_offset=18, starts_line=False, line_number=3),
-  make_inst(opname='BUILD_TUPLE', arg=4, argval=4, argrepr='', offset=20, 
start_offset=20, starts_line=False, line_number=3),
-  make_inst(opname='LOAD_CONST', arg=1, argval=code_object_inner, 
argrepr=repr(code_object_inner), offset=22, start_offset=22, starts_line=False, 
line_number=3),
-  make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', 
offset=24, start_offset=24, starts_line=False, line_number=3),
-  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, 
argrepr='closure', offset=26, start_offset=26, starts_line=False, 
line_number=3),
-  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, 
argrepr='defaults', offset=28, start_offset=28, starts_line=False, 
line_number=3),
-  make_inst(opname='STORE_FAST', arg=2, argval='inner', argrepr='inner', 
offset=30, start_offset=30, starts_line=False, line_number=3),
-  make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + 
NULL', offset=32, start_offset=32, starts_line=True, line_number=5, 
cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), 
('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, 
b'\x00\x00')]),
-  make_inst(opname='LOAD_DEREF', arg=3, argval='a', argrepr='a', offset=42, 
start_offset=42, starts_line=False, line_number=5),
-  make_inst(opname='LOAD_DEREF', arg=4, argval='b', argrepr='b', offset=44, 
start_offset=44, starts_line=False, line_number=5),
-  make_inst(opname='LOAD_DEREF', arg=0, argval='c', argrepr='c', offset=46, 
start_offset=46, starts_line=False, line_number=5),
-  make_inst(opname='LOAD_DEREF', arg=1, argval='d', argrepr='d', offset=48, 
start_offset=48, starts_line=False, line_number=5),
-  make_inst(opname='CALL', arg=4, argval=4, argrepr='', offset=50, 
start_offset=50, starts_line=False, line_number=5, cache_info=[('counter', 1, 
b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
-  make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=58, 
start_offset=58, starts_line=False, line_number=5),
-  make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='inner', argrepr='inner', 
offset=60, start_offset=60, starts_line=True, line_number=6),
-  make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', 
offset=62, start_offset=62, starts_line=False, line_number=6),
+  make_inst(opname='LOAD_FAST_BORROW_LOAD_FAST_BORROW', arg=52, argval=('a', 
'b'), argrepr='a, b', offset=12, start_offset=12, starts_line=False, 
line_number=3),
+  make_inst(opname='LOAD_FAST_BORROW_LOAD_FAST_BORROW', arg=1, argval=('c', 
'd'), argrepr='c, d', offset=14, start_offset=14, starts_line=False, 
line_number=3),
+  make_inst(opname='BUILD_TUPLE', arg=4, argval=4, argrepr='', offset=16, 
start_offset=16, starts_line=False, line_number=3),
+  make_inst(opname='LOAD_CONST', arg=1, argval=code_object_inner, 
argrepr=repr(code_object_inner), offset=18, start_offset=18, starts_line=False, 
line_number=3),
+  make_inst(opname='MAKE_FUNCTION', arg=None, argval=None, argrepr='', 
offset=20, start_offset=20, starts_line=False, line_number=3),
+  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=8, argval=8, 
argrepr='closure', offset=22, start_offset=22, starts_line=False, 
line_number=3),
+  make_inst(opname='SET_FUNCTION_ATTRIBUTE', arg=1, argval=1, 
argrepr='defaults', offset=24, start_offset=24, starts_line=False, 
line_number=3),
+  make_inst(opname='STORE_FAST', arg=2, argval='inner', argrepr='inner', 
offset=26, start_offset=26, starts_line=False, line_number=3),
+  make_inst(opname='LOAD_GLOBAL', arg=1, argval='print', argrepr='print + 
NULL', offset=28, start_offset=28, starts_line=True, line_number=5, 
cache_info=[('counter', 1, b'\x00\x00'), ('index', 1, b'\x00\x00'), 
('module_keys_version', 1, b'\x00\x00'), ('builtin_keys_version', 1, 
b'\x00\x00')]),
+  make_inst(opname='LOAD_DEREF', arg=3, argval='a', argrepr='a', offset=38, 
start_offset=38, starts_line=False, line_number=5),
+  make_inst(opname='LOAD_DEREF', arg=4, argval='b', argrepr='b', offset=40, 
start_offset=40, starts_line=False, line_number=5),
+  make_inst(opname='LOAD_DEREF', arg=0, argval='c', argrepr='c', offset=42, 
start_offset=42, starts_line=False, line_number=5),
+  make_inst(opname='LOAD_DEREF', arg=1, argval='d', argrepr='d', offset=44, 
start_offset=44, starts_line=False, line_number=5),
+  make_inst(opname='CALL', arg=4, argval=4, argrepr='', offset=46, 
start_offset=46, starts_line=False, line_number=5, cache_info=[('counter', 1, 
b'\x00\x00'), ('func_version', 2, b'\x00\x00\x00\x00')]),
+  make_inst(opname='POP_TOP', arg=None, argval=None, argrepr='', offset=54, 
start_offset=54, starts_line=False, line_number=5),
+  make_inst(opname='LOAD_FAST_BORROW', arg=2, argval='inner', argrepr='inner', 
offset=56, start_offset=56, starts_line=True, line_number=6),
+  make_inst(opname='RETURN_VALUE', arg=None, argval=None, argrepr='', 
offset=58, start_offset=58, starts_line=False, line_number=6),
 ]
 
 expected_opinfo_inner = [
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 8727352a31a7a0..fa8b5ceebb9f32 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -855,6 +855,31 @@ def f():
             y = x + x
         self.assertInBytecode(f, 'LOAD_FAST_BORROW_LOAD_FAST_BORROW')
 
+    def test_store_fast_load_fast_borrow(self):
+        # gh-159493: a LOAD_FAST that can borrow must not be fused into
+        # STORE_FAST_LOAD_FAST, regardless of how the source is laid out.
+        def f(shared, n):
+            for i in range(n): shared
+
+        def g(shared, n):
+            for i in range(n):
+                shared
+
+        for func in (f, g):
+            with self.subTest(func=func.__name__):
+                self.assertNotInBytecode(func, 'STORE_FAST_LOAD_FAST')
+                self.assertInBytecode(func, 'LOAD_FAST_BORROW', 'shared')
+        self.assertEqual([i.opname for i in dis.get_instructions(f)],
+                         [i.opname for i in dis.get_instructions(g)])
+
+    def test_store_fast_load_fast_not_borrowable(self):
+        def f(x):
+            y = x; z = y
+            return z
+        # The load of y is stored into z, so it needs a strong reference and
+        # can still be fused.
+        self.assertInBytecode(f, 'STORE_FAST_LOAD_FAST', ('y', 'y'))
+
     def test_load_fast_unknown_simple(self):
         def f():
             if condition():
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-14-00-00.gh-issue-159493.sfLf1b.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-14-00-00.gh-issue-159493.sfLf1b.rst
new file mode 100644
index 00000000000000..35289d995b3d35
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-21-14-00-00.gh-issue-159493.sfLf1b.rst
@@ -0,0 +1,3 @@
+Superinstructions are now formed after the compiler decides which
+:opcode:`LOAD_FAST` instructions can borrow their reference.
+This fixes reference count contention slowdown on the free-threaded build for 
single-line loops and comprehensions.
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index 17489ae5bfb525..b8d57dcade32d2 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -2719,6 +2719,12 @@ insert_superinstructions(cfg_builder *g)
                         make_super_instruction(inst, &b->b_instr[i + 1], 
LOAD_FAST_LOAD_FAST);
                     }
                     break;
+                case LOAD_FAST_BORROW:
+                    if (nextop == LOAD_FAST_BORROW) {
+                        make_super_instruction(inst, &b->b_instr[i + 1],
+                                               
LOAD_FAST_BORROW_LOAD_FAST_BORROW);
+                    }
+                    break;
                 case STORE_FAST:
                     switch (nextop) {
                         case LOAD_FAST:
@@ -2732,9 +2738,9 @@ insert_superinstructions(cfg_builder *g)
             }
         }
     }
-    int res = remove_redundant_nops(g);
+    RETURN_IF_ERROR(remove_redundant_nops(g));
     assert(no_redundant_nops(g));
-    return res;
+    return SUCCESS;
 }
 
 #define NOT_LOCAL -1
@@ -3834,8 +3840,6 @@ _PyCfg_OptimizeCodeUnit(cfg_builder *g, PyObject *consts, 
PyObject *const_cache,
     RETURN_IF_ERROR(
         add_checks_for_loads_of_uninitialized_variables(
             g->g_entryblock, nlocals, nparams));
-    RETURN_IF_ERROR(insert_superinstructions(g));
-
     RETURN_IF_ERROR(push_cold_blocks_to_end(g));
     RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno));
     // temporarily remove assert. See 
https://github.com/python/cpython/issues/125845
@@ -4157,11 +4161,19 @@ _PyCfg_OptimizedCfgToInstructionSequence(cfg_builder *g,
     RETURN_IF_ERROR(normalize_jumps(g));
     assert(no_redundant_jumps(g));
 
-    /* Can't modify the bytecode after inserting instructions that produce
-     * borrowed references.
+    /* After this point the bytecode may only be modified in ways that
+     * preserve the borrowed references computed here.
+     *
+     * NOTE: keep the passes below in sync with _PyCompile_OptimizeCfg().
      */
     RETURN_IF_ERROR(optimize_load_fast(g));
 
+    /* Superinstructions are inserted after optimize_load_fast() so that a
+     * LOAD_FAST which can be borrowed is converted to LOAD_FAST_BORROW
+     * instead of being fused into a STORE_FAST_LOAD_FAST. Fusing adjacent
+     * instructions does not change which references are borrowed. */
+    RETURN_IF_ERROR(insert_superinstructions(g));
+
     /* Can't modify the bytecode after computing jump offsets. */
     if (_PyCfg_ToInstructionSequence(g, seq) < 0) {
         return ERROR;
@@ -4254,10 +4266,15 @@ _PyCompile_OptimizeCfg(PyObject *seq, PyObject *consts, 
int nlocals)
         goto error;
     }
 
+    /* NOTE: keep in sync with _PyCfg_OptimizedCfgToInstructionSequence(). */
     if (optimize_load_fast(g) != SUCCESS) {
         goto error;
     }
 
+    if (insert_superinstructions(g) < 0) {
+        goto error;
+    }
+
     res = cfg_to_instruction_sequence(g);
 error:
     Py_DECREF(const_cache);

_______________________________________________
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