Skip to content

Commit ac50aad

Browse files
committed
Fold set into frozenset in CFG
1 parent 9f1feb4 commit ac50aad

File tree

3 files changed

+74
-15
lines changed

3 files changed

+74
-15
lines changed

Lib/test/test_ast/test_ast.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3228,7 +3228,6 @@ def test_folding_comparator(self):
32283228
operators = [("in", ast.In()), ("not in", ast.NotIn())]
32293229
braces = [
32303230
("[", "]", ast.List, (1,)),
3231-
("{", "}", ast.Set, frozenset({1})),
32323231
]
32333232
for left, right, non_optimized_comparator, optimized_comparator in braces:
32343233
for op, node in operators:
@@ -3246,7 +3245,6 @@ def test_folding_iter(self):
32463245
code = "for _ in %s1%s: pass"
32473246
braces = [
32483247
("[", "]", ast.List, (1,)),
3249-
("{", "}", ast.Set, frozenset({1})),
32503248
]
32513249

32523250
for left, right, ast_cls, optimized_iter in braces:

Python/ast_opt.c

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -575,8 +575,7 @@ fold_subscr(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
575575
}
576576

577577
/* Change literal list or set of constants into constant
578-
tuple or frozenset respectively. Change literal list of
579-
non-constants into tuple.
578+
tuple. Change literal list of non-constants into tuple.
580579
Used for right operand of "in" and "not in" tests and for iterable
581580
in "for" loop and comprehensions.
582581
*/
@@ -597,12 +596,6 @@ fold_iter(expr_ty arg, PyArena *arena, _PyASTOptimizeState *state)
597596
/* Try to create a constant tuple. */
598597
newval = make_const_tuple(elts);
599598
}
600-
else if (arg->kind == Set_kind) {
601-
newval = make_const_tuple(arg->v.Set.elts);
602-
if (newval) {
603-
Py_SETREF(newval, PyFrozenSet_New(newval));
604-
}
605-
}
606599
else {
607600
return 1;
608601
}
@@ -618,8 +611,7 @@ fold_compare(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
618611

619612
ops = node->v.Compare.ops;
620613
args = node->v.Compare.comparators;
621-
/* Change literal list or set in 'in' or 'not in' into
622-
tuple or frozenset respectively. */
614+
/* Change literal list or set in 'in' or 'not in' into tuple. */
623615
i = asdl_seq_LEN(ops) - 1;
624616
int op = asdl_seq_GET(ops, i);
625617
if (op == In || op == NotIn) {

Python/flowgraph.c

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,18 @@ add_const(PyObject *newconst, PyObject *consts, PyObject *const_cache)
13361336
return (int)index;
13371337
}
13381338

1339+
static int
1340+
is_sequence_constant(cfg_instr *inst, int n)
1341+
{
1342+
for (int i = 0; i < n; i++) {
1343+
if (!loads_const(inst[i].i_opcode)) {
1344+
return 0;
1345+
}
1346+
}
1347+
return 1;
1348+
}
1349+
1350+
13391351
/* Replace LOAD_CONST c1, LOAD_CONST c2 ... LOAD_CONST cn, BUILD_TUPLE n
13401352
with LOAD_CONST (c1, c2, ... cn).
13411353
The consts table must still be in list form so that the
@@ -1353,17 +1365,60 @@ fold_tuple_on_constants(PyObject *const_cache,
13531365
assert(inst[n].i_opcode == BUILD_TUPLE);
13541366
assert(inst[n].i_oparg == n);
13551367

1368+
if (!is_sequence_constant(inst, n)) {
1369+
return SUCCESS;
1370+
}
1371+
1372+
/* Buildup new tuple of constants */
1373+
PyObject *newconst = PyTuple_New(n);
1374+
if (newconst == NULL) {
1375+
return ERROR;
1376+
}
13561377
for (int i = 0; i < n; i++) {
1357-
if (!loads_const(inst[i].i_opcode)) {
1358-
return SUCCESS;
1378+
int op = inst[i].i_opcode;
1379+
int arg = inst[i].i_oparg;
1380+
PyObject *constant = get_const_value(op, arg, consts);
1381+
if (constant == NULL) {
1382+
return ERROR;
13591383
}
1384+
PyTuple_SET_ITEM(newconst, i, constant);
1385+
}
1386+
int index = add_const(newconst, consts, const_cache);
1387+
if (index < 0) {
1388+
return ERROR;
1389+
}
1390+
for (int i = 0; i < n; i++) {
1391+
INSTR_SET_OP0(&inst[i], NOP);
1392+
}
1393+
INSTR_SET_OP1(&inst[n], LOAD_CONST, index);
1394+
return SUCCESS;
1395+
}
1396+
1397+
1398+
// Replaces const set with a frozenset.
1399+
// This should be used only in situations where we 100% sure that
1400+
// this set cannot be changed: where's constant set is a rhs in `for` loop
1401+
// or it's a rhs in `in` operation.
1402+
static int
1403+
fold_set_on_constants(PyObject *const_cache,
1404+
cfg_instr *inst,
1405+
int n, PyObject *consts)
1406+
{
1407+
/* Pre-conditions */
1408+
assert(PyDict_CheckExact(const_cache));
1409+
assert(PyList_CheckExact(consts));
1410+
assert(inst[n].i_opcode == BUILD_SET);
1411+
assert(inst[n].i_oparg == n);
1412+
1413+
if (!is_sequence_constant(inst, n)) {
1414+
return SUCCESS;
13601415
}
13611416

1362-
/* Buildup new tuple of constants */
13631417
PyObject *newconst = PyTuple_New(n);
13641418
if (newconst == NULL) {
13651419
return ERROR;
13661420
}
1421+
13671422
for (int i = 0; i < n; i++) {
13681423
int op = inst[i].i_opcode;
13691424
int arg = inst[i].i_oparg;
@@ -1373,6 +1428,13 @@ fold_tuple_on_constants(PyObject *const_cache,
13731428
}
13741429
PyTuple_SET_ITEM(newconst, i, constant);
13751430
}
1431+
1432+
PyObject *frozenset = PyFrozenSet_New(newconst);
1433+
if (frozenset == NULL) {
1434+
return ERROR;
1435+
}
1436+
Py_SETREF(newconst, frozenset);
1437+
13761438
int index = add_const(newconst, consts, const_cache);
13771439
if (index < 0) {
13781440
return ERROR;
@@ -1751,6 +1813,13 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
17511813
}
17521814
}
17531815
break;
1816+
case BUILD_SET:
1817+
if (nextop == CONTAINS_OP || nextop == GET_ITER) {
1818+
if (fold_set_on_constants(const_cache, inst-oparg, oparg, consts)) {
1819+
goto error;
1820+
}
1821+
}
1822+
break;
17541823
case POP_JUMP_IF_NOT_NONE:
17551824
case POP_JUMP_IF_NONE:
17561825
switch (target->i_opcode) {

0 commit comments

Comments
 (0)