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]