Skip to content

Commit 084230e

Browse files
Eclips4iritkatriel
andauthored
gh-148817: Fold long lists/sets of constant elements into constant tuples/frozensets (#149016)
Fold long lists/sets of constant elements into constant tuples/frozensets. Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
1 parent 4dd8f0b commit 084230e

4 files changed

Lines changed: 218 additions & 22 deletions

File tree

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ Tools/cases_generator/ @markshannon
223223
Python/assemble.c @markshannon @iritkatriel
224224
Python/codegen.c @markshannon @iritkatriel
225225
Python/compile.c @markshannon @iritkatriel
226-
Python/flowgraph.c @markshannon @iritkatriel
226+
Python/flowgraph.c @markshannon @iritkatriel @eclips4
227227
Python/instruction_sequence.c @iritkatriel
228228
Python/symtable.c @JelleZijlstra @carljm
229229

Lib/test/test_peepholer.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2470,6 +2470,168 @@ def test_list_to_tuple_get_iter_is_safe(self):
24702470
self.assertEqual(b, [3, 2, 1, 0])
24712471
self.assertEqual(items, [])
24722472

2473+
def test_fold_constant_big_list_for_iter(self):
2474+
# for x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple
2475+
consts = 35
2476+
before = (
2477+
[("BUILD_LIST", 0, 1)] +
2478+
[("LOAD_CONST", 0, 2), ("LIST_APPEND", 1, 3)] * consts +
2479+
[("GET_ITER", 0, 4),
2480+
top := self.Label(),
2481+
("FOR_ITER", end := self.Label(), 5),
2482+
("STORE_FAST", 0, 6),
2483+
("JUMP", top, 7),
2484+
end,
2485+
("END_FOR", None, 8),
2486+
("POP_ITER", None, 9),
2487+
("LOAD_CONST", 0, 10),
2488+
("RETURN_VALUE", None, 11)]
2489+
)
2490+
after = [
2491+
("LOAD_CONST", 1, 3),
2492+
("GET_ITER", 0, 4),
2493+
top := self.Label(),
2494+
("FOR_ITER", end := self.Label(), 5),
2495+
("STORE_FAST", 0, 6),
2496+
("JUMP", top, 7),
2497+
end,
2498+
("END_FOR", None, 8),
2499+
("POP_ITER", None, 9),
2500+
("LOAD_CONST", 0, 10),
2501+
("RETURN_VALUE", None, 11),
2502+
]
2503+
result_const = tuple(["test"] * consts)
2504+
self.cfg_optimization_test(before, after, consts=["test"],
2505+
expected_consts=["test", result_const])
2506+
2507+
def test_fold_constant_big_set_for_iter(self):
2508+
# for x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset
2509+
before = [
2510+
("BUILD_SET", 0, 1),
2511+
("LOAD_SMALL_INT", 1, 2), ("SET_ADD", 1, 3),
2512+
("LOAD_SMALL_INT", 2, 4), ("SET_ADD", 1, 5),
2513+
("LOAD_SMALL_INT", 3, 6), ("SET_ADD", 1, 7),
2514+
("GET_ITER", 0, 8),
2515+
top := self.Label(),
2516+
("FOR_ITER", end := self.Label(), 9),
2517+
("STORE_FAST", 0, 10),
2518+
("JUMP", top, 11),
2519+
end,
2520+
("END_FOR", None, 12),
2521+
("POP_ITER", None, 13),
2522+
("LOAD_CONST", 0, 14),
2523+
("RETURN_VALUE", None, 15),
2524+
]
2525+
after = [
2526+
("LOAD_CONST", 1, 7),
2527+
("GET_ITER", 0, 8),
2528+
top := self.Label(),
2529+
("FOR_ITER", end := self.Label(), 9),
2530+
("STORE_FAST", 0, 10),
2531+
("JUMP", top, 11),
2532+
end,
2533+
("END_FOR", None, 12),
2534+
("POP_ITER", None, 13),
2535+
("LOAD_CONST", 0, 14),
2536+
("RETURN_VALUE", None, 15),
2537+
]
2538+
self.cfg_optimization_test(before, after, consts=["test"],
2539+
expected_consts=["test", frozenset({1, 2, 3})])
2540+
2541+
def test_fold_constant_list_to_tuple_for_iter(self):
2542+
INTRINSIC_LIST_TO_TUPLE = 6
2543+
before = [
2544+
("BUILD_LIST", 0, 1),
2545+
("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3),
2546+
("LOAD_SMALL_INT", 2, 4), ("LIST_APPEND", 1, 5),
2547+
("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7),
2548+
("CALL_INTRINSIC_1", INTRINSIC_LIST_TO_TUPLE, 8),
2549+
("GET_ITER", 0, 9),
2550+
top := self.Label(),
2551+
("FOR_ITER", end := self.Label(), 10),
2552+
("STORE_FAST", 0, 11),
2553+
("JUMP", top, 12),
2554+
end,
2555+
("END_FOR", None, 13),
2556+
("POP_ITER", None, 14),
2557+
("LOAD_CONST", 0, 15),
2558+
("RETURN_VALUE", None, 16),
2559+
]
2560+
after = [
2561+
("LOAD_CONST", 1, 8),
2562+
("GET_ITER", 0, 9),
2563+
top := self.Label(),
2564+
("FOR_ITER", end := self.Label(), 10),
2565+
("STORE_FAST", 0, 11),
2566+
("JUMP", top, 12),
2567+
end,
2568+
("END_FOR", None, 13),
2569+
("POP_ITER", None, 14),
2570+
("LOAD_CONST", 0, 15),
2571+
("RETURN_VALUE", None, 16),
2572+
]
2573+
self.cfg_optimization_test(before, after, consts=["test"],
2574+
expected_consts=["test", (1, 2, 3)])
2575+
2576+
def test_fold_constant_big_list_contains_op(self):
2577+
# x in [c1, c2, ..., cN] (N > 30) should fold to LOAD_CONST tuple
2578+
before = [
2579+
("LOAD_FAST", 0, 1),
2580+
("BUILD_LIST", 0, 2),
2581+
("LOAD_SMALL_INT", 1, 3), ("LIST_APPEND", 1, 4),
2582+
("LOAD_SMALL_INT", 2, 5), ("LIST_APPEND", 1, 6),
2583+
("LOAD_SMALL_INT", 3, 7), ("LIST_APPEND", 1, 8),
2584+
("CONTAINS_OP", 0, 9),
2585+
("RETURN_VALUE", None, 10),
2586+
]
2587+
after = [
2588+
("LOAD_FAST_BORROW", 0, 1),
2589+
("LOAD_CONST", 1, 8),
2590+
("CONTAINS_OP", 0, 9),
2591+
("RETURN_VALUE", None, 10),
2592+
]
2593+
self.cfg_optimization_test(before, after, consts=[None],
2594+
expected_consts=[None, (1, 2, 3)])
2595+
2596+
def test_fold_constant_big_set_contains_op(self):
2597+
# x in {c1, c2, ..., cN} (N > 30) should fold to LOAD_CONST frozenset
2598+
before = [
2599+
("LOAD_FAST", 0, 1),
2600+
("BUILD_SET", 0, 2),
2601+
("LOAD_SMALL_INT", 1, 3), ("SET_ADD", 1, 4),
2602+
("LOAD_SMALL_INT", 2, 5), ("SET_ADD", 1, 6),
2603+
("LOAD_SMALL_INT", 3, 7), ("SET_ADD", 1, 8),
2604+
("CONTAINS_OP", 0, 9),
2605+
("RETURN_VALUE", None, 10),
2606+
]
2607+
after = [
2608+
("LOAD_FAST_BORROW", 0, 1),
2609+
("LOAD_CONST", 1, 8),
2610+
("CONTAINS_OP", 0, 9),
2611+
("RETURN_VALUE", None, 10),
2612+
]
2613+
self.cfg_optimization_test(before, after, consts=[None],
2614+
expected_consts=[None, frozenset({1, 2, 3})])
2615+
2616+
def test_no_fold_big_list_for_iter_with_non_const(self):
2617+
same = [
2618+
("BUILD_LIST", 0, 1),
2619+
("LOAD_SMALL_INT", 1, 2), ("LIST_APPEND", 1, 3),
2620+
("LOAD_FAST_BORROW", 0, 4), ("LIST_APPEND", 1, 5),
2621+
("LOAD_SMALL_INT", 3, 6), ("LIST_APPEND", 1, 7),
2622+
("GET_ITER", 0, 8),
2623+
top := self.Label(),
2624+
("FOR_ITER", end := self.Label(), 9),
2625+
("STORE_FAST", 1, 10),
2626+
("JUMP", top, 11),
2627+
end,
2628+
("END_FOR", None, 12),
2629+
("POP_ITER", None, 13),
2630+
("LOAD_CONST", 0, 14),
2631+
("RETURN_VALUE", None, 15),
2632+
]
2633+
self.cfg_optimization_test(same, same, consts=["test"])
2634+
24732635

24742636
class OptimizeLoadFastTestCase(DirectCfgOptimizerTests):
24752637
def make_bb(self, insts):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fold large constant list and set literals used as the iterable of a
2+
:keyword:`for` loop or ``in``/``not in`` test into a constant
3+
:class:`tuple` or :class:`frozenset`, restoring an optimization
4+
previously done by the AST optimizer that was lost when constant
5+
folding moved to the CFG.

Python/flowgraph.c

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,34 +1569,48 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts,
15691569
}
15701570

15711571
/* Replace:
1572-
BUILD_LIST 0
1572+
BUILD_LIST/BUILD_SET 0
15731573
LOAD_CONST c1
1574-
LIST_APPEND 1
1574+
LIST_APPEND/SET_ADD 1
15751575
LOAD_CONST c2
1576-
LIST_APPEND 1
1576+
LIST_APPEND/SET_ADD 1
15771577
...
15781578
LOAD_CONST cN
1579-
LIST_APPEND 1
1580-
CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
1579+
LIST_APPEND/SET_ADD 1
1580+
[CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE] <-- optional
15811581
with:
15821582
LOAD_CONST (c1, c2, ... cN)
1583+
The instruction at `i` is either the LIST_TO_TUPLE intrinsic (so the
1584+
immediately preceding non-NOP instruction is expected to be a
1585+
LIST_APPEND, and only the BUILD_LIST/LIST_APPEND form is considered),
1586+
or the trailing LIST_APPEND or SET_ADD itself, in which case the
1587+
matching BUILD_LIST/BUILD_SET start is selected from its opcode, and
1588+
for sets the result is wrapped in a frozenset.
15831589
*/
15841590
static int
1585-
fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
1586-
PyObject *consts, PyObject *const_cache,
1587-
_Py_hashtable_t *consts_index)
1591+
fold_constant_seq_into_load_const(basicblock *bb, int i,
1592+
PyObject *consts, PyObject *const_cache,
1593+
_Py_hashtable_t *consts_index)
15881594
{
15891595
assert(PyDict_CheckExact(const_cache));
15901596
assert(PyList_CheckExact(consts));
15911597
assert(i >= 0);
15921598
assert(i < bb->b_iused);
15931599

1594-
cfg_instr *intrinsic = &bb->b_instr[i];
1595-
assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
1596-
assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
1597-
1600+
cfg_instr *target = &bb->b_instr[i];
1601+
assert(target->i_opcode == LIST_APPEND || target->i_opcode == SET_ADD ||
1602+
(target->i_opcode == CALL_INTRINSIC_1 &&
1603+
target->i_oparg == INTRINSIC_LIST_TO_TUPLE));
1604+
bool expected_append = target->i_opcode == CALL_INTRINSIC_1;
1605+
int append_op = expected_append ? LIST_APPEND : target->i_opcode;
1606+
assert(append_op == LIST_APPEND || append_op == SET_ADD);
1607+
int build_op = append_op == LIST_APPEND ? BUILD_LIST : BUILD_SET;
15981608
int consts_found = 0;
1599-
bool expect_append = true;
1609+
/* Walking backward from `i`, we expect LIST_APPEND/SET_ADD and
1610+
LOAD_CONST to alternate. If `i` is the trailing LIST_TO_TUPLE
1611+
intrinsic, the next instruction back is an APPEND. If `i` is the
1612+
trailing APPEND itself, the next instruction back is a LOAD_CONST. */
1613+
bool expect_append = expected_append;
16001614

16011615
for (int pos = i - 1; pos >= 0; pos--) {
16021616
cfg_instr *instr = &bb->b_instr[pos];
@@ -1607,7 +1621,7 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
16071621
continue;
16081622
}
16091623

1610-
if (opcode == BUILD_LIST && oparg == 0) {
1624+
if (opcode == build_op && oparg == 0) {
16111625
if (!expect_append) {
16121626
/* Not a sequence start. */
16131627
return SUCCESS;
@@ -1619,7 +1633,8 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
16191633
return ERROR;
16201634
}
16211635

1622-
for (int newpos = i - 1; newpos >= pos; newpos--) {
1636+
int newpos_start = expected_append ? i - 1 : i;
1637+
for (int newpos = newpos_start; newpos >= pos; newpos--) {
16231638
instr = &bb->b_instr[newpos];
16241639
if (instr->i_opcode == NOP) {
16251640
continue;
@@ -1636,11 +1651,20 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
16361651
nop_out(&instr, 1);
16371652
}
16381653
assert(consts_found == 0);
1639-
return instr_make_load_const(intrinsic, newconst, consts, const_cache, consts_index);
1654+
1655+
if (build_op == BUILD_SET) {
1656+
PyObject *frozen = PyFrozenSet_New(newconst);
1657+
Py_DECREF(newconst);
1658+
if (frozen == NULL) {
1659+
return ERROR;
1660+
}
1661+
newconst = frozen;
1662+
}
1663+
return instr_make_load_const(target, newconst, consts, const_cache, consts_index);
16401664
}
16411665

16421666
if (expect_append) {
1643-
if (opcode != LIST_APPEND || oparg != 1) {
1667+
if (opcode != append_op || oparg != 1) {
16441668
return SUCCESS;
16451669
}
16461670
}
@@ -2579,17 +2603,22 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts,
25792603
break;
25802604
case CALL_INTRINSIC_1:
25812605
if (oparg == INTRINSIC_LIST_TO_TUPLE) {
2582-
if (nextop == GET_ITER) {
2606+
RETURN_IF_ERROR(fold_constant_seq_into_load_const(bb, i, consts, const_cache, consts_index));
2607+
if (inst->i_opcode == CALL_INTRINSIC_1 && nextop == GET_ITER) {
25832608
INSTR_SET_OP0(inst, NOP);
25842609
}
2585-
else {
2586-
RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache, consts_index));
2587-
}
25882610
}
25892611
else if (oparg == INTRINSIC_UNARY_POSITIVE) {
25902612
RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache, consts_index));
25912613
}
25922614
break;
2615+
case LIST_APPEND:
2616+
case SET_ADD:
2617+
if (oparg == 1 && (nextop == GET_ITER || nextop == CONTAINS_OP)) {
2618+
RETURN_IF_ERROR(fold_constant_seq_into_load_const(
2619+
bb, i, consts, const_cache, consts_index));
2620+
}
2621+
break;
25932622
case BINARY_OP:
25942623
RETURN_IF_ERROR(fold_const_binop(bb, i, consts, const_cache, consts_index));
25952624
break;

0 commit comments

Comments
 (0)