Repository: python/cpython
Pull request #130769: gh-126835: Move constant tuple folding to CFG
Author: login: WolframAlph
Labels: name: skip news
Created: 2025-03-02T19:44:12Z | Merged: 2025-03-19T20:59:59Z

<!--
Thanks for your contribution!
Please read this comment in its entirety. It's quite important.

# Pull Request title

It should be in the following format:

```
gh-NNNNN: Summary of the changes made
```

Where: gh-NNNNN refers to the GitHub issue number.

Most PRs will require an issue number. Trivial changes, like fixing a typo, do not need an issue.

# Backport Pull Request title

If this is a backport PR (PR made against branches other than `main`),
please ensure that the PR title is in the following format:

```
[X.Y] <title from the original PR> (GH-NNNN)
```

Where: [X.Y] is the branch name, e.g. [3.6].

GH-NNNN refers to the PR number from `main`.

-->


<!-- gh-issue-number: gh-126835 -->
* Issue: gh-126835
<!-- /gh-issue-number -->



Diff:
diff --git a/Lib/test/test_ast/test_ast.py b/Lib/test/test_ast/test_ast.py
index e63ddb7d1fecc4..1b108ceddb1c11 100644
--- a/Lib/test/test_ast/test_ast.py
+++ b/Lib/test/test_ast/test_ast.py
@@ -153,22 +153,6 @@ def test_optimization_levels__debug__(self):
                         self.assertIsInstance(res.body[0].value, ast.Name)
                         self.assertEqual(res.body[0].value.id, expected)
 
-    def test_optimization_levels_const_folding(self):
-        folded = ('Expr', (1, 0, 1, 6), ('Constant', (1, 0, 1, 6), (1, 2), None))
-        not_folded = ('Expr', (1, 0, 1, 6),
-                         ('Tuple', (1, 0, 1, 6),
-                             [('Constant', (1, 1, 1, 2), 1, None),
-                             ('Constant', (1, 4, 1, 5), 2, None)], ('Load',)))
-
-        cases = [(-1, not_folded), (0, not_folded), (1, folded), (2, folded)]
-        for (optval, expected) in cases:
-            with self.subTest(optval=optval):
-                tree1 = ast.parse("(1, 2)", optimize=optval)
-                tree2 = ast.parse(ast.parse("(1, 2)"), optimize=optval)
-                for tree in [tree1, tree2]:
-                    res = to_tuple(tree.body[0])
-                    self.assertEqual(res, expected)
-
     def test_invalid_position_information(self):
         invalid_linenos = [
             (10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1)
@@ -3193,101 +3177,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_type_param_in_function_def(self):
-        code = "def foo[%s = (1, 2)](): pass"
-
-        unoptimized_tuple = ast.Tuple(elts=[ast.Constant(1), ast.Constant(2)])
-        unoptimized_type_params = [
-            ("T", "T", ast.TypeVar),
-            ("**P", "P", ast.ParamSpec),
-            ("*Ts", "Ts", ast.TypeVarTuple),
-        ]
-
-        for type, name, type_param in unoptimized_type_params:
-            result_code = code % type
-            optimized_target = self.wrap_statement(
-                ast.FunctionDef(
-                    name='foo',
-                    args=ast.arguments(),
-                    body=[ast.Pass()],
-                    type_params=[type_param(name=name, default_value=ast.Constant((1, 2)))]
-                )
-            )
-            non_optimized_target = self.wrap_statement(
-                ast.FunctionDef(
-                    name='foo',
-                    args=ast.arguments(),
-                    body=[ast.Pass()],
-                    type_params=[type_param(name=name, default_value=unoptimized_tuple)]
-                )
-            )
-            self.assert_ast(result_code, non_optimized_target, optimized_target)
-
-    def test_folding_type_param_in_class_def(self):
-        code = "class foo[%s = (1, 2)]: pass"
-
-        unoptimized_tuple = ast.Tuple(elts=[ast.Constant(1), ast.Constant(2)])
-        unoptimized_type_params = [
-            ("T", "T", ast.TypeVar),
-            ("**P", "P", ast.ParamSpec),
-            ("*Ts", "Ts", ast.TypeVarTuple),
-        ]
-
-        for type, name, type_param in unoptimized_type_params:
-            result_code = code % type
-            optimized_target = self.wrap_statement(
-                ast.ClassDef(
-                    name='foo',
-                    body=[ast.Pass()],
-                    type_params=[type_param(name=name, default_value=ast.Constant((1, 2)))]
-                )
-            )
-            non_optimized_target = self.wrap_statement(
-                ast.ClassDef(
-                    name='foo',
-                    body=[ast.Pass()],
-                    type_params=[type_param(name=name, default_value=unoptimized_tuple)]
-                )
-            )
-            self.assert_ast(result_code, non_optimized_target, optimized_target)
-
-    def test_folding_type_param_in_type_alias(self):
-        code = "type foo[%s = (1, 2)] = 1"
-
-        unoptimized_tuple = ast.Tuple(elts=[ast.Constant(1), ast.Constant(2)])
-        unoptimized_type_params = [
-            ("T", "T", ast.TypeVar),
-            ("**P", "P", ast.ParamSpec),
-            ("*Ts", "Ts", ast.TypeVarTuple),
-        ]
-
-        for type, name, type_param in unoptimized_type_params:
-            result_code = code % type
-            optimized_target = self.wrap_statement(
-                ast.TypeAlias(
-                    name=ast.Name(id='foo', ctx=ast.Store()),
-                    type_params=[type_param(name=name, default_value=ast.Constant((1, 2)))],
-                    value=ast.Constant(value=1),
-                )
-            )
-            non_optimized_target = self.wrap_statement(
-                ast.TypeAlias(
-                    name=ast.Name(id='foo', ctx=ast.Store()),
-                    type_params=[type_param(name=name, default_value=unoptimized_tuple)],
-                    value=ast.Constant(value=1),
-                )
-            )
-            self.assert_ast(result_code, non_optimized_target, optimized_target)
-
     def test_folding_match_case_allowed_expressions(self):
         def get_match_case_values(node):
             result = []
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 90998ffce6d4e1..0cc172c8b77237 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -554,7 +554,7 @@ def test_compile_async_generator(self):
         self.assertEqual(type(glob['ticker']()), AsyncGeneratorType)
 
     def test_compile_ast(self):
-        args = ("a*(1,2)", "f.py", "exec")
+        args = ("a*__debug__", "f.py", "exec")
         raw = compile(*args, flags = ast.PyCF_ONLY_AST).body[0]
         opt1 = compile(*args, flags = ast.PyCF_OPTIMIZED_AST).body[0]
         opt2 = compile(ast.parse(args[0]), *args[1:], flags = ast.PyCF_OPTIMIZED_AST).body[0]
@@ -565,14 +565,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
 
     def test_delattr(self):
         sys.spam = 1
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index b1dc770fe4d411..ce9c060641d6c5 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -793,9 +793,9 @@ def check_same_constant(const):
         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])
+                      f2.__code__.co_consts[1][0])
         self.assertIs(next(iter(f3.__code__.co_consts[1])),
-                      f2.__code__.co_consts[0])
+                      f2.__code__.co_consts[1])
 
         # {0} is converted to a constant frozenset({0}) by the peephole
         # optimizer
@@ -1129,6 +1129,31 @@ def foo(x):
                 self.assertIn('LOAD_ATTR', instructions)
                 self.assertIn('CALL', instructions)
 
+    def test_folding_type_param(self):
+        get_code_fn_cls = lambda x: x.co_consts[0].co_consts[2]
+        get_code_type_alias = lambda x: x.co_consts[0].co_consts[3]
+        snippets = [
+            ("def foo[T = 40 + 5](): pass", get_code_fn_cls),
+            ("def foo[**P = 40 + 5](): pass", get_code_fn_cls),
+            ("def foo[*Ts = 40 + 5](): pass", get_code_fn_cls),
+            ("class foo[T = 40 + 5]: pass", get_code_fn_cls),
+            ("class foo[**P = 40 + 5]: pass", get_code_fn_cls),
+            ("class foo[*Ts = 40 + 5]: pass", get_code_fn_cls),
+            ("type foo[T = 40 + 5] = 1", get_code_type_alias),
+            ("type foo[**P = 40 + 5] = 1", get_code_type_alias),
+            ("type foo[*Ts = 40 + 5] = 1", get_code_type_alias),
+        ]
+        for snippet, get_code in snippets:
+            c = compile(snippet, "<dummy>", "exec")
+            code = get_code(c)
+            opcodes = list(dis.get_instructions(code))
+            instructions = [opcode.opname for opcode in opcodes]
+            args = [opcode.oparg for opcode in opcodes]
+            self.assertNotIn(40, args)
+            self.assertNotIn(5, args)
+            self.assertIn('LOAD_SMALL_INT', instructions)
+            self.assertIn(45, args)
+
     def test_lineno_procedure_call(self):
         def call():
             (
diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py
index ce9c489f32cf7d..ac132465b227ad 100644
--- a/Lib/test/test_opcache.py
+++ b/Lib/test/test_opcache.py
@@ -1666,7 +1666,8 @@ def to_bool_str():
     def test_unpack_sequence(self):
         def unpack_sequence_two_tuple():
             for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
-                a, b = 1, 2
+                t = 1, 2
+                a, b = t
                 self.assertEqual(a, 1)
                 self.assertEqual(b, 2)
 
@@ -1677,8 +1678,11 @@ def unpack_sequence_two_tuple():
 
         def unpack_sequence_tuple():
             for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
-                a, = 1,
+                a, b, c, d = 1, 2, 3, 4
                 self.assertEqual(a, 1)
+                self.assertEqual(b, 2)
+                self.assertEqual(c, 3)
+                self.assertEqual(d, 4)
 
         unpack_sequence_tuple()
         self.assert_specialized(unpack_sequence_tuple, "UNPACK_SEQUENCE_TUPLE")
diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py
index 4644641144bee5..6de89c4043d3f7 100644
--- a/Lib/test/test_peepholer.py
+++ b/Lib/test/test_peepholer.py
@@ -154,7 +154,7 @@ def test_folding_of_tuples_of_constants(self):
         for line, elem in (
             ('a = 1,2,3', (1, 2, 3)),
             ('("a","b","c")', ('a', 'b', 'c')),
-            ('a,b,c = 1,2,3', (1, 2, 3)),
+            ('a,b,c,d = 1,2,3,4', (1, 2, 3, 4)),
             ('(None, 1, None)', (None, 1, None)),
             ('((1, 2), 3, 4)', ((1, 2), 3, 4)),
             ):
@@ -1349,6 +1349,111 @@ def test_fold_tuple_of_constants(self):
         ]
         self.cfg_optimization_test(same, same, consts=[])
 
+    def test_fold_constant_intrinsic_list_to_tuple(self):
+        INTRINSIC_LIST_TO_TUPLE = 6
+
+        # long tuple
+        consts = 1000
+        before = (
+            [('BUILD_LIST', 0, 0)] +
+            [('LOAD_CONST', 0, 0), ('LIST_APPEND', 1, 0)] * consts +
+            [('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0), ('RETURN_VALUE', None, 0)]
+        )
+        after = [
+            ('LOAD_CONST', 1, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        result_const = tuple(["test"] * consts)
+        self.cfg_optimization_test(before, after, consts=["test"], expected_consts=["test", result_const])
+
+        # empty list
+        before = [
+            ('BUILD_LIST', 0, 0),
+            ('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        after = [
+            ('LOAD_CONST', 0, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        self.cfg_optimization_test(before, after, consts=[], expected_consts=[()])
+
+        # multiple BUILD_LIST 0: ([], 1, [], 2)
+        same = [
+            ('BUILD_LIST', 0, 0),
+            ('BUILD_LIST', 0, 0),
+            ('LIST_APPEND', 1, 0),
+            ('LOAD_SMALL_INT', 1, 0),
+            ('LIST_APPEND', 1, 0),
+            ('BUILD_LIST', 0, 0),
+            ('LIST_APPEND', 1, 0),
+            ('LOAD_SMALL_INT', 2, 0),
+            ('LIST_APPEND', 1, 0),
+            ('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        self.cfg_optimization_test(same, same, consts=[])
+
+        # nested folding: (1, 1+1, 3)
+        before = [
+            ('BUILD_LIST', 0, 0),
+            ('LOAD_SMALL_INT', 1, 0),
+            ('LIST_APPEND', 1, 0),
+            ('LOAD_SMALL_INT', 1, 0),
+            ('LOAD_SMALL_INT', 1, 0),
+            ('BINARY_OP', get_binop_argval('NB_ADD'), 0),
+            ('LIST_APPEND', 1, 0),
+            ('LOAD_SMALL_INT', 3, 0),
+            ('LIST_APPEND', 1, 0),
+            ('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        after = [
+            ('LOAD_CONST', 0, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        self.cfg_optimization_test(before, after, consts=[], expected_consts=[(1, 2, 3)])
+
+        # NOP's in between: (1, 2, 3)
+        before = [
+            ('BUILD_LIST', 0, 0),
+            ('NOP', None, 0),
+            ('LOAD_SMALL_INT', 1, 0),
+            ('NOP', None, 0),
+            ('NOP', None, 0),
+            ('LIST_APPEND', 1, 0),
+            ('NOP', None, 0),
+            ('LOAD_SMALL_INT', 2, 0),
+            ('NOP', None, 0),
+            ('NOP', None, 0),
+            ('LIST_APPEND', 1, 0),
+            ('NOP', None, 0),
+            ('LOAD_SMALL_INT', 3, 0),
+            ('NOP', None, 0),
+            ('LIST_APPEND', 1, 0),
+            ('NOP', None, 0),
+            ('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        after = [
+            ('LOAD_CONST', 0, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        self.cfg_optimization_test(before, after, consts=[], expected_consts=[(1, 2, 3)])
+
+        # no sequence start
+        same = [
+            ('LOAD_SMALL_INT', 1, 0),
+            ('LIST_APPEND', 1, 0),
+            ('LOAD_SMALL_INT', 2, 0),
+            ('LIST_APPEND', 1, 0),
+            ('LOAD_SMALL_INT', 3, 0),
+            ('LIST_APPEND', 1, 0),
+            ('CALL_INTRINSIC_1', INTRINSIC_LIST_TO_TUPLE, 0),
+            ('RETURN_VALUE', None, 0)
+        ]
+        self.cfg_optimization_test(same, same, consts=[])
+
     def test_optimize_if_const_list(self):
         before = [
             ('NOP', None, 0),
diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h
index 1c7f74cc8039f2..281cf1c1bf9b27 100644
--- a/Programs/test_frozenmain.h
+++ b/Programs/test_frozenmain.h
@@ -9,19 +9,19 @@ unsigned char M_test_frozenmain[] = {
     30,0,89,1,78,8,0,0,0,0,0,0,0,0,0,0,
     0,0,0,0,0,0,0,0,32,0,50,0,0,0,0,0,
     0,0,80,4,43,26,0,0,0,0,0,0,0,0,0,0,
-    112,5,80,5,15,0,68,24,0,0,112,6,89,2,32,0,
-    80,6,89,6,11,0,80,7,89,5,89,6,43,26,0,0,
+    112,5,80,7,15,0,68,24,0,0,112,6,89,2,32,0,
+    80,5,89,6,11,0,80,6,89,5,89,6,43,26,0,0,
     0,0,0,0,0,0,0,0,11,0,48,4,50,1,0,0,
     0,0,0,0,30,0,73,26,0,0,8,0,29,0,80,1,
     34,0,41,8,233,0,0,0,0,78,122,18,70,114,111,122,
     101,110,32,72,101,108,108,111,32,87,111,114,108,100,122,8,
     115,121,115,46,97,114,103,118,218,6,99,111,110,102,105,103,
-    41,5,218,12,112,114,111,103,114,97,109,95,110,97,109,101,
-    218,10,101,120,101,99,117,116,97,98,108,101,218,15,117,115,
-    101,95,101,110,118,105,114,111,110,109,101,110,116,218,17,99,
-    111,110,102,105,103,117,114,101,95,99,95,115,116,100,105,111,
-    218,14,98,117,102,102,101,114,101,100,95,115,116,100,105,111,
-    122,7,99,111,110,102,105,103,32,122,2,58,32,41,7,218,
+    122,7,99,111,110,102,105,103,32,122,2,58,32,41,5,218,
+    12,112,114,111,103,114,97,109,95,110,97,109,101,218,10,101,
+    120,101,99,117,116,97,98,108,101,218,15,117,115,101,95,101,
+    110,118,105,114,111,110,109,101,110,116,218,17,99,111,110,102,
+    105,103,117,114,101,95,99,95,115,116,100,105,111,218,14,98,
+    117,102,102,101,114,101,100,95,115,116,100,105,111,41,7,218,
     3,115,121,115,218,17,95,116,101,115,116,105,110,116,101,114,
     110,97,108,99,97,112,105,218,5,112,114,105,110,116,218,4,
     97,114,103,118,218,11,103,101,116,95,99,111,110,102,105,103,
diff --git a/Python/ast_opt.c b/Python/ast_opt.c
index 4a191e919e412c..8ee322fdd15197 100644
--- a/Python/ast_opt.c
+++ b/Python/ast_opt.c
@@ -390,44 +390,6 @@ fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
     return 1;
 }
 
-static PyObject*
-make_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 NULL;
-        }
-    }
-
-    PyObject *newval = PyTuple_New(asdl_seq_LEN(elts));
-    if (newval == NULL) {
-        return NULL;
-    }
-
-    for (Py_ssize_t i = 0; i < asdl_seq_LEN(elts); i++) {
-        expr_ty e = (expr_ty)asdl_seq_GET(elts, i);
-        PyObject *v = e->v.Constant.value;
-        PyTuple_SET_ITEM(newval, i, Py_NewRef(v));
-    }
-    return newval;
-}
-
-static int
-fold_tuple(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
-{
-    if (state->syntax_check_only) {
-        return 1;
-    }
-    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 astfold_mod(mod_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
 static int astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
 static int astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
@@ -620,7 +582,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 (state->syntax_check_only) {
diff --git a/Python/codegen.c b/Python/codegen.c
index e1f647451f7002..44d23ca6d93014 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -1688,11 +1688,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;
     }
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index 8154129090b222..cf3e74005ce1f8 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -1482,6 +1482,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return instr_make_load_const(instr, const_tuple, consts, const_cache);
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            if (!expect_append) {
+                /* Not a sequence start. */
+                return SUCCESS;
+            }
+
+            /* Sequence start, we are done. */
+            PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found);
+            if (newconst == NULL) {
+                return ERROR;
+            }
+
+            for (int newpos = i - 1; newpos >= pos; newpos--) {
+                instr = &bb->b_instr[newpos];
+                if (instr->i_opcode == NOP) {
+                    continue;
+                }
+                if (loads_const(instr->i_opcode)) {
+                    PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
+                    if (constant == NULL) {
+                        Py_DECREF(newconst);
+                        return ERROR;
+                    }
+                    assert(consts_found > 0);
+                    PyTuple_SET_ITEM(newconst, --consts_found, constant);
+                }
+                nop_out(&instr, 1);
+            }
+            assert(consts_found == 0);
+            return instr_make_load_const(intrinsic, newconst, consts, const_cache);
+        }
+
+        if (expect_append) {
+            if (opcode != LIST_APPEND || oparg != 1) {
+                return SUCCESS;
+            }
+        }
+        else {
+            if (!loads_const(opcode)) {
+                return SUCCESS;
+            }
+            consts_found++;
+        }
+
+        expect_append = !expect_append;
+    }
+
+    /* Did not find sequence start. */
+    return SUCCESS;
+}
+
 #define MIN_CONST_SEQUENCE_SIZE 3
 /*
 Optimize lists and sets for:
@@ -2378,9 +2467,13 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    if (nextop == GET_ITER) {
+                        INSTR_SET_OP0(inst, NOP);
+                    }
+                    else {
+                        RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache));
+                    }
                 }
                 else if (oparg == INTRINSIC_UNARY_POSITIVE) {
                     RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));


Reviews:
user: login: picnixz
body: Small style comments. It's always better to split assertions into two when possible to know which condition failed.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-02T21:36:13Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T11:07:39Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:22:51Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:25:33Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:34:24Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:39:43Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T11:58:29Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T12:00:29Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T12:03:12Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T12:05:19Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T12:09:53Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T12:11:20Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T12:14:36Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T12:18:10Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T12:24:37Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T12:30:00Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T12:43:36Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T14:15:42Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T14:16:15Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T15:07:01Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T15:23:34Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T15:36:14Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T15:39:46Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T16:50:50Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T17:14:13Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T17:16:46Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T17:18:49Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T17:44:29Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T18:29:33Z
user: login: Eclips4
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T19:59:37Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T22:35:16Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T22:42:14Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T06:51:07Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T06:59:12Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T07:12:26Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T19:38:07Z
user: login: iritkatriel
body: I think it's a bit simpler if we grab two instructions in each iteration.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T22:14:11Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T13:09:50Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T13:14:39Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T13:16:13Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T13:28:56Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T13:34:54Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T14:02:01Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T21:52:45Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T21:59:41Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T22:29:00Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T22:34:24Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T22:35:56Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T22:38:55Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T22:40:11Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T22:41:25Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T22:52:17Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T23:07:22Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T23:09:58Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T23:11:48Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T23:14:11Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T23:16:49Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T23:38:14Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T23:40:45Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-06T23:41:55Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T08:02:24Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T08:06:52Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T11:34:09Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T11:36:48Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T11:41:39Z
user: login: iritkatriel
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-07T11:45:59Z
user: login: WolframAlph
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T11:53:13Z
user: login: iritkatriel
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-19T20:13:15Z

Review comments:
diff_hunk: @@ -1452,6 +1452,96 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND
+    LOAD_CONST c2
+    LIST_APPEND
+    ...
+    LOAD_CONST cN
+    LIST_APPEND
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0 && i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(
+        intrinsic->i_opcode == CALL_INTRINSIC_1 &&
+        intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE
+    );
path: Python/flowgraph.c
user: login: picnixz
body: ```suggestion
    assert(PyDict_CheckExact(const_cache));
    assert(PyList_CheckExact(consts));
    assert(i >= 0);
    assert(i < bb->b_iused);
    cfg_instr *intrinsic = &bb->b_instr[i];
    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
```
created_at: 2025-03-02T21:29:41Z
author_association: MEMBER
diff_hunk: @@ -2295,8 +2385,12 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 break;
             case CALL_INTRINSIC_1:
                 // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    if (nextop == GET_ITER) {
+                        INSTR_SET_OP0(inst, NOP);
+                    } else {
+                        fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache);
+                    }
path: Python/flowgraph.c
user: login: picnixz
body: ```suggestion
                    }
                    else {
                        fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache);
                    }
```
created_at: 2025-03-02T21:35:15Z
author_association: MEMBER
diff_hunk: @@ -566,14 +566,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
path: Lib/test/test_builtin.py
user: login: picnixz
body: I'm a bit surprised here. I thought that `__debug__` would be `False` because `-O` should remove `assert` statements and `if __debug__` statements right?

So how come is `__debug__` actually True in this case? shouldn't it be False? (maybe it's the expected behaviour but I'm surprised)
created_at: 2025-03-03T10:46:49Z
author_association: MEMBER
diff_hunk: @@ -1693,11 +1693,26 @@ codegen_typealias(compiler *c, stmt_ty s)
     return SUCCESS;
 }
 
+static bool
+is_const_tuple(asdl_expr_seq *elts)
path: Python/codegen.c
user: login: picnixz
body: What about nested tuples?
created_at: 2025-03-03T10:49:26Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
path: Python/flowgraph.c
user: login: picnixz
body: Maybe we could pre-allocate a bit more than just 0 items, say something like 5*2 (if i > 0). It could speed-up (but this needs to be checked) small cases, as we won't need to resize the list every time. If there's an error, then we would have overallocated but it doesn't really matter as it's an error case.

Can you perform some benchmarks?
created_at: 2025-03-03T11:06:00Z
author_association: MEMBER
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: picnixz
body: Err... isn't the comment reversed? seems to me that we're actually transforming a tuple into a list instead
created_at: 2025-03-03T11:06:55Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
path: Python/flowgraph.c
user: login: WolframAlph
body: Problem with pre-allocation is that we would need to manually set list items (and keep track of all that) and cannot rely on `append`. Considering that we don't know in advance how many constants there would be, I think empty list + appending is cleaner way. Performance-wise, this is not hot code, so I am not sure it's worth benchmarking, as the rest of constant foldings migrated to CFG were not benchmarked either.
created_at: 2025-03-03T11:22:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: WolframAlph
body: This is old comment. I just moved it to the corresponding `if`. It has nothing to do with this PR.
created_at: 2025-03-03T11:25:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1693,11 +1693,26 @@ codegen_typealias(compiler *c, stmt_ty s)
     return SUCCESS;
 }
 
+static bool
+is_const_tuple(asdl_expr_seq *elts)
path: Python/codegen.c
user: login: WolframAlph
body: Good point. This is actually common problem with constant folding migration to the peepholer. Please take a look at: https://github.com/python/cpython/issues/126835#issuecomment-2657348272. Similar problem, and we ended up leaving as it is with having best effort to emit the warning.
created_at: 2025-03-03T11:34:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1693,11 +1693,26 @@ codegen_typealias(compiler *c, stmt_ty s)
     return SUCCESS;
 }
 
+static bool
+is_const_tuple(asdl_expr_seq *elts)
path: Python/codegen.c
user: login: WolframAlph
body: But maybe we could do it recursively here actually, let me check.
created_at: 2025-03-03T11:39:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
path: Python/flowgraph.c
user: login: picnixz
body: OOF, I actually overlooked the `if (!loads_const(instr->i_opcode))` part!

I think the function should be renamed "fold_intrinsic_list_to_tuple" as the comment may hint that we're only calling this function with a list with constant values.
created_at: 2025-03-03T11:58:29Z
author_association: MEMBER
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: picnixz
body: No I meant, even if it's an old one, isn't it incorrect? Or am I reading it wrong? because I understand the comment as "transform (\*foo, \*bar) into [\*foo, \*bar]", but the intrinsic here is "list to tuple".
created_at: 2025-03-03T12:00:29Z
author_association: MEMBER
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: WolframAlph
body: I think it tries to say that we are preventing intrinsic list to tuple in this case, but I agree it looks confusing. As if we had tuple and converted it to list.
created_at: 2025-03-03T12:03:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: picnixz
body: I think you can change/improve the comment in that case (let's ask @iritkatriel as well)
created_at: 2025-03-03T12:05:18Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
path: Python/flowgraph.c
user: login: WolframAlph
body: Are we set with the pre-allocation then? Also, for the context: in reality this pattern would occur only if there are at least `STACK_USE_GUIDLINE` (30+) constants. Due to `STACK_USE_GUIDLINE` (which is in `codegen.c`) we cannot emit more subsequent `LOAD_CONST`s than 30. This function tries to fold those cases as well to be consistent with how long tuples were folded before. So we can have 30-30+x constants here. Pre-allocating to 30 would still make it less readable and maintainable IMO.
created_at: 2025-03-03T12:09:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
path: Python/flowgraph.c
user: login: picnixz
body: > Are we set with the pre-allocation then

No need for pre-allocation, sorry! just maybe renaming the function or indicate that it does not optimize other kind of BUILD_LIST sequences.
created_at: 2025-03-03T12:11:20Z
author_association: MEMBER
diff_hunk: @@ -1693,11 +1693,26 @@ codegen_typealias(compiler *c, stmt_ty s)
     return SUCCESS;
 }
 
+static bool
+is_const_tuple(asdl_expr_seq *elts)
path: Python/codegen.c
user: login: picnixz
body: > Similar problem, and we ended up leaving as it is with having best effort to emit the warning.

Let's maybe leave it as is for now and add a comment saying we don't optimize nested tuples for now. ~~Because we would also have something like `(VERY_LONG_CONSTANT_TUPLE, some_non_constant_value)`.~~

~~If we start optimizing it, depending on when we're doing the recursion we'll end up doing useless checks.~~ Until we have a real use case, let's keep it simple.
created_at: 2025-03-03T12:14:36Z
author_association: MEMBER
diff_hunk: @@ -1693,11 +1693,26 @@ codegen_typealias(compiler *c, stmt_ty s)
     return SUCCESS;
 }
 
+static bool
+is_const_tuple(asdl_expr_seq *elts)
path: Python/codegen.c
user: login: WolframAlph
body: We could do `is_const_tuple` recursively for tuple elements, actually.
>Let's maybe leave it as is for now and add a comment saying we don't optimize nested tuples for now

One of those cases when I don't think I have strong opinion on what would be the best, so I would do as core devs suggest.
created_at: 2025-03-03T12:18:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -566,14 +566,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
path: Lib/test/test_builtin.py
user: login: WolframAlph
body: So we are testing `PyCF_ONLY_AST` vs `PyCF_OPTIMIZED_AST` flags passed in to `compile` here. This is how `__debug__` is optimized: https://github.com/python/cpython/blob/990ad272f66fe6a50087ad044725bb0f9f8e181d/Python/ast_opt.c#L510-L516
`state->optimize` is based on `PyCF_OPTIMIZED_AST` flag, so everything is as expected.
created_at: 2025-03-03T12:24:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -566,14 +566,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
path: Lib/test/test_builtin.py
user: login: picnixz
body: I still don't understand what `__debug__` would be True and not False in this case.

- `state->optimize` would be 1 in this case (or is it not 1?)
- `PyBool_FromLong(!state->optimize)` would be False

Why is it True in the above test? (or am I missing something critical?)
created_at: 2025-03-03T12:29:59Z
author_association: MEMBER
diff_hunk: @@ -566,14 +566,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
path: Lib/test/test_builtin.py
user: login: WolframAlph
body: >state->optimize is based on PyCF_OPTIMIZED_AST flag, so everything is as expected.

Sorry, actually it is based on `optimize` parameter of `compile` itself, `PyCF_OPTIMIZED_AST` only controls whether we run ast optimizer. By default `optimize=-1`. Here is what is going on:
```python
compile("a*__debug__", "f.py", "exec", flags=ast.PyCF_OPTIMIZED_AST).body[0]  # __debug__ = True
compile("a*__debug__", "f.py", "exec", flags=ast.PyCF_OPTIMIZED_AST, optimize=1).body[0]  # __debug__ = False
```
created_at: 2025-03-03T12:43:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1693,11 +1693,26 @@ codegen_typealias(compiler *c, stmt_ty s)
     return SUCCESS;
 }
 
+static bool
+is_const_tuple(asdl_expr_seq *elts)
path: Python/codegen.c
user: login: iritkatriel
body: > Let's maybe leave it as is for now and add a comment saying we don't optimize nested tuples for now. 

I'm not sure what the purpose of such a comment would be. If we think we should handle nested tuples in the future then we should create an issue for it.
created_at: 2025-03-03T14:15:42Z
author_association: MEMBER
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: iritkatriel
body: Yes, if the comment is misleading then by all means improve it.
created_at: 2025-03-03T14:16:15Z
author_association: MEMBER
diff_hunk: @@ -3138,101 +3122,6 @@ def test_folding_format(self):
 
         self.assert_ast(code, non_optimized_target, optimized_target)
 
-
path: Lib/test/test_ast/test_ast.py
user: login: iritkatriel
body: I think we need replacements for all these tests in test_compile.
created_at: 2025-03-03T14:52:01Z
author_association: MEMBER
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: WolframAlph
body: I'd rather we remove it. Related piece of code looks quite straightforward.
created_at: 2025-03-03T15:07:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -566,14 +566,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
path: Lib/test/test_builtin.py
user: login: picnixz
body: Oh. I see. Thanks
created_at: 2025-03-03T15:23:33Z
author_association: MEMBER
diff_hunk: @@ -1693,11 +1693,26 @@ codegen_typealias(compiler *c, stmt_ty s)
     return SUCCESS;
 }
 
+static bool
+is_const_tuple(asdl_expr_seq *elts)
path: Python/codegen.c
user: login: picnixz
body: Let's open an issue to track it and discuss what to do 
created_at: 2025-03-03T15:36:14Z
author_association: MEMBER
diff_hunk: @@ -566,14 +566,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
path: Lib/test/test_builtin.py
user: login: picnixz
body: This makes me wonder whether we should actually document that behaviour a bit more precisely. We do mention `PyCF_OPTIMIZED_AST` in the docs and `optimize` but we don't explain that `optimize` is for `-O` and `-OO`: https://docs.python.org/3/library/ast.html#ast.parse.
created_at: 2025-03-03T15:39:45Z
author_association: MEMBER
diff_hunk: @@ -1666,7 +1666,8 @@ def to_bool_str():
     def test_unpack_sequence(self):
         def unpack_sequence_two_tuple():
             for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
-                a, b = 1, 2
+                t = 1, 2
+                a, b = t
path: Lib/test/test_opcache.py
user: login: iritkatriel
body: Why is this change necessary?
created_at: 2025-03-03T16:22:32Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: iritkatriel
body: TBH, I'm not sure I like this. It's more complex than whatever was in ast_opt.c.

Perhaps it would be simpler to detect this case in `starunpack_helper_impl`?
created_at: 2025-03-03T16:50:25Z
author_association: MEMBER
diff_hunk: @@ -1666,7 +1666,8 @@ def to_bool_str():
     def test_unpack_sequence(self):
         def unpack_sequence_two_tuple():
             for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
-                a, b = 1, 2
+                t = 1, 2
+                a, b = t
path: Lib/test/test_opcache.py
user: login: WolframAlph
body: It no longer generates `UNPACK_SEQUENCE` if written directly.
created_at: 2025-03-03T17:14:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: WolframAlph
body: >Perhaps it would be simpler to detect this case in starunpack_helper_impl?

Detect and fold right at codegen stage or just generate x `LOAD_CONST`s and `BUILD_TUPLE` and rely on peepholer to optimize?
created_at: 2025-03-03T17:16:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: iritkatriel
body: Whichever is simpler.
created_at: 2025-03-03T17:18:49Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: WolframAlph
body: Relying on peepholer to optimize feels a bit wrong. Folding right at codegen shouldn't be difficult. Although I am not sure how good it is to fold during codegen stage.
created_at: 2025-03-03T17:44:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: WolframAlph
body: I've moved folding to codegen: https://github.com/python/cpython/pull/130769/commits/70ce2c81da3e4d64a87ef043c9bdd6d982fb66a8 (no tests added yet). Does it look better to you? Requires extra pass though. We could also rearrange some code to make it more readable (make single condition `big && tuple` for example?).
created_at: 2025-03-03T18:29:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -566,14 +566,14 @@ def test_compile_ast(self):
             self.assertIsInstance(tree.value.left, ast.Name)
             self.assertEqual(tree.value.left.id, 'a')
 
-        raw_right = raw.value.right  # expect Tuple((1, 2))
-        self.assertIsInstance(raw_right, ast.Tuple)
-        self.assertListEqual([elt.value for elt in raw_right.elts], [1, 2])
+        raw_right = raw.value.right
+        self.assertIsInstance(raw_right, ast.Name)
+        self.assertEqual(raw_right.id, "__debug__")
 
         for opt in [opt1, opt2]:
-            opt_right = opt.value.right  # expect Constant((1,2))
+            opt_right = opt.value.right
             self.assertIsInstance(opt_right, ast.Constant)
-            self.assertEqual(opt_right.value, (1, 2))
+            self.assertEqual(opt_right.value, True)
path: Lib/test/test_builtin.py
user: login: Eclips4
body: I personally don't think that we should document it. If we do, some users might rely on it, and if we decide to change its behavior in the future, nobody will complain (at least, nobody should complain since there wouldn't be any guarantees 😄).
created_at: 2025-03-03T19:59:37Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: iritkatriel
body: > Relying on peepholer to optimize feels a bit wrong.

What do you mean?

created_at: 2025-03-03T22:35:16Z
author_association: MEMBER
diff_hunk: @@ -3235,6 +3250,25 @@ starunpack_helper_impl(compiler *c, location loc,
     }
     int sequence_built = 0;
     if (big) {
+        if (tuple) {
+            PyObject *newconst = PyTuple_New(n);
+            if (newconst == NULL) {
+                return ERROR;
+            }
+            bool is_const = true;
+            for (Py_ssize_t i = 0; i < n; i++) {
+                expr_ty elt = asdl_seq_GET(elts, i);
+                if (elt->kind != Constant_kind) {
+                    is_const = false;
+                    Py_DECREF(newconst);
+                    break;
+                }
+                PyTuple_SET_ITEM(newconst, i, Py_NewRef(elt->v.Constant.value));
+            }
+            if (is_const) {
+                return codegen_addop_load_const(c, loc, newconst);
path: Python/codegen.c
user: login: iritkatriel
body: If both `big` and `seen_star` are true, we shouldn't return here, right?
created_at: 2025-03-03T22:42:13Z
author_association: MEMBER
diff_hunk: @@ -3235,6 +3250,25 @@ starunpack_helper_impl(compiler *c, location loc,
     }
     int sequence_built = 0;
     if (big) {
+        if (tuple) {
+            PyObject *newconst = PyTuple_New(n);
+            if (newconst == NULL) {
+                return ERROR;
+            }
+            bool is_const = true;
+            for (Py_ssize_t i = 0; i < n; i++) {
+                expr_ty elt = asdl_seq_GET(elts, i);
+                if (elt->kind != Constant_kind) {
+                    is_const = false;
+                    Py_DECREF(newconst);
+                    break;
+                }
+                PyTuple_SET_ITEM(newconst, i, Py_NewRef(elt->v.Constant.value));
+            }
+            if (is_const) {
+                return codegen_addop_load_const(c, loc, newconst);
path: Python/codegen.c
user: login: WolframAlph
body: Right. If `seen_star=true` then `is_const` would be false. We could add `seen_star` to the condition (`if (tuple && !seen_star)`) so we don't waste iterations if `seen_star=true` as we already know it is not constant.
created_at: 2025-03-04T06:51:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: WolframAlph
body: I mean that it feels wrong to produce unoptimized code and expect that it must be optimized in the next step of a pipeline. Putting responsibility on the next step in a pipeline, essentially depending on it.
created_at: 2025-03-04T06:59:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
path: Python/flowgraph.c
user: login: iritkatriel
body: We do that all over the place, to keep the optimization logic in one place.
created_at: 2025-03-04T07:12:26Z
author_association: MEMBER
diff_hunk: @@ -2294,9 +2383,14 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
path: Python/flowgraph.c
user: login: iritkatriel
body: Sure.
created_at: 2025-03-05T19:38:07Z
author_association: MEMBER
diff_hunk: @@ -2294,9 +2383,13 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
                 RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
                 break;
             case CALL_INTRINSIC_1:
-                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
-                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
-                    INSTR_SET_OP0(inst, NOP);
+                if (oparg == INTRINSIC_LIST_TO_TUPLE) {
+                    if (nextop == GET_ITER) {
+                        INSTR_SET_OP0(inst, NOP);
+                    }
+                    else {
+                        fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache);
path: Python/flowgraph.c
user: login: iritkatriel
body: ```suggestion
                        RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache));
```
created_at: 2025-03-05T21:23:38Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    bool expect_append = true;
+
+    for (int pos = i-1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+
+        if (instr->i_opcode == NOP) {
+            continue;
+        }
+
+        if (instr->i_opcode == BUILD_LIST && instr->i_oparg == 0) {
+            if (!expect_append) {
+                /* Not a sequence start */
+                goto exit;
+            }
+            /* Sequence start, we are done. */
+            if (PyList_Reverse(list) < 0) {
+                goto error;
+            }
+            PyObject *newconst = PyList_AsTuple(list);
+            if (newconst == NULL) {
+                goto error;
+            }
+            Py_DECREF(list);
+            int nops = (int)PyTuple_Size(newconst) * 2 + 1;
+            nop_out(bb, i-1, nops);
+            return instr_make_load_const(intrinsic, newconst, consts, const_cache);
+        }
+
+        if (expect_append) {
+            if (!(instr->i_opcode == LIST_APPEND && instr->i_oparg == 1)) {
+                goto exit;
+            }
+        }
+        else {
+            if (!loads_const(instr->i_opcode)) {
+                goto exit;
+            }
+            PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
+            if (constant == NULL) {
+                goto error;
+            }
+            int r = PyList_Append(list, constant);
+            Py_DECREF(constant);
+            if (r < 0) {
+                goto error;
+            }
+        }
+
+        expect_append = !expect_append;
path: Python/flowgraph.c
user: login: iritkatriel
body: ```suggestion
        if (!(instr->i_opcode == LIST_APPEND && instr->i_oparg == 1)) {
            goto exit;
        }
        pos--;
        instr = &bb->b_instr[pos];
        if (!loads_const(instr->i_opcode)) {
            goto exit;
        }
        PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
        if (constant == NULL) {
            goto error;
        }
        int r = PyList_Append(list, constant);
        Py_DECREF(constant);
        if (r < 0) {
            goto error;
        }
```
created_at: 2025-03-05T22:11:49Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    bool expect_append = true;
+
+    for (int pos = i-1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+
+        if (instr->i_opcode == NOP) {
+            continue;
+        }
+
+        if (instr->i_opcode == BUILD_LIST && instr->i_oparg == 0) {
+            if (!expect_append) {
+                /* Not a sequence start */
+                goto exit;
+            }
path: Python/flowgraph.c
user: login: iritkatriel
body: ```suggestion
```
created_at: 2025-03-05T22:12:49Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,95 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    bool expect_append = true;
path: Python/flowgraph.c
user: login: iritkatriel
body: ```suggestion
```
created_at: 2025-03-05T22:13:08Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,92 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    for (int pos = i-1; pos >= 0; pos--) {
path: Python/flowgraph.c
user: login: iritkatriel
body: For performance reasons, I would do this in two passes. The first checks if it's a const tuple, and only if it is we allocate a list.  The second pass doesn't need to check anymore, just push every other item to the list and NOPs the instructions.
created_at: 2025-03-06T13:09:16Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,92 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    for (int pos = i-1; pos >= 0; pos--) {
path: Python/flowgraph.c
user: login: WolframAlph
body: I see. Your concern is list resizing when appending?
created_at: 2025-03-06T13:14:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,92 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    for (int pos = i-1; pos >= 0; pos--) {
path: Python/flowgraph.c
user: login: iritkatriel
body: That, and also allocating memory that we don't need.
created_at: 2025-03-06T13:16:13Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,92 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    for (int pos = i-1; pos >= 0; pos--) {
path: Python/flowgraph.c
user: login: WolframAlph
body: Additionally, I think checking 2 instructions per iteration is error prone. Second instruction could be `NOP`, which breaks implementation. I can't think of scenario where it could happen because we fold everything into last instruction of every sequence, and various tests did not catch such scenarios, but still I think we should expect that.
created_at: 2025-03-06T13:28:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,92 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    for (int pos = i-1; pos >= 0; pos--) {
path: Python/flowgraph.c
user: login: WolframAlph
body: >various tests did not catch such scenarios

Actually there are not a lot of tests for constant intrinsic list to tuple folding, except for those I wrote. There is test with nested folding, but it works fine because we fold into last instruction of a sequence with is right before `LIST_APPEND`. I will add tests to catch that.
created_at: 2025-03-06T13:34:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,92 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return ERROR;
+    }
+
+    for (int pos = i-1; pos >= 0; pos--) {
path: Python/flowgraph.c
user: login: iritkatriel
body: Ok, that's a good point.
created_at: 2025-03-06T14:02:01Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
path: Python/flowgraph.c
user: login: iritkatriel
body: ```suggestion

    cfg_instr *intrinsic = &bb->b_instr[i];
```
created_at: 2025-03-06T21:46:38Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: iritkatriel
body: I think it will be simpler to look for this instruction before we enter this loop. So first look for this instruction, then enter a loop that checks whether everything is const.
created_at: 2025-03-06T21:51:21Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: WolframAlph
body: We are looking at 3 iterations then, right?
created_at: 2025-03-06T21:59:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: WolframAlph
body: We still need to check that for every `LOAD_CONST` there is `LIST_APPEND` and they are in correct sequence (possible `NOP`s in between). So upon exiting this loop, we still need to check what state was when we exited (`expect_append` in this case). So I don't know how moving sequence start searching to a separate loop would simplify things.
created_at: 2025-03-06T22:28:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: iritkatriel
body: > We are looking at 3 iterations then, right?

No, the "first iteration" ends where the "second iteration" begins, so it's just breaking up the first iteration into two parts.
created_at: 2025-03-06T22:34:24Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: WolframAlph
body: You may have missed my previous comment.
created_at: 2025-03-06T22:35:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
path: Python/flowgraph.c
user: login: iritkatriel
body: Can we just return here?
created_at: 2025-03-06T22:38:55Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
path: Python/flowgraph.c
user: login: WolframAlph
body: We can, but we need to check what is the value of `expect_append` which we already check immediately after breaking out of the loop.
created_at: 2025-03-06T22:40:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: iritkatriel
body: If not a separate pass, then we need to change this to be

`if (consts_found == 0 && opcode == BUILD_LIST && oparg == 0) {`


I would also set `expect_append` to `false` initially, and inside this if set it to `true`, because we are only looking for an append after we saw the `BUILD_LIST`.
created_at: 2025-03-06T22:41:25Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: WolframAlph
body: >I would also set expect_append to false initially, and inside this if set it to true, because we are only looking for an append after we saw the BUILD_LIST.

We are going backwards here, so we look for `LIST_APPEND` first.

> If not a separate pass, then we need to change this to be
>
> `if (consts_found == 0 && opcode == BUILD_LIST && oparg == 0) {`

What is the point of additional `consts_found == 0`?
created_at: 2025-03-06T22:52:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: iritkatriel
body: `consts_found == 0` makes sure that you find the ~~`LIST_APPEND`~~ `BUILD_LIST` first, not after you saw some consts. 
created_at: 2025-03-06T23:07:22Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: iritkatriel
body: Sorry, the I meant we find the `BUILD_LIST` first (not `LIST_APPEND`)
created_at: 2025-03-06T23:09:58Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
path: Python/flowgraph.c
user: login: iritkatriel
body: Ok, I see I was confused - the BUILD_LIST comes last, so `consts_found` is not 0 then.
created_at: 2025-03-06T23:11:48Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
path: Python/flowgraph.c
user: login: iritkatriel
body: Let's just return error here if `!expect_append`.
created_at: 2025-03-06T23:14:11Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
path: Python/flowgraph.c
user: login: WolframAlph
body: We cannot. Consider this example:
```python
(1, 2, [], 3, 4, ... 100)
```
it is valid scenario, and one of those I added tests for.
created_at: 2025-03-06T23:16:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
path: Python/flowgraph.c
user: login: iritkatriel
body: I see. 
created_at: 2025-03-06T23:38:14Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
+            break;
+        }
+
+        if (expect_append) {
+            if (opcode != LIST_APPEND || oparg != 1) {
+                break;
path: Python/flowgraph.c
user: login: iritkatriel
body: Can we return error here, and on line 1505?
created_at: 2025-03-06T23:40:44Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
+            break;
+        }
+
+        if (expect_append) {
+            if (opcode != LIST_APPEND || oparg != 1) {
+                break;
path: Python/flowgraph.c
user: login: iritkatriel
body: Sorry, return SUCCESS.
created_at: 2025-03-06T23:41:55Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
+            break;
+        }
+
+        if (expect_append) {
+            if (opcode != LIST_APPEND || oparg != 1) {
+                break;
path: Python/flowgraph.c
user: login: WolframAlph
body: We can. I see your point. I've updated logic to return as early as possible. Hopefully, it is more readable this way.
created_at: 2025-03-07T08:02:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
+            break;
+        }
+
+        if (expect_append) {
+            if (opcode != LIST_APPEND || oparg != 1) {
+                break;
path: Python/flowgraph.c
user: login: WolframAlph
body: Does it make sense to add tests testing no sequence start? Even though it won't happen in reality, but still.
created_at: 2025-03-07T08:06:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,93 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            if (!expect_append) {
+                /* Not a sequence start. */
+                return SUCCESS;
+            }
+
+            /* Sequence start, we are done. */
+            PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found);
+            if (newconst == NULL) {
+                return ERROR;
+            }
+
+            int nops = consts_found * 2 + 1;
+            for (pos = i - 1; pos >= 0 && consts_found > 0; pos--) {
+                instr = &bb->b_instr[pos];
+                if (loads_const(instr->i_opcode)) {
+                    PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
+                    if (constant == NULL) {
+                        Py_DECREF(newconst);
+                        return ERROR;
+                    }
+                    PyTuple_SET_ITEM(newconst, --consts_found, constant);
+                }
path: Python/flowgraph.c
user: login: iritkatriel
body: Maybe we can just NOP here as we go instead of making another pass for NOPing?
created_at: 2025-03-07T11:34:09Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,93 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            if (!expect_append) {
+                /* Not a sequence start. */
+                return SUCCESS;
+            }
+
+            /* Sequence start, we are done. */
+            PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found);
+            if (newconst == NULL) {
+                return ERROR;
+            }
+
+            int nops = consts_found * 2 + 1;
+            for (pos = i - 1; pos >= 0 && consts_found > 0; pos--) {
+                instr = &bb->b_instr[pos];
+                if (loads_const(instr->i_opcode)) {
+                    PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
+                    if (constant == NULL) {
+                        Py_DECREF(newconst);
+                        return ERROR;
+                    }
+                    PyTuple_SET_ITEM(newconst, --consts_found, constant);
+                }
path: Python/flowgraph.c
user: login: WolframAlph
body: We can, but there would still be 1 more instruction left to nop out which would require another loop (because there could be NOPs again...).
created_at: 2025-03-07T11:36:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,93 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            if (!expect_append) {
+                /* Not a sequence start. */
+                return SUCCESS;
+            }
+
+            /* Sequence start, we are done. */
+            PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found);
+            if (newconst == NULL) {
+                return ERROR;
+            }
+
+            int nops = consts_found * 2 + 1;
+            for (pos = i - 1; pos >= 0 && consts_found > 0; pos--) {
+                instr = &bb->b_instr[pos];
+                if (loads_const(instr->i_opcode)) {
+                    PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
+                    if (constant == NULL) {
+                        Py_DECREF(newconst);
+                        return ERROR;
+                    }
+                    PyTuple_SET_ITEM(newconst, --consts_found, constant);
+                }
path: Python/flowgraph.c
user: login: WolframAlph
body: If performance here is a factor we can complicate this piece of code by doing so. Otherwise, calling `nop_out` and make another pass is cleaner. Your call.
created_at: 2025-03-07T11:41:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,6 +1452,91 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool start_found = false;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            start_found = expect_append;
+            break;
+        }
+
+        if (expect_append) {
+            if (opcode != LIST_APPEND || oparg != 1) {
+                break;
path: Python/flowgraph.c
user: login: iritkatriel
body: Might as well add.
created_at: 2025-03-07T11:45:59Z
author_association: MEMBER
diff_hunk: @@ -1452,6 +1452,93 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const
     return SUCCESS;
 }
 
+/* Replace:
+    BUILD_LIST 0
+    LOAD_CONST c1
+    LIST_APPEND 1
+    LOAD_CONST c2
+    LIST_APPEND 1
+    ...
+    LOAD_CONST cN
+    LIST_APPEND 1
+    CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE
+   with:
+    LOAD_CONST (c1, c2, ... cN)
+*/
+static int
+fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i,
+                                      PyObject *consts, PyObject *const_cache)
+{
+    assert(PyDict_CheckExact(const_cache));
+    assert(PyList_CheckExact(consts));
+    assert(i >= 0);
+    assert(i < bb->b_iused);
+
+    cfg_instr *intrinsic = &bb->b_instr[i];
+    assert(intrinsic->i_opcode == CALL_INTRINSIC_1);
+    assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE);
+
+    int consts_found = 0;
+    bool expect_append = true;
+
+    for (int pos = i - 1; pos >= 0; pos--) {
+        cfg_instr *instr = &bb->b_instr[pos];
+        int opcode = instr->i_opcode;
+        int oparg = instr->i_oparg;
+
+        if (opcode == NOP) {
+            continue;
+        }
+
+        if (opcode == BUILD_LIST && oparg == 0) {
+            if (!expect_append) {
+                /* Not a sequence start. */
+                return SUCCESS;
+            }
+
+            /* Sequence start, we are done. */
+            PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found);
+            if (newconst == NULL) {
+                return ERROR;
+            }
+
+            int nops = consts_found * 2 + 1;
+            for (pos = i - 1; pos >= 0 && consts_found > 0; pos--) {
+                instr = &bb->b_instr[pos];
+                if (loads_const(instr->i_opcode)) {
+                    PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts);
+                    if (constant == NULL) {
+                        Py_DECREF(newconst);
+                        return ERROR;
+                    }
+                    PyTuple_SET_ITEM(newconst, --consts_found, constant);
+                }
path: Python/flowgraph.c
user: login: WolframAlph
body: Maybe we don't need another loop, let me check.
created_at: 2025-03-07T11:53:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: WolframAlph
created_at: 2025-03-04T16:59:07Z
body: After moving folding to codegen (https://github.com/python/cpython/pull/130769/commits/70ce2c81da3e4d64a87ef043c9bdd6d982fb66a8) I can see couple refleaks after running test suite in refleak hunter mode. After some debugging I narrowed down the problem. Long tuple defined directly does not report refleaks:
```python
x = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)
```
but if it is run via `compile`, we get refleaks:
```python
compile("x = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)", "", "single")
```
no idea yet why this is happening. I will be debugging further, but maybe someone has some insight what could be the problem?
author_association: CONTRIBUTOR
user: login: iritkatriel
created_at: 2025-03-04T18:09:01Z
body: Does it leak with compile with other modes (exec, eval)?
author_association: MEMBER
user: login: WolframAlph
created_at: 2025-03-04T18:20:34Z
body: >Does it leak with compile with other modes (exec, eval)?

Yes. I wonder if it could be some form of false positive.
author_association: CONTRIBUTOR
user: login: iritkatriel
created_at: 2025-03-04T18:50:08Z
body: No idea.
author_association: MEMBER
user: login: WolframAlph
created_at: 2025-03-05T08:43:47Z
body: Problem found. Turns out `codegen_addop_load_const` internally creates new reference to the passed constant object.
author_association: CONTRIBUTOR
user: login: iritkatriel
created_at: 2025-03-05T08:47:58Z
body: Why does it not happen at the prompt?
author_association: MEMBER
user: login: WolframAlph
created_at: 2025-03-05T19:14:27Z
body: No idea. Maybe refleak checking flaw, but not sure. Maybe someone who knows more about it could tell us?

Back to this PR - if we fold long tuples at codegen stage, we won't be able to fold long tuples that have constant expressions inside, for instance:
```python
(1, 2, 1 + 2, 4, ... 100)  # long constant tuple
```
will not be folded, unless we fold it in peepholer as written initially. Drawback of having optimizations that have to work together split between different stages.
author_association: CONTRIBUTOR
user: login: iritkatriel
created_at: 2025-03-05T19:23:53Z
body: I tend to agree. So I guess you're saying we should choose the second option from https://github.com/python/cpython/pull/130769#discussion_r1977889666.
author_association: MEMBER
user: login: WolframAlph
created_at: 2025-03-05T19:29:10Z
body: Second options assumes that if we have constant tuple, we can skip `STACK_USE_GUIDLINE` limitation and emit long tuple bytecode relying on peepholer to optimize. However, with constant expressions inside (like in my previous example), at codegen stage we don't know if it is constant or not. And we cannot emit long tuple bytecode, unless we are sure it will be optimized by peepholer. I'm saying it looks like we should stick with optimizing in the peepholer.
author_association: CONTRIBUTOR
user: login: WolframAlph
created_at: 2025-03-05T19:53:52Z
body: >Second options assumes that if we have constant tuple, we can skip STACK_USE_GUIDLINE limitation and emit long tuple bytecode relying on peepholer to optimize. However, with constant expressions inside (like in my previous example), at codegen stage we don't know if it is constant or not. And we cannot emit long tuple bytecode, unless we are sure it will be optimized by peepholer. I'm saying it looks like we should stick with optimizing in the peepholer.

We could check recursively for constant expressions and emit long tuple bytecode if we know all subexpressions are constant, but I believe it would complicate code more than optimizing it in the peepholer.
author_association: CONTRIBUTOR
user: login: iritkatriel
created_at: 2025-03-05T19:55:54Z
body: I understand. Ok, let's revert back to the peephole situation then.
author_association: MEMBER
user: login: iritkatriel
created_at: 2025-03-05T20:04:46Z
body: Another idea (not in this PR) would be to move all the `STACK_USE_GUIDELINE` stuff to the peephole. So codegen emits code without considering whether it's big or not, and later (after optimisations) we have a pass that finds big stack use and reduces. I don't know how this compares to what we have now in terms of code complexity.
author_association: MEMBER
user: login: WolframAlph
created_at: 2025-03-05T20:07:00Z
body: I will make a note and investigate later.
author_association: CONTRIBUTOR
user: login: WolframAlph
created_at: 2025-03-05T20:30:43Z
body: I've reverted to the peepholer optimization and addressed previous review, please take a look.
author_association: CONTRIBUTOR
user: login: WolframAlph
created_at: 2025-03-19T10:49:29Z
body: @iritkatriel can you review again when you have time?
author_association: CONTRIBUTOR
user: login: iritkatriel
created_at: 2025-03-19T20:13:47Z
body: !buildbot refleak
author_association: MEMBER
user: login: bedevere-bot
created_at: 2025-03-19T20:13:51Z
body: :robot: New build scheduled with the buildbot fleet by @iritkatriel for commit 4b11cf88be33a56f55d9ab1bb2169abb27bfd139 :robot:

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F130769%2Fmerge

The command will test the builders whose names match following regular expression: `refleak`

The builders matched are:
- `AMD64 Fedora Rawhide Refleaks PR`
- `AMD64 Fedora Stable Refleaks PR`
- `aarch64 Fedora Rawhide NoGIL refleaks PR`
- `AMD64 CentOS9 NoGIL Refleaks PR`
- `aarch64 Fedora Stable Refleaks PR`
- `AMD64 CentOS9 Refleaks PR`
- `s390x RHEL9 Refleaks PR`
- `AMD64 Windows11 Refleaks PR`
- `PPC64LE Fedora Rawhide Refleaks PR`
- `ARM64 MacOS M1 Refleaks NoGIL PR`
- `s390x RHEL8 Refleaks PR`
- `AMD64 Fedora Rawhide NoGIL refleaks PR`
- `aarch64 RHEL8 Refleaks PR`
- `PPC64LE Fedora Rawhide NoGIL refleaks PR`
- `aarch64 CentOS9 Refleaks PR`
- `aarch64 Fedora Rawhide Refleaks PR`
- `AMD64 FreeBSD Refleaks PR`
- `PPC64LE RHEL8 Refleaks PR`
- `PPC64LE Fedora Stable Refleaks PR`
- `PPC64LE CentOS9 Refleaks PR`
- `AMD64 RHEL8 Refleaks PR`

author_association: NONE