Skip to content

Commit 75103d9

Browse files
authored
gh-126835: Move constant tuple folding from ast_opt to CFG (#130769)
1 parent 54efe29 commit 75103d9

File tree

9 files changed

+264
-172
lines changed

9 files changed

+264
-172
lines changed

Lib/test/test_ast/test_ast.py

-111
Original file line numberDiff line numberDiff line change
@@ -153,22 +153,6 @@ def test_optimization_levels__debug__(self):
153153
self.assertIsInstance(res.body[0].value, ast.Name)
154154
self.assertEqual(res.body[0].value.id, expected)
155155

156-
def test_optimization_levels_const_folding(self):
157-
folded = ('Expr', (1, 0, 1, 6), ('Constant', (1, 0, 1, 6), (1, 2), None))
158-
not_folded = ('Expr', (1, 0, 1, 6),
159-
('Tuple', (1, 0, 1, 6),
160-
[('Constant', (1, 1, 1, 2), 1, None),
161-
('Constant', (1, 4, 1, 5), 2, None)], ('Load',)))
162-
163-
cases = [(-1, not_folded), (0, not_folded), (1, folded), (2, folded)]
164-
for (optval, expected) in cases:
165-
with self.subTest(optval=optval):
166-
tree1 = ast.parse("(1, 2)", optimize=optval)
167-
tree2 = ast.parse(ast.parse("(1, 2)"), optimize=optval)
168-
for tree in [tree1, tree2]:
169-
res = to_tuple(tree.body[0])
170-
self.assertEqual(res, expected)
171-
172156
def test_invalid_position_information(self):
173157
invalid_linenos = [
174158
(10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1)
@@ -3193,101 +3177,6 @@ def test_folding_format(self):
31933177

31943178
self.assert_ast(code, non_optimized_target, optimized_target)
31953179

3196-
3197-
def test_folding_tuple(self):
3198-
code = "(1,)"
3199-
3200-
non_optimized_target = self.wrap_expr(ast.Tuple(elts=[ast.Constant(1)]))
3201-
optimized_target = self.wrap_expr(ast.Constant(value=(1,)))
3202-
3203-
self.assert_ast(code, non_optimized_target, optimized_target)
3204-
3205-
def test_folding_type_param_in_function_def(self):
3206-
code = "def foo[%s = (1, 2)](): pass"
3207-
3208-
unoptimized_tuple = ast.Tuple(elts=[ast.Constant(1), ast.Constant(2)])
3209-
unoptimized_type_params = [
3210-
("T", "T", ast.TypeVar),
3211-
("**P", "P", ast.ParamSpec),
3212-
("*Ts", "Ts", ast.TypeVarTuple),
3213-
]
3214-
3215-
for type, name, type_param in unoptimized_type_params:
3216-
result_code = code % type
3217-
optimized_target = self.wrap_statement(
3218-
ast.FunctionDef(
3219-
name='foo',
3220-
args=ast.arguments(),
3221-
body=[ast.Pass()],
3222-
type_params=[type_param(name=name, default_value=ast.Constant((1, 2)))]
3223-
)
3224-
)
3225-
non_optimized_target = self.wrap_statement(
3226-
ast.FunctionDef(
3227-
name='foo',
3228-
args=ast.arguments(),
3229-
body=[ast.Pass()],
3230-
type_params=[type_param(name=name, default_value=unoptimized_tuple)]
3231-
)
3232-
)
3233-
self.assert_ast(result_code, non_optimized_target, optimized_target)
3234-
3235-
def test_folding_type_param_in_class_def(self):
3236-
code = "class foo[%s = (1, 2)]: pass"
3237-
3238-
unoptimized_tuple = ast.Tuple(elts=[ast.Constant(1), ast.Constant(2)])
3239-
unoptimized_type_params = [
3240-
("T", "T", ast.TypeVar),
3241-
("**P", "P", ast.ParamSpec),
3242-
("*Ts", "Ts", ast.TypeVarTuple),
3243-
]
3244-
3245-
for type, name, type_param in unoptimized_type_params:
3246-
result_code = code % type
3247-
optimized_target = self.wrap_statement(
3248-
ast.ClassDef(
3249-
name='foo',
3250-
body=[ast.Pass()],
3251-
type_params=[type_param(name=name, default_value=ast.Constant((1, 2)))]
3252-
)
3253-
)
3254-
non_optimized_target = self.wrap_statement(
3255-
ast.ClassDef(
3256-
name='foo',
3257-
body=[ast.Pass()],
3258-
type_params=[type_param(name=name, default_value=unoptimized_tuple)]
3259-
)
3260-
)
3261-
self.assert_ast(result_code, non_optimized_target, optimized_target)
3262-
3263-
def test_folding_type_param_in_type_alias(self):
3264-
code = "type foo[%s = (1, 2)] = 1"
3265-
3266-
unoptimized_tuple = ast.Tuple(elts=[ast.Constant(1), ast.Constant(2)])
3267-
unoptimized_type_params = [
3268-
("T", "T", ast.TypeVar),
3269-
("**P", "P", ast.ParamSpec),
3270-
("*Ts", "Ts", ast.TypeVarTuple),
3271-
]
3272-
3273-
for type, name, type_param in unoptimized_type_params:
3274-
result_code = code % type
3275-
optimized_target = self.wrap_statement(
3276-
ast.TypeAlias(
3277-
name=ast.Name(id='foo', ctx=ast.Store()),
3278-
type_params=[type_param(name=name, default_value=ast.Constant((1, 2)))],
3279-
value=ast.Constant(value=1),
3280-
)
3281-
)
3282-
non_optimized_target = self.wrap_statement(
3283-
ast.TypeAlias(
3284-
name=ast.Name(id='foo', ctx=ast.Store()),
3285-
type_params=[type_param(name=name, default_value=unoptimized_tuple)],
3286-
value=ast.Constant(value=1),
3287-
)
3288-
)
3289-
self.assert_ast(result_code, non_optimized_target, optimized_target)
3290-
32913180
def test_folding_match_case_allowed_expressions(self):
32923181
def get_match_case_values(node):
32933182
result = []

Lib/test/test_builtin.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ def test_compile_async_generator(self):
554554
self.assertEqual(type(glob['ticker']()), AsyncGeneratorType)
555555

556556
def test_compile_ast(self):
557-
args = ("a*(1,2)", "f.py", "exec")
557+
args = ("a*__debug__", "f.py", "exec")
558558
raw = compile(*args, flags = ast.PyCF_ONLY_AST).body[0]
559559
opt1 = compile(*args, flags = ast.PyCF_OPTIMIZED_AST).body[0]
560560
opt2 = compile(ast.parse(args[0]), *args[1:], flags = ast.PyCF_OPTIMIZED_AST).body[0]
@@ -565,14 +565,14 @@ def test_compile_ast(self):
565565
self.assertIsInstance(tree.value.left, ast.Name)
566566
self.assertEqual(tree.value.left.id, 'a')
567567

568-
raw_right = raw.value.right # expect Tuple((1, 2))
569-
self.assertIsInstance(raw_right, ast.Tuple)
570-
self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
568+
raw_right = raw.value.right
569+
self.assertIsInstance(raw_right, ast.Name)
570+
self.assertEqual(raw_right.id, "__debug__")
571571

572572
for opt in [opt1, opt2]:
573-
opt_right = opt.value.right # expect Constant((1,2))
573+
opt_right = opt.value.right
574574
self.assertIsInstance(opt_right, ast.Constant)
575-
self.assertEqual(opt_right.value, (1, 2))
575+
self.assertEqual(opt_right.value, True)
576576

577577
def test_delattr(self):
578578
sys.spam = 1

Lib/test/test_compile.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -793,9 +793,9 @@ def check_same_constant(const):
793793
f1, f2 = lambda: "not a name", lambda: ("not a name",)
794794
f3 = lambda x: x in {("not a name",)}
795795
self.assertIs(f1.__code__.co_consts[0],
796-
f2.__code__.co_consts[0][0])
796+
f2.__code__.co_consts[1][0])
797797
self.assertIs(next(iter(f3.__code__.co_consts[1])),
798-
f2.__code__.co_consts[0])
798+
f2.__code__.co_consts[1])
799799

800800
# {0} is converted to a constant frozenset({0}) by the peephole
801801
# optimizer
@@ -1129,6 +1129,31 @@ def foo(x):
11291129
self.assertIn('LOAD_ATTR', instructions)
11301130
self.assertIn('CALL', instructions)
11311131

1132+
def test_folding_type_param(self):
1133+
get_code_fn_cls = lambda x: x.co_consts[0].co_consts[2]
1134+
get_code_type_alias = lambda x: x.co_consts[0].co_consts[3]
1135+
snippets = [
1136+
("def foo[T = 40 + 5](): pass", get_code_fn_cls),
1137+
("def foo[**P = 40 + 5](): pass", get_code_fn_cls),
1138+
("def foo[*Ts = 40 + 5](): pass", get_code_fn_cls),
1139+
("class foo[T = 40 + 5]: pass", get_code_fn_cls),
1140+
("class foo[**P = 40 + 5]: pass", get_code_fn_cls),
1141+
("class foo[*Ts = 40 + 5]: pass", get_code_fn_cls),
1142+
("type foo[T = 40 + 5] = 1", get_code_type_alias),
1143+
("type foo[**P = 40 + 5] = 1", get_code_type_alias),
1144+
("type foo[*Ts = 40 + 5] = 1", get_code_type_alias),
1145+
]
1146+
for snippet, get_code in snippets:
1147+
c = compile(snippet, "<dummy>", "exec")
1148+
code = get_code(c)
1149+
opcodes = list(dis.get_instructions(code))
1150+
instructions = [opcode.opname for opcode in opcodes]
1151+
args = [opcode.oparg for opcode in opcodes]
1152+
self.assertNotIn(40, args)
1153+
self.assertNotIn(5, args)
1154+
self.assertIn('LOAD_SMALL_INT', instructions)
1155+
self.assertIn(45, args)
1156+
11321157
def test_lineno_procedure_call(self):
11331158
def call():
11341159
(

Lib/test/test_opcache.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1666,7 +1666,8 @@ def to_bool_str():
16661666
def test_unpack_sequence(self):
16671667
def unpack_sequence_two_tuple():
16681668
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
1669-
a, b = 1, 2
1669+
t = 1, 2
1670+
a, b = t
16701671
self.assertEqual(a, 1)
16711672
self.assertEqual(b, 2)
16721673

@@ -1677,8 +1678,11 @@ def unpack_sequence_two_tuple():
16771678

16781679
def unpack_sequence_tuple():
16791680
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
1680-
a, = 1,
1681+
a, b, c, d = 1, 2, 3, 4
16811682
self.assertEqual(a, 1)
1683+
self.assertEqual(b, 2)
1684+
self.assertEqual(c, 3)
1685+
self.assertEqual(d, 4)
16821686

16831687
unpack_sequence_tuple()
16841688
self.assert_specialized(unpack_sequence_tuple, "UNPACK_SEQUENCE_TUPLE")

Lib/test/test_peepholer.py

+106-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def test_folding_of_tuples_of_constants(self):
154154
for line, elem in (
155155
('a = 1,2,3', (1, 2, 3)),
156156
('("a","b","c")', ('a', 'b', 'c')),
157-
('a,b,c = 1,2,3', (1, 2, 3)),
157+
('a,b,c,d = 1,2,3,4', (1, 2, 3, 4)),
158158
('(None, 1, None)', (None, 1, None)),
159159
('((1, 2), 3, 4)', ((1, 2), 3, 4)),
160160
):
@@ -1349,6 +1349,111 @@ def test_fold_tuple_of_constants(self):
13491349
]
13501350
self.cfg_optimization_test(same, same, consts=[])
13511351

1352+
def test_fold_constant_intrinsic_list_to_tuple(self):
1353+
INTRINSIC_LIST_TO_TUPLE = 6
1354+
1355+
# long tuple
1356+
consts = 1000
1357+
before = (
1358+
[('BUILD_LIST', 0, 0)] +
1359+
[('LOAD_CONST', 0, 0), ('LIST_APPEND', 1, 0)] * consts +
1360+
[('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0), ('RETURN_VALUE', None, 0)]
1361+
)
1362+
after = [
1363+
('LOAD_CONST', 1, 0),
1364+
('RETURN_VALUE', None, 0)
1365+
]
1366+
result_const = tuple(["test"] * consts)
1367+
self.cfg_optimization_test(before, after, consts=["test"], expected_consts=["test", result_const])
1368+
1369+
# empty list
1370+
before = [
1371+
('BUILD_LIST', 0, 0),
1372+
('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
1373+
('RETURN_VALUE', None, 0)
1374+
]
1375+
after = [
1376+
('LOAD_CONST', 0, 0),
1377+
('RETURN_VALUE', None, 0)
1378+
]
1379+
self.cfg_optimization_test(before, after, consts=[], expected_consts=[()])
1380+
1381+
# multiple BUILD_LIST 0: ([], 1, [], 2)
1382+
same = [
1383+
('BUILD_LIST', 0, 0),
1384+
('BUILD_LIST', 0, 0),
1385+
('LIST_APPEND', 1, 0),
1386+
('LOAD_SMALL_INT', 1, 0),
1387+
('LIST_APPEND', 1, 0),
1388+
('BUILD_LIST', 0, 0),
1389+
('LIST_APPEND', 1, 0),
1390+
('LOAD_SMALL_INT', 2, 0),
1391+
('LIST_APPEND', 1, 0),
1392+
('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
1393+
('RETURN_VALUE', None, 0)
1394+
]
1395+
self.cfg_optimization_test(same, same, consts=[])
1396+
1397+
# nested folding: (1, 1+1, 3)
1398+
before = [
1399+
('BUILD_LIST', 0, 0),
1400+
('LOAD_SMALL_INT', 1, 0),
1401+
('LIST_APPEND', 1, 0),
1402+
('LOAD_SMALL_INT', 1, 0),
1403+
('LOAD_SMALL_INT', 1, 0),
1404+
('BINARY_OP', get_binop_argval('NB_ADD'), 0),
1405+
('LIST_APPEND', 1, 0),
1406+
('LOAD_SMALL_INT', 3, 0),
1407+
('LIST_APPEND', 1, 0),
1408+
('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
1409+
('RETURN_VALUE', None, 0)
1410+
]
1411+
after = [
1412+
('LOAD_CONST', 0, 0),
1413+
('RETURN_VALUE', None, 0)
1414+
]
1415+
self.cfg_optimization_test(before, after, consts=[], expected_consts=[(1, 2, 3)])
1416+
1417+
# NOP's in between: (1, 2, 3)
1418+
before = [
1419+
('BUILD_LIST', 0, 0),
1420+
('NOP', None, 0),
1421+
('LOAD_SMALL_INT', 1, 0),
1422+
('NOP', None, 0),
1423+
('NOP', None, 0),
1424+
('LIST_APPEND', 1, 0),
1425+
('NOP', None, 0),
1426+
('LOAD_SMALL_INT', 2, 0),
1427+
('NOP', None, 0),
1428+
('NOP', None, 0),
1429+
('LIST_APPEND', 1, 0),
1430+
('NOP', None, 0),
1431+
('LOAD_SMALL_INT', 3, 0),
1432+
('NOP', None, 0),
1433+
('LIST_APPEND', 1, 0),
1434+
('NOP', None, 0),
1435+
('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
1436+
('RETURN_VALUE', None, 0)
1437+
]
1438+
after = [
1439+
('LOAD_CONST', 0, 0),
1440+
('RETURN_VALUE', None, 0)
1441+
]
1442+
self.cfg_optimization_test(before, after, consts=[], expected_consts=[(1, 2, 3)])
1443+
1444+
# no sequence start
1445+
same = [
1446+
('LOAD_SMALL_INT', 1, 0),
1447+
('LIST_APPEND', 1, 0),
1448+
('LOAD_SMALL_INT', 2, 0),
1449+
('LIST_APPEND', 1, 0),
1450+
('LOAD_SMALL_INT', 3, 0),
1451+
('LIST_APPEND', 1, 0),
1452+
('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
1453+
('RETURN_VALUE', None, 0)
1454+
]
1455+
self.cfg_optimization_test(same, same, consts=[])
1456+
13521457
def test_optimize_if_const_list(self):
13531458
before = [
13541459
('NOP', None, 0),

Programs/test_frozenmain.h

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)