Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-97933: inline list/dict/set comprehensions #101441

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
72afa83
gh-97933: inline sync list/dict/set comprehensions
carljm Jan 30, 2023
8988234
simplify cell handling code slightly
carljm Jan 31, 2023
22c4a86
clarify comments
carljm Jan 31, 2023
c1b54f0
enable inlining async comprehensions also
carljm Jan 31, 2023
43db9b8
fix typo
carljm Jan 31, 2023
ed3209b
Merge branch 'main' into inlinecomp2
carljm Jan 31, 2023
aceb6c7
fix outer-cell, inner-local case
carljm Jan 31, 2023
e57c354
fix restoring NULL (unbound outer name) followed by load
carljm Jan 31, 2023
795d854
emit 1 x SWAP N+1 instead of N x SWAP 2
carljm Jan 31, 2023
686221a
remove stray dis.dis() call
carljm Jan 31, 2023
ac99697
fix compiler warning about Py_ssize_t -> int conversion
carljm Jan 31, 2023
db208d5
Merge branch 'main' into inlinecomp2
carljm Jan 31, 2023
8b76051
Merge branch 'main' into inlinecomp2
carljm Feb 1, 2023
4620856
add a couple more tests
carljm Feb 1, 2023
8773653
clear comp locals on entry, eval iter expr first
carljm Feb 1, 2023
be3becc
Merge branch 'main' into inlinecomp2
carljm Feb 9, 2023
f0c051e
fix double decref in error case
carljm Feb 9, 2023
142859a
adjust to RETURN_CONST
carljm Feb 9, 2023
568a470
fix up refcounting
carljm Feb 10, 2023
add772e
Merge branch 'main' into inlinecomp2
carljm Feb 10, 2023
17d5d84
improve importlib comment
carljm Feb 10, 2023
b87d209
mark STORE_FAST_MAYBE_NULL as possibly NULLing a local
carljm Feb 10, 2023
36b2917
Merge branch 'main' into inlinecomp2
carljm Feb 10, 2023
ae0bd02
Merge branch 'main' into inlinecomp2
carljm Feb 13, 2023
9f0fc5b
Merge branch 'main' into inlinecomp2
carljm Feb 20, 2023
463c740
add test for NameError/UnboundLocalError
carljm Feb 28, 2023
67f50ba
fix case where iter var is free in outer scope
carljm Feb 28, 2023
73dc0ed
Merge branch 'main' into inlinecomp2
carljm Feb 28, 2023
ecb313c
Merge branch 'main' into inlinecomp2
carljm Mar 6, 2023
4109baa
add inlining of non-function-scope comprehensions
carljm Mar 7, 2023
d8802a1
simplify scope handling
carljm Mar 7, 2023
1c019a7
Merge branch 'main' into inlinecomp2
carljm Mar 7, 2023
24a9d9f
Merge branch 'main' into inlinecomp2
carljm Mar 8, 2023
b6a025b
add tests for comprehensions in class scope
carljm Mar 8, 2023
90b34de
run all listcomp scope tests in module, class, and func scope
carljm Mar 8, 2023
06db319
Merge branch 'main' into inlinecomp2
carljm Mar 8, 2023
b52046b
handle frame locals materialization in class/module scope
carljm Mar 14, 2023
6c5f269
Merge branch 'main' into inlinecomp2
carljm Mar 14, 2023
1a8f4a0
Merge branch 'main' into inlinecomp2
carljm Mar 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1210,6 +1210,14 @@ iterations of the loop.

.. versionadded:: 3.12

.. opcode:: LOAD_FAST_AND_CLEAR (var_num)

Pushes a reference to the local ``co_varnames[var_num]`` onto the stack (or
pushes ``NULL`` onto the stack if the local variable has not been
initialized) and sets ``co_varnames[var_num]`` to ``NULL``.

.. versionadded:: 3.12

.. opcode:: STORE_FAST (var_num)

Stores ``STACK.pop()`` into the local ``co_varnames[var_num]``.
@@ -125,6 +125,7 @@ struct callable_cache {
// Note that these all fit within a byte, as do combinations.
// Later, we will use the smaller numbers to differentiate the different
// kinds of locals (e.g. pos-only arg, varkwargs, local-only).
#define CO_FAST_HIDDEN 0x10
#define CO_FAST_LOCAL 0x20
#define CO_FAST_CELL 0x40
#define CO_FAST_FREE 0x80

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -64,6 +64,7 @@ typedef struct _symtable_entry {
unsigned ste_needs_class_closure : 1; /* for class scopes, true if a
closure over __class__
should be created */
unsigned ste_comp_inlined : 1; /* true if this comprehension is inlined */
unsigned ste_comp_iter_target : 1; /* true if visiting comprehension target */
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
int ste_lineno; /* first line of block */

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -435,6 +435,7 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.12a6 3519 (Modify SEND instruction)
# Python 3.12a6 3520 (Remove PREP_RERAISE_STAR, add CALL_INTRINSIC_2)
# Python 3.12a7 3521 (Shrink the LOAD_GLOBAL caches)
# Python 3.12a7 3522 (Inline list/dict/set comprehensions)

# Python 3.13 will start with 3550

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

MAGIC_NUMBER = (3521).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3522).to_bytes(2, 'little') + b'\r\n'

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

@@ -197,6 +197,8 @@ def pseudo_op(name, op, real_ops):
hascompare.append(141)

def_op('CALL_FUNCTION_EX', 142) # Flags
def_op('LOAD_FAST_AND_CLEAR', 143) # Local variable number
haslocal.append(143)

def_op('EXTENDED_ARG', 144)
EXTENDED_ARG = 144
@@ -242,6 +244,8 @@ def pseudo_op(name, op, real_ops):

pseudo_op('LOAD_METHOD', 262, ['LOAD_ATTR'])

pseudo_op('STORE_FAST_MAYBE_NULL', 263, ['STORE_FAST'])

MAX_PSEUDO_OPCODE = MIN_PSEUDO_OPCODE + len(_pseudo_ops) - 1

del def_op, name_op, jrel_op, jabs_op, pseudo_op
@@ -1352,14 +1352,11 @@ def test_multiline_list_comprehension(self):
and x != 50)]
""")
compiled_code, _ = self.check_positions_against_ast(snippet)
compiled_code = compiled_code.co_consts[0]
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'LIST_APPEND',
line=1, end_line=2, column=1, end_column=8, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=1, end_line=2, column=1, end_column=8, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_VALUE',
line=1, end_line=6, column=0, end_column=32, occurrence=1)

def test_multiline_async_list_comprehension(self):
snippet = textwrap.dedent("""\
@@ -1374,13 +1371,13 @@ async def f():
compiled_code, _ = self.check_positions_against_ast(snippet)
g = {}
eval(compiled_code, g)
compiled_code = g['f'].__code__.co_consts[1]
compiled_code = g['f'].__code__
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'LIST_APPEND',
line=2, end_line=3, column=5, end_column=12, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=2, end_line=3, column=5, end_column=12, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_VALUE',
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_CONST',
line=2, end_line=7, column=4, end_column=36, occurrence=1)

def test_multiline_set_comprehension(self):
@@ -1393,14 +1390,11 @@ def test_multiline_set_comprehension(self):
and x != 50)}
""")
compiled_code, _ = self.check_positions_against_ast(snippet)
compiled_code = compiled_code.co_consts[0]
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'SET_ADD',
line=1, end_line=2, column=1, end_column=8, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=1, end_line=2, column=1, end_column=8, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_VALUE',
line=1, end_line=6, column=0, end_column=32, occurrence=1)

def test_multiline_async_set_comprehension(self):
snippet = textwrap.dedent("""\
@@ -1415,13 +1409,13 @@ async def f():
compiled_code, _ = self.check_positions_against_ast(snippet)
g = {}
eval(compiled_code, g)
compiled_code = g['f'].__code__.co_consts[1]
compiled_code = g['f'].__code__
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'SET_ADD',
line=2, end_line=3, column=5, end_column=12, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=2, end_line=3, column=5, end_column=12, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_VALUE',
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_CONST',
line=2, end_line=7, column=4, end_column=36, occurrence=1)

def test_multiline_dict_comprehension(self):
@@ -1434,14 +1428,11 @@ def test_multiline_dict_comprehension(self):
and x != 50)}
""")
compiled_code, _ = self.check_positions_against_ast(snippet)
compiled_code = compiled_code.co_consts[0]
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'MAP_ADD',
line=1, end_line=2, column=1, end_column=7, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=1, end_line=2, column=1, end_column=7, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_VALUE',
line=1, end_line=6, column=0, end_column=32, occurrence=1)

def test_multiline_async_dict_comprehension(self):
snippet = textwrap.dedent("""\
@@ -1456,13 +1447,13 @@ async def f():
compiled_code, _ = self.check_positions_against_ast(snippet)
g = {}
eval(compiled_code, g)
compiled_code = g['f'].__code__.co_consts[1]
compiled_code = g['f'].__code__
self.assertIsInstance(compiled_code, types.CodeType)
self.assertOpcodeSourcePositionIs(compiled_code, 'MAP_ADD',
line=2, end_line=3, column=5, end_column=11, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'JUMP_BACKWARD',
line=2, end_line=3, column=5, end_column=11, occurrence=1)
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_VALUE',
self.assertOpcodeSourcePositionIs(compiled_code, 'RETURN_CONST',
line=2, end_line=7, column=4, end_column=36, occurrence=1)

def test_matchcase_sequence(self):
@@ -1711,9 +1702,6 @@ def test_column_offset_deduplication(self):
for source in [
"lambda: a",
"(a for b in c)",
"[a for b in c]",
"{a for b in c}",
"{a: b for c in d}",
]:
with self.subTest(source):
code = compile(f"{source}, {source}", "<test>", "eval")
@@ -154,15 +154,15 @@ def bug708901():


def bug1333982(x=[]):
assert 0, ([s for s in x] +
assert 0, ((s for s in x) +
1)
pass

dis_bug1333982 = """\
%3d RESUME 0

%3d LOAD_ASSERTION_ERROR
LOAD_CONST 1 (<code object <listcomp> at 0x..., file "%s", line %d>)
LOAD_CONST 1 (<code object <genexpr> at 0x..., file "%s", line %d>)
MAKE_FUNCTION 0
LOAD_FAST 0 (x)
GET_ITER
@@ -628,7 +628,7 @@ async def _co(x):
def _h(y):
def foo(x):
'''funcdoc'''
return [x + z for z in y]
return list(x + z for z in y)
return foo

dis_nested_0 = """\
@@ -658,13 +658,15 @@ def foo(x):

%3d RESUME 0

%3d LOAD_CLOSURE 0 (x)
%3d LOAD_GLOBAL 1 (NULL + list)
LOAD_CLOSURE 0 (x)
BUILD_TUPLE 1
LOAD_CONST 1 (<code object <listcomp> at 0x..., file "%s", line %d>)
LOAD_CONST 1 (<code object <genexpr> at 0x..., file "%s", line %d>)
MAKE_FUNCTION 8 (closure)
LOAD_DEREF 1 (y)
GET_ITER
CALL 0
CALL 1
RETURN_VALUE
""" % (dis_nested_0,
__file__,
@@ -676,21 +678,28 @@ def foo(x):
)

dis_nested_2 = """%s
Disassembly of <code object <listcomp> at 0x..., file "%s", line %d>:
Disassembly of <code object <genexpr> at 0x..., file "%s", line %d>:
COPY_FREE_VARS 1

%3d RESUME 0
BUILD_LIST 0
%3d RETURN_GENERATOR
POP_TOP
RESUME 0
LOAD_FAST 0 (.0)
>> FOR_ITER 7 (to 26)
>> FOR_ITER 9 (to 32)
STORE_FAST 1 (z)
LOAD_DEREF 2 (x)
LOAD_FAST 1 (z)
BINARY_OP 0 (+)
LIST_APPEND 2
JUMP_BACKWARD 9 (to 8)
YIELD_VALUE 1
RESUME 1
POP_TOP
JUMP_BACKWARD 11 (to 10)
>> END_FOR
RETURN_VALUE
RETURN_CONST 0 (None)
>> CALL_INTRINSIC_1 3
RERAISE 1
ExceptionTable:
1 row
""" % (dis_nested_1,
__file__,
_h.__code__.co_firstlineno + 3,
@@ -4215,14 +4215,14 @@ def test(*args, **kwargs):

@cpython_only
def test_signature_bind_implicit_arg(self):
# Issue #19611: getcallargs should work with set comprehensions
# Issue #19611: getcallargs should work with comprehensions
def make_set():
return {z * z for z in range(5)}
setcomp_code = make_set.__code__.co_consts[1]
setcomp_func = types.FunctionType(setcomp_code, {})
return set(z * z for z in range(5))
gencomp_code = make_set.__code__.co_consts[1]
gencomp_func = types.FunctionType(gencomp_code, {})

iterator = iter(range(5))
self.assertEqual(self.call(setcomp_func, iterator), {0, 1, 4, 9, 16})
self.assertEqual(set(self.call(gencomp_func, iterator)), {0, 1, 4, 9, 16})

def test_signature_bind_posonly_kwargs(self):
def foo(bar, /, **kwargs):