Skip to content

Commit 8bff940

Browse files
gh-105775: Convert LOAD_CLOSURE to a pseudo-op (#106059)
This enables super-instruction formation, removal of checks for uninitialized variables, and frees up an instruction.
1 parent 3c70d46 commit 8bff940

File tree

16 files changed

+733
-702
lines changed

16 files changed

+733
-702
lines changed

Doc/library/dis.rst

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,18 +1291,6 @@ iterations of the loop.
12911291
.. versionadded:: 3.11
12921292

12931293

1294-
.. opcode:: LOAD_CLOSURE (i)
1295-
1296-
Pushes a reference to the cell contained in slot ``i`` of the "fast locals"
1297-
storage. The name of the variable is ``co_fastlocalnames[i]``.
1298-
1299-
Note that ``LOAD_CLOSURE`` is effectively an alias for ``LOAD_FAST``.
1300-
It exists to keep bytecode a little more readable.
1301-
1302-
.. versionchanged:: 3.11
1303-
``i`` is no longer offset by the length of ``co_varnames``.
1304-
1305-
13061294
.. opcode:: LOAD_DEREF (i)
13071295

13081296
Loads the cell contained in slot ``i`` of the "fast locals" storage.
@@ -1725,6 +1713,17 @@ but are replaced by real opcodes or removed before bytecode is generated.
17251713
Undirected relative jump instructions which are replaced by their
17261714
directed (forward/backward) counterparts by the assembler.
17271715

1716+
.. opcode:: LOAD_CLOSURE (i)
1717+
1718+
Pushes a reference to the cell contained in slot ``i`` of the "fast locals"
1719+
storage.
1720+
1721+
Note that ``LOAD_CLOSURE`` is replaced with ``LOAD_FAST`` in the assembler.
1722+
1723+
.. versionchanged:: 3.13
1724+
This opcode is now a pseudo-instruction.
1725+
1726+
17281727
.. opcode:: LOAD_METHOD
17291728

17301729
Optimized unbound method lookup. Emitted as a ``LOAD_ATTR`` opcode

Include/internal/pycore_opcode.h

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/opcode.h

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ def _write_atomic(path, data, mode=0o666):
451451
# Python 3.13a1 3553 (Add SET_FUNCTION_ATTRIBUTE)
452452
# Python 3.13a1 3554 (more efficient bytecodes for f-strings)
453453
# Python 3.13a1 3555 (generate specialized opcodes metadata from bytecodes.c)
454+
# Python 3.13a1 3556 (Convert LOAD_CLOSURE to a pseudo-op)
454455

455456
# Python 3.14 will start with 3600
456457

@@ -467,7 +468,7 @@ def _write_atomic(path, data, mode=0o666):
467468
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
468469
# in PC/launcher.c must also be updated.
469470

470-
MAGIC_NUMBER = (3555).to_bytes(2, 'little') + b'\r\n'
471+
MAGIC_NUMBER = (3556).to_bytes(2, 'little') + b'\r\n'
471472

472473
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
473474

Lib/opcode.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,6 @@ def pseudo_op(name, op, real_ops):
198198
jrel_op('JUMP_BACKWARD_NO_INTERRUPT', 134) # Number of words to skip (backwards)
199199
def_op('MAKE_CELL', 135)
200200
hasfree.append(135)
201-
def_op('LOAD_CLOSURE', 136)
202-
hasfree.append(136)
203201
def_op('LOAD_DEREF', 137)
204202
hasfree.append(137)
205203
def_op('STORE_DEREF', 138)
@@ -293,6 +291,7 @@ def pseudo_op(name, op, real_ops):
293291
pseudo_op('LOAD_ZERO_SUPER_ATTR', 265, ['LOAD_SUPER_ATTR'])
294292

295293
pseudo_op('STORE_FAST_MAYBE_NULL', 266, ['STORE_FAST'])
294+
pseudo_op('LOAD_CLOSURE', 267, ['LOAD_FAST'])
296295

297296
MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1
298297

Lib/test/test_compiler_assemble.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,41 @@ def test_simple_expr(self):
7070
]
7171
expected = {(3, 4) : 3.5, (-100, 200) : 50, (10, 18) : 14}
7272
self.assemble_test(insts, metadata, expected)
73+
74+
75+
def test_expression_with_pseudo_instruction_load_closure(self):
76+
77+
def mod_two(x):
78+
def inner():
79+
return x
80+
return inner() % 2
81+
82+
inner_code = mod_two.__code__.co_consts[1]
83+
assert isinstance(inner_code, types.CodeType)
84+
85+
metadata = {
86+
'filename' : 'mod_two.py',
87+
'name' : 'mod_two',
88+
'qualname' : 'nested.mod_two',
89+
'cellvars' : {'x' : 0},
90+
'consts': {None: 0, inner_code: 1, 2: 2},
91+
'argcount' : 1,
92+
'varnames' : {'x' : 0},
93+
}
94+
95+
instructions = [
96+
('RESUME', 0,),
97+
('PUSH_NULL', 0, 1),
98+
('LOAD_CLOSURE', 0, 1),
99+
('BUILD_TUPLE', 1, 1),
100+
('LOAD_CONST', 1, 1),
101+
('MAKE_FUNCTION', 0, 2),
102+
('SET_FUNCTION_ATTRIBUTE', 8, 2),
103+
('CALL', 0, 2), # (lambda: x)()
104+
('LOAD_CONST', 2, 2), # 2
105+
('BINARY_OP', 6, 2), # %
106+
('RETURN_VALUE', 0, 2)
107+
]
108+
109+
expected = {(0,): 0, (1,): 1, (2,): 0, (120,): 0, (121,): 1}
110+
self.assemble_test(instructions, metadata, expected)

Lib/test/test_dis.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ def foo(x):
685685
686686
%3d RESUME 0
687687
688-
%3d LOAD_CLOSURE 0 (y)
688+
%3d LOAD_FAST 0 (y)
689689
BUILD_TUPLE 1
690690
LOAD_CONST 1 (<code object foo at 0x..., file "%s", line %d>)
691691
MAKE_FUNCTION
@@ -709,7 +709,7 @@ def foo(x):
709709
%3d RESUME 0
710710
711711
%3d LOAD_GLOBAL 1 (NULL + list)
712-
LOAD_CLOSURE 0 (x)
712+
LOAD_FAST 0 (x)
713713
BUILD_TUPLE 1
714714
LOAD_CONST 1 (<code object <genexpr> at 0x..., file "%s", line %d>)
715715
MAKE_FUNCTION
@@ -1596,8 +1596,8 @@ def _prepare_test_cases():
15961596
Instruction(opname='MAKE_CELL', opcode=135, arg=1, argval='b', argrepr='b', offset=2, start_offset=2, starts_line=None, is_jump_target=False, positions=None),
15971597
Instruction(opname='RESUME', opcode=151, arg=0, argval=0, argrepr='', offset=4, start_offset=4, starts_line=1, is_jump_target=False, positions=None),
15981598
Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=(3, 4), argrepr='(3, 4)', offset=6, start_offset=6, starts_line=2, is_jump_target=False, positions=None),
1599-
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=0, argval='a', argrepr='a', offset=8, start_offset=8, starts_line=None, is_jump_target=False, positions=None),
1600-
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=1, argval='b', argrepr='b', offset=10, start_offset=10, starts_line=None, is_jump_target=False, positions=None),
1599+
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='a', argrepr='a', offset=8, start_offset=8, starts_line=None, is_jump_target=False, positions=None),
1600+
Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='b', argrepr='b', offset=10, start_offset=10, starts_line=None, is_jump_target=False, positions=None),
16011601
Instruction(opname='BUILD_TUPLE', opcode=102, arg=2, argval=2, argrepr='', offset=12, start_offset=12, starts_line=None, is_jump_target=False, positions=None),
16021602
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=code_object_f, argrepr=repr(code_object_f), offset=14, start_offset=14, starts_line=None, is_jump_target=False, positions=None),
16031603
Instruction(opname='MAKE_FUNCTION', opcode=24, arg=None, argval=None, argrepr='', offset=16, start_offset=16, starts_line=None, is_jump_target=False, positions=None),
@@ -1624,10 +1624,10 @@ def _prepare_test_cases():
16241624
Instruction(opname='MAKE_CELL', opcode=135, arg=1, argval='d', argrepr='d', offset=4, start_offset=4, starts_line=None, is_jump_target=False, positions=None),
16251625
Instruction(opname='RESUME', opcode=151, arg=0, argval=0, argrepr='', offset=6, start_offset=6, starts_line=2, is_jump_target=False, positions=None),
16261626
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=(5, 6), argrepr='(5, 6)', offset=8, start_offset=8, starts_line=3, is_jump_target=False, positions=None),
1627-
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=3, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=None, is_jump_target=False, positions=None),
1628-
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=4, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=None, is_jump_target=False, positions=None),
1629-
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=None, is_jump_target=False, positions=None),
1630-
Instruction(opname='LOAD_CLOSURE', opcode=136, arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=None, is_jump_target=False, positions=None),
1627+
Instruction(opname='LOAD_FAST', opcode=124, arg=3, argval='a', argrepr='a', offset=10, start_offset=10, starts_line=None, is_jump_target=False, positions=None),
1628+
Instruction(opname='LOAD_FAST', opcode=124, arg=4, argval='b', argrepr='b', offset=12, start_offset=12, starts_line=None, is_jump_target=False, positions=None),
1629+
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='c', argrepr='c', offset=14, start_offset=14, starts_line=None, is_jump_target=False, positions=None),
1630+
Instruction(opname='LOAD_FAST', opcode=124, arg=1, argval='d', argrepr='d', offset=16, start_offset=16, starts_line=None, is_jump_target=False, positions=None),
16311631
Instruction(opname='BUILD_TUPLE', opcode=102, arg=4, argval=4, argrepr='', offset=18, start_offset=18, starts_line=None, is_jump_target=False, positions=None),
16321632
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=code_object_inner, argrepr=repr(code_object_inner), offset=20, start_offset=20, starts_line=None, is_jump_target=False, positions=None),
16331633
Instruction(opname='MAKE_FUNCTION', opcode=24, arg=None, argval=None, argrepr='', offset=22, start_offset=22, starts_line=None, is_jump_target=False, positions=None),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:opcode:`LOAD_CLOSURE` is now a pseudo-op.

PC/launcher.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,7 @@ static PYC_MAGIC magic_values[] = {
12701270
/* Allow 50 magic numbers per version from here on */
12711271
{ 3450, 3499, L"3.11" },
12721272
{ 3500, 3549, L"3.12" },
1273+
{ 3550, 3599, L"3.13" },
12731274
{ 0 }
12741275
};
12751276

Python/bytecodes.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,9 @@ dummy_func(
175175
}
176176
}
177177

178-
inst(LOAD_CLOSURE, (-- value)) {
179-
/* We keep LOAD_CLOSURE so that the bytecode stays more readable. */
180-
value = GETLOCAL(oparg);
181-
ERROR_IF(value == NULL, unbound_local_error);
182-
Py_INCREF(value);
183-
}
178+
pseudo(LOAD_CLOSURE) = {
179+
LOAD_FAST,
180+
};
184181

185182
inst(LOAD_FAST_CHECK, (-- value)) {
186183
value = GETLOCAL(oparg);

0 commit comments

Comments
 (0)