Skip to content

gh-126835: Disable tuple folding in the AST optimizer #128802

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

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 5 additions & 52 deletions Lib/test/test_ast/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -3153,13 +3153,6 @@ def test_folding_binop(self):
):
self.assert_ast(result_code, non_optimized_target, optimized_target)

# Multiplication of constant tuples must be folded
code = "(1,) * 3"
non_optimized_target = self.wrap_expr(self.create_binop("*", ast.Tuple(elts=[ast.Constant(value=1)]), ast.Constant(value=3)))
optimized_target = self.wrap_expr(ast.Constant(eval(code)))

self.assert_ast(code, non_optimized_target, optimized_target)

def test_folding_unaryop(self):
code = "%s1"
operators = self.unaryop.keys()
Expand All @@ -3179,38 +3172,6 @@ def create_unaryop(operand):
):
self.assert_ast(result_code, non_optimized_target, optimized_target)

def test_folding_not(self):
code = "not (1 %s (1,))"
operators = {
"in": ast.In(),
"is": ast.Is(),
}
opt_operators = {
"is": ast.IsNot(),
"in": ast.NotIn(),
}

def create_notop(operand):
return ast.UnaryOp(op=ast.Not(), operand=ast.Compare(
left=ast.Constant(value=1),
ops=[operators[operand]],
comparators=[ast.Tuple(elts=[ast.Constant(value=1)])]
))

for op in operators.keys():
result_code = code % op
non_optimized_target = self.wrap_expr(create_notop(op))
optimized_target = self.wrap_expr(
ast.Compare(left=ast.Constant(1), ops=[opt_operators[op]], comparators=[ast.Constant(value=(1,))])
)

with self.subTest(
result_code=result_code,
non_optimized_target=non_optimized_target,
optimized_target=optimized_target
):
self.assert_ast(result_code, non_optimized_target, optimized_target)

def test_folding_format(self):
code = "'%s' % (a,)"

Expand All @@ -3230,15 +3191,6 @@ def test_folding_format(self):

self.assert_ast(code, non_optimized_target, optimized_target)


def test_folding_tuple(self):
code = "(1,)"

non_optimized_target = self.wrap_expr(ast.Tuple(elts=[ast.Constant(1)]))
optimized_target = self.wrap_expr(ast.Constant(value=(1,)))

self.assert_ast(code, non_optimized_target, optimized_target)

def test_folding_comparator(self):
code = "1 %s %s1%s"
operators = [("in", ast.In()), ("not in", ast.NotIn())]
Expand Down Expand Up @@ -3280,13 +3232,14 @@ def test_folding_iter(self):
self.assert_ast(code % (left, right), non_optimized_target, optimized_target)

def test_folding_subscript(self):
code = "(1,)[0]"
code = "'abcd'[0]"

non_optimized_target = self.wrap_expr(
ast.Subscript(value=ast.Tuple(elts=[ast.Constant(value=1)]), slice=ast.Constant(value=0))
ast.Subscript(value=ast.Constant(value='abcd'), slice=ast.Constant(value=0))
)
optimized_target = self.wrap_expr(
ast.JoinedStr(values=[ast.Constant(value='a')])
)
optimized_target = self.wrap_expr(ast.Constant(value=1))

self.assert_ast(code, non_optimized_target, optimized_target)

def test_folding_type_param_in_function_def(self):
Expand Down
8 changes: 0 additions & 8 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,14 +793,6 @@ def check_same_constant(const):
self.check_constant(f1, Ellipsis)
self.assertEqual(repr(f1()), repr(Ellipsis))

# Merge constants in tuple or frozenset
f1, f2 = lambda: "not a name", lambda: ("not a name",)
f3 = lambda x: x in {("not a name",)}
self.assertIs(f1.__code__.co_consts[0],
f2.__code__.co_consts[0][0])
self.assertIs(next(iter(f3.__code__.co_consts[0])),
f2.__code__.co_consts[0])

# {0} is converted to a constant frozenset({0}) by the peephole
# optimizer
f1, f2 = lambda x: x in {0}, lambda x: x in {0}
Expand Down
7 changes: 4 additions & 3 deletions Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -1652,8 +1652,9 @@ def to_bool_str():
@requires_specialization_ft
def test_unpack_sequence(self):
def unpack_sequence_two_tuple():
t = 1, 2
for _ in range(100):
a, b = 1, 2
a, b = t
self.assertEqual(a, 1)
self.assertEqual(b, 2)

Expand All @@ -1664,8 +1665,8 @@ def unpack_sequence_two_tuple():

def unpack_sequence_tuple():
for _ in range(100):
a, = 1,
self.assertEqual(a, 1)
a, b, c, d = 1, 2, 3, 4
self.assertEqual((a, b, c, d), (1, 2, 3, 4))

unpack_sequence_tuple()
self.assert_specialized(unpack_sequence_tuple, "UNPACK_SEQUENCE_TUPLE")
Expand Down
17 changes: 10 additions & 7 deletions Lib/test/test_peepholer.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ def test_folding_of_sets_of_constants(self):
('a in {1,2,3}', frozenset({1, 2, 3})),
('a not in {"a","b","c"}', frozenset({'a', 'c', 'b'})),
('a in {None, 1, None}', frozenset({1, None})),
('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
# Tuple folding is currently disabled in the AST optimizer
# ('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})),
):
with self.subTest(line=line):
Expand Down Expand Up @@ -239,7 +240,8 @@ def test_folding_of_binops_on_constants(self):
('a = 14%4', 2), # binary modulo
('a = 2+3', 5), # binary add
('a = 13-4', 9), # binary subtract
('a = (12,13)[1]', 13), # binary subscr
# Tuple folding is currently disabled in the AST optimizer
# ('a = (12,13)[1]', 13), # binary subscr
('a = 13 << 2', 52), # binary lshift
('a = 13 >> 2', 3), # binary rshift
('a = 13 & 7', 5), # binary and
Expand Down Expand Up @@ -458,11 +460,12 @@ def test_constant_folding(self):
'-3 * 5',
'2 * (3 * 4)',
'(2 * 3) * 4',
'(-1, 2, 3)',
'(1, -2, 3)',
'(1, 2, -3)',
'(1, 2, -3) * 6',
'lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}',
# Tuple folding is currently disabled in the AST optimizer
# '(-1, 2, 3)',
# '(1, -2, 3)',
# '(1, 2, -3)',
# '(1, 2, -3) * 6',
# 'lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}',
]
for e in exprs:
with self.subTest(e=e):
Expand Down
13 changes: 0 additions & 13 deletions Python/ast_opt.c
Original file line number Diff line number Diff line change
Expand Up @@ -555,18 +555,6 @@ make_const_tuple(asdl_expr_seq *elts)
return newval;
}

static int
fold_tuple(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
{
PyObject *newval;

if (node->v.Tuple.ctx != Load)
return 1;

newval = make_const_tuple(node->v.Tuple.elts);
return make_const(node, newval, arena);
}

static int
fold_subscr(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
{
Expand Down Expand Up @@ -837,7 +825,6 @@ astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
break;
case Tuple_kind:
CALL_SEQ(astfold_expr, expr, node_->v.Tuple.elts);
CALL(fold_tuple, expr_ty, node_);
break;
case Name_kind:
if (node_->v.Name.ctx == Load &&
Expand Down
15 changes: 15 additions & 0 deletions Python/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -1685,11 +1685,26 @@ codegen_typealias(compiler *c, stmt_ty s)
return SUCCESS;
}

static bool
is_const_tuple(asdl_expr_seq *elts)
{
for (Py_ssize_t i = 0; i < asdl_seq_LEN(elts); i++) {
expr_ty e = (expr_ty)asdl_seq_GET(elts, i);
if (e->kind != Constant_kind) {
return false;
}
}
return true;
}

/* Return false if the expression is a constant value except named singletons.
Return true otherwise. */
static bool
check_is_arg(expr_ty e)
{
if (e->kind == Tuple_kind) {
return !is_const_tuple(e->v.Tuple.elts);
}
if (e->kind != Constant_kind) {
return true;
}
Expand Down
Loading