From 389f99f1450dc51416124c9d5d66e37be9df943c Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Sun, 2 Mar 2025 20:36:30 +0100 Subject: [PATCH 01/24] remove tuple folding from ast_opt.c --- Python/ast_opt.c | 39 --------------------------------------- 1 file changed, 39 deletions(-) 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) { From c1e4f23e7f7c9ed7ead5309a8aa74905ad9dfca2 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Sun, 2 Mar 2025 20:36:57 +0100 Subject: [PATCH 02/24] preserve constant tuple warnings --- Python/codegen.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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; } From 7ffac41f65b01e6b55824be0c6b99fb3736639cb Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Sun, 2 Mar 2025 20:37:34 +0100 Subject: [PATCH 03/24] implement constant intrinsic list to tuple folding --- Python/flowgraph.c | 95 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 8154129090b222..98f1a527bc2fdb 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1482,6 +1482,93 @@ 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 + 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 + ); + + 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); + int r = PyList_Append(list, constant); + Py_DECREF(constant); + if (r < 0) { + goto error; + } + } + + expect_append = !expect_append; + } + +exit: + Py_DECREF(list); + return SUCCESS; +error: + Py_DECREF(list); + return ERROR; +} + #define MIN_CONST_SEQUENCE_SIZE 3 /* Optimize lists and sets for: @@ -2379,8 +2466,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); + } } else if (oparg == INTRINSIC_UNARY_POSITIVE) { RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache)); From 4eae42322756a16921569f68afd40bd10d7a9ae2 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Sun, 2 Mar 2025 20:38:21 +0100 Subject: [PATCH 04/24] adjust tests --- Lib/test/test_ast/test_ast.py | 111 ---------------------------------- Lib/test/test_builtin.py | 12 ++-- Lib/test/test_compile.py | 4 +- Lib/test/test_opcache.py | 8 ++- 4 files changed, 14 insertions(+), 121 deletions(-) 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 f5d757d8fc72c9..8089e8993dd24a 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 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") From 3691e74010ffc9a56fbdcaee39d8d83ea38b5ea9 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Sun, 2 Mar 2025 20:38:36 +0100 Subject: [PATCH 05/24] add peepholer tests --- Lib/test/test_peepholer.py | 67 +++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 4644641144bee5..79ef10b4bec8a5 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,71 @@ 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)]) + def test_optimize_if_const_list(self): before = [ ('NOP', None, 0), From ed312ad853c1ac8cd7b39efe84017c2699694420 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Sun, 2 Mar 2025 20:47:31 +0100 Subject: [PATCH 06/24] add constant null check --- Python/flowgraph.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 98f1a527bc2fdb..a0cd945926665e 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1551,6 +1551,9 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, 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) { From 3e41b41c570febb7e8e4b08006e560c40b3beda2 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Mon, 3 Mar 2025 09:59:43 +0100 Subject: [PATCH 07/24] address review --- Python/flowgraph.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index a0cd945926665e..3021f8c0e6b33e 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1501,12 +1501,11 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, { assert(PyDict_CheckExact(const_cache)); assert(PyList_CheckExact(consts)); - assert(i >= 0 && i < bb->b_iused); + assert(i >= 0); + assert(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 - ); + assert(intrinsic->i_opcode == CALL_INTRINSIC_1); + assert(intrinsic->i_oparg == INTRINSIC_LIST_TO_TUPLE); PyObject *list = PyList_New(0); if (list == NULL) { @@ -2468,11 +2467,12 @@ 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) { + // for _ in (*foo, *bar) -> for _ in [*foo, *bar] if (nextop == GET_ITER) { INSTR_SET_OP0(inst, NOP); - } else { + } + else { fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache); } } From f150c15c473ea2996526d22d019eec07efbfbc8e Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Mon, 3 Mar 2025 10:02:33 +0100 Subject: [PATCH 08/24] update comment --- Python/flowgraph.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 3021f8c0e6b33e..b3e6a8380dc532 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1485,12 +1485,12 @@ fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const /* Replace: BUILD_LIST 0 LOAD_CONST c1 - LIST_APPEND + LIST_APPEND 1 LOAD_CONST c2 - LIST_APPEND + LIST_APPEND 1 ... LOAD_CONST cN - LIST_APPEND + LIST_APPEND 1 CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE with: LOAD_CONST (c1, c2, ... cN) From 4331a08bd2b12d62bf10c49fbdb66764bdb01e29 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Mon, 3 Mar 2025 19:27:41 +0100 Subject: [PATCH 09/24] fold in codegen instead of cfg --- Lib/test/test_peepholer.py | 65 --------------------------- Python/codegen.c | 19 ++++++++ Python/flowgraph.c | 92 -------------------------------------- 3 files changed, 19 insertions(+), 157 deletions(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 79ef10b4bec8a5..5facbbde2210e7 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1349,71 +1349,6 @@ 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)]) - def test_optimize_if_const_list(self): before = [ ('NOP', None, 0), diff --git a/Python/codegen.c b/Python/codegen.c index 44d23ca6d93014..7c18ea09698e4f 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3246,6 +3246,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); + } + } ADDOP_I(c, loc, build, pushed); sequence_built = 1; } diff --git a/Python/flowgraph.c b/Python/flowgraph.c index b3e6a8380dc532..8d1af323cd3c48 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1482,95 +1482,6 @@ 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); - - 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; - } - -exit: - Py_DECREF(list); - return SUCCESS; -error: - Py_DECREF(list); - return ERROR; -} - #define MIN_CONST_SEQUENCE_SIZE 3 /* Optimize lists and sets for: @@ -2472,9 +2383,6 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) if (nextop == GET_ITER) { INSTR_SET_OP0(inst, NOP); } - else { - 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)); From 70c4388a15713fc2c8f0fc058ac7339178bc3ab0 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Wed, 5 Mar 2025 13:31:32 +0100 Subject: [PATCH 10/24] update const tuple creation --- Python/codegen.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index 7c18ea09698e4f..132fca912b57c0 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3221,11 +3221,15 @@ starunpack_helper_impl(compiler *c, location loc, Py_ssize_t n = asdl_seq_LEN(elts); int big = n + pushed + (injected_arg ? 1 : 0) > _PY_STACK_USE_GUIDELINE; int seen_star = 0; + int is_const = 1; for (Py_ssize_t i = 0; i < n; i++) { expr_ty elt = asdl_seq_GET(elts, i); - if (elt->kind == Starred_kind) { - seen_star = 1; - break; + if (elt->kind != Constant_kind) { + is_const = 0; + if (elt->kind == Starred_kind) { + seen_star = 1; + break; + } } } if (!seen_star && !big) { @@ -3246,24 +3250,18 @@ starunpack_helper_impl(compiler *c, location loc, } int sequence_built = 0; if (big) { - if (tuple) { + if (is_const && tuple && !injected_arg && !pushed) { 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); - } + int r = codegen_addop_load_const(c, loc, newconst); + Py_DECREF(newconst); + return r; } ADDOP_I(c, loc, build, pushed); sequence_built = 1; From 4a44c02e57f04fae70dcdda34d24a52c37e6a1e6 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Wed, 5 Mar 2025 20:57:24 +0100 Subject: [PATCH 11/24] Revert "update const tuple creation" This reverts commit dfad19824e3373df55ed8c456b88bf023398b53f. --- Python/codegen.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index 132fca912b57c0..7c18ea09698e4f 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3221,15 +3221,11 @@ starunpack_helper_impl(compiler *c, location loc, Py_ssize_t n = asdl_seq_LEN(elts); int big = n + pushed + (injected_arg ? 1 : 0) > _PY_STACK_USE_GUIDELINE; int seen_star = 0; - int is_const = 1; for (Py_ssize_t i = 0; i < n; i++) { expr_ty elt = asdl_seq_GET(elts, i); - if (elt->kind != Constant_kind) { - is_const = 0; - if (elt->kind == Starred_kind) { - seen_star = 1; - break; - } + if (elt->kind == Starred_kind) { + seen_star = 1; + break; } } if (!seen_star && !big) { @@ -3250,18 +3246,24 @@ starunpack_helper_impl(compiler *c, location loc, } int sequence_built = 0; if (big) { - if (is_const && tuple && !injected_arg && !pushed) { + 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)); } - int r = codegen_addop_load_const(c, loc, newconst); - Py_DECREF(newconst); - return r; + if (is_const) { + return codegen_addop_load_const(c, loc, newconst); + } } ADDOP_I(c, loc, build, pushed); sequence_built = 1; From d0cd2a5d0098927b81afb34a1b9df3a7f4f5d8af Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Wed, 5 Mar 2025 20:57:39 +0100 Subject: [PATCH 12/24] Revert "fold in codegen instead of cfg" This reverts commit 70ce2c81da3e4d64a87ef043c9bdd6d982fb66a8. --- Lib/test/test_peepholer.py | 65 +++++++++++++++++++++++++++ Python/codegen.c | 19 -------- Python/flowgraph.c | 92 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 19 deletions(-) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 5facbbde2210e7..79ef10b4bec8a5 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1349,6 +1349,71 @@ 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)]) + def test_optimize_if_const_list(self): before = [ ('NOP', None, 0), diff --git a/Python/codegen.c b/Python/codegen.c index 7c18ea09698e4f..44d23ca6d93014 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -3246,25 +3246,6 @@ 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); - } - } ADDOP_I(c, loc, build, pushed); sequence_built = 1; } diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 8d1af323cd3c48..b3e6a8380dc532 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); + + 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; + } + +exit: + Py_DECREF(list); + return SUCCESS; +error: + Py_DECREF(list); + return ERROR; +} + #define MIN_CONST_SEQUENCE_SIZE 3 /* Optimize lists and sets for: @@ -2383,6 +2472,9 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) if (nextop == GET_ITER) { INSTR_SET_OP0(inst, NOP); } + else { + 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)); From e4d916401dd13360269eca6638f7a91ef8fc4206 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Wed, 5 Mar 2025 21:22:26 +0100 Subject: [PATCH 13/24] remove comment --- Python/flowgraph.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index b3e6a8380dc532..c9dfe9ea7429ee 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -2468,7 +2468,6 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) break; case CALL_INTRINSIC_1: if (oparg == INTRINSIC_LIST_TO_TUPLE) { - // for _ in (*foo, *bar) -> for _ in [*foo, *bar] if (nextop == GET_ITER) { INSTR_SET_OP0(inst, NOP); } From 75a2762434265fbad6f92dff464aecc30944c1dc Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Wed, 5 Mar 2025 21:28:02 +0100 Subject: [PATCH 14/24] replace typeparam tests from test_ast in test_compile --- Lib/test/test_compile.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 8089e8993dd24a..ac583500bd88a6 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -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, "", "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(): ( From f0429c8c1ba4f30da88836bc2c58b043e5036ace Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Thu, 6 Mar 2025 09:27:09 +0100 Subject: [PATCH 15/24] process 2 instructions per iteration --- Python/flowgraph.c | 47 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index c9dfe9ea7429ee..29bb8acff0ee19 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1512,8 +1512,6 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, return ERROR; } - bool expect_append = true; - for (int pos = i-1; pos >= 0; pos--) { cfg_instr *instr = &bb->b_instr[pos]; @@ -1522,10 +1520,6 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, } 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; @@ -1540,27 +1534,30 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, 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; - } + if (pos < 1) { + /* Can't process 2 instructions. */ + 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; - } + + if (!(instr->i_opcode == LIST_APPEND && instr->i_oparg == 1)) { + goto exit; + } + + instr = &bb->b_instr[--pos]; + if (!loads_const(instr->i_opcode)) { + goto exit; } - expect_append = !expect_append; + 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; + } } exit: @@ -2472,7 +2469,7 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts) INSTR_SET_OP0(inst, NOP); } else { - fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache); + RETURN_IF_ERROR(fold_constant_intrinsic_list_to_tuple(bb, i, consts, const_cache)); } } else if (oparg == INTRINSIC_UNARY_POSITIVE) { From 939fbeea33ec88f86dbadc53141192db980b3959 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Thu, 6 Mar 2025 15:23:05 +0100 Subject: [PATCH 16/24] make 2 passes to avoid unnecessary allocations --- Python/flowgraph.c | 75 ++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 29bb8acff0ee19..a904c7d11417ca 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1507,65 +1507,62 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int 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; - } + 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]; - if (instr->i_opcode == NOP) { continue; } - if (instr->i_opcode == BUILD_LIST && instr->i_oparg == 0) { - /* Sequence start, we are done. */ - if (PyList_Reverse(list) < 0) { - goto error; + if (instr->i_opcode == BUILD_LIST &&instr->i_oparg == 0) { + start_found = expect_append; + break; + } + + if (expect_append) { + if (!(instr->i_opcode == LIST_APPEND && instr->i_oparg == 1)) { + break; } - PyObject *newconst = PyList_AsTuple(list); - if (newconst == NULL) { - goto error; + } + else { + if (!loads_const(instr->i_opcode)) { + break; } - 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); + consts_found++; } - if (pos < 1) { - /* Can't process 2 instructions. */ - goto exit; - } + expect_append = !expect_append; + } - if (!(instr->i_opcode == LIST_APPEND && instr->i_oparg == 1)) { - goto exit; - } + if (!start_found) { + return SUCCESS; + } - instr = &bb->b_instr[--pos]; + PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found); + if (newconst == NULL) { + return ERROR; + } + + int nops = consts_found * 2 + 1; + for (int pos = i-1; pos >= 0 && consts_found > 0; pos--) { + cfg_instr *instr = &bb->b_instr[pos]; if (!loads_const(instr->i_opcode)) { - goto exit; + continue; } - 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; + Py_DECREF(newconst); + return ERROR; } + PyTuple_SET_ITEM(newconst, --consts_found, constant); } -exit: - Py_DECREF(list); - return SUCCESS; -error: - Py_DECREF(list); - return ERROR; + assert(consts_found == 0); + nop_out(bb, i-1, nops); + return instr_make_load_const(intrinsic, newconst, consts, const_cache); } #define MIN_CONST_SEQUENCE_SIZE 3 From 57e6f075dc39adfe8caaa69c910d3f966c8faf6d Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Thu, 6 Mar 2025 15:23:20 +0100 Subject: [PATCH 17/24] add more tests --- Lib/test/test_peepholer.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 79ef10b4bec8a5..2cfaeaa2c6f6bc 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1414,6 +1414,33 @@ def test_fold_constant_intrinsic_list_to_tuple(self): ] 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)]) + def test_optimize_if_const_list(self): before = [ ('NOP', None, 0), From 4ae59f86ece7c7496381780ef45abba6eef9d7ca Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Thu, 6 Mar 2025 21:35:36 +0100 Subject: [PATCH 18/24] improve styling --- Python/flowgraph.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index a904c7d11417ca..2ec24157e9ef5a 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1511,24 +1511,27 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, bool start_found = false; bool expect_append = true; - for (int pos = i-1; pos >= 0; pos--) { + for (int pos = i - 1; pos >= 0; pos--) { cfg_instr *instr = &bb->b_instr[pos]; - if (instr->i_opcode == NOP) { + int opcode = instr->i_opcode; + int oparg = instr->i_oparg; + + if (opcode == NOP) { continue; } - if (instr->i_opcode == BUILD_LIST &&instr->i_oparg == 0) { + if (opcode == BUILD_LIST && oparg == 0) { start_found = expect_append; break; } if (expect_append) { - if (!(instr->i_opcode == LIST_APPEND && instr->i_oparg == 1)) { + if (opcode != LIST_APPEND || oparg != 1) { break; } } else { - if (!loads_const(instr->i_opcode)) { + if (!loads_const(opcode)) { break; } consts_found++; @@ -1547,17 +1550,16 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, } int nops = consts_found * 2 + 1; - for (int pos = i-1; pos >= 0 && consts_found > 0; pos--) { + for (int pos = i - 1; pos >= 0 && consts_found > 0; pos--) { cfg_instr *instr = &bb->b_instr[pos]; - if (!loads_const(instr->i_opcode)) { - continue; - } - PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts); - if (constant == NULL) { - Py_DECREF(newconst); - return ERROR; + 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); } - PyTuple_SET_ITEM(newconst, --consts_found, constant); } assert(consts_found == 0); From bffc456184d95d5697d1b4c97696f228343c74a5 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Fri, 7 Mar 2025 09:00:54 +0100 Subject: [PATCH 19/24] address reivew --- Python/flowgraph.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 2ec24157e9ef5a..fa21214e5558e0 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1503,12 +1503,12 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, 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--) { @@ -1521,18 +1521,20 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, } if (opcode == BUILD_LIST && oparg == 0) { - start_found = expect_append; - break; + if (!expect_append) { + return SUCCESS; + } + goto start_found; } if (expect_append) { if (opcode != LIST_APPEND || oparg != 1) { - break; + return SUCCESS; } } else { if (!loads_const(opcode)) { - break; + return SUCCESS; } consts_found++; } @@ -1540,10 +1542,10 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, expect_append = !expect_append; } - if (!start_found) { - return SUCCESS; - } + /* Did not find sequence start. */ + return SUCCESS; +start_found:; PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found); if (newconst == NULL) { return ERROR; From e8e70e0594c65620fe0c790f2cec5e0a8d8d8236 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Fri, 7 Mar 2025 11:19:48 +0100 Subject: [PATCH 20/24] get rid of label --- Python/flowgraph.c | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index fa21214e5558e0..77063c952cce83 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1522,9 +1522,32 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, if (opcode == BUILD_LIST && oparg == 0) { if (!expect_append) { + /* Not a sequence start. */ return SUCCESS; } - goto start_found; + + /* 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); + } + } + + assert(consts_found == 0); + nop_out(bb, i-1, nops); + return instr_make_load_const(intrinsic, newconst, consts, const_cache); } if (expect_append) { @@ -1544,29 +1567,6 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, /* Did not find sequence start. */ return SUCCESS; - -start_found:; - PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found); - if (newconst == NULL) { - return ERROR; - } - - int nops = consts_found * 2 + 1; - for (int pos = i - 1; pos >= 0 && consts_found > 0; pos--) { - cfg_instr *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); - } - } - - assert(consts_found == 0); - nop_out(bb, i-1, nops); - return instr_make_load_const(intrinsic, newconst, consts, const_cache); } #define MIN_CONST_SEQUENCE_SIZE 3 From a7dbe98f9024ebdcbccfda3df0d35c86a666523c Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Fri, 7 Mar 2025 13:06:37 +0100 Subject: [PATCH 21/24] inline noping out --- Python/flowgraph.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 77063c952cce83..4fee2a5be56bb5 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1532,21 +1532,24 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, return ERROR; } - int nops = consts_found * 2 + 1; - for (pos = i - 1; pos >= 0 && consts_found > 0; pos--) { - instr = &bb->b_instr[pos]; + 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); } + INSTR_SET_OP0(instr, NOP); + INSTR_SET_LOC(instr, NO_LOCATION); } - assert(consts_found == 0); - nop_out(bb, i-1, nops); return instr_make_load_const(intrinsic, newconst, consts, const_cache); } From 836c0ee500d58c1e6509320756a66f62690b03a5 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Fri, 7 Mar 2025 13:06:48 +0100 Subject: [PATCH 22/24] add more tests --- Lib/test/test_peepholer.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 2cfaeaa2c6f6bc..6de89c4043d3f7 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -1441,6 +1441,19 @@ def test_fold_constant_intrinsic_list_to_tuple(self): ] 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), From 72a3e652f8f8863874ecc4713a0c06a7dea4a1aa Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Tue, 18 Mar 2025 13:34:18 +0100 Subject: [PATCH 23/24] resolve conflicts --- Programs/test_frozenmain.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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, From 967a18e368ee4788f1c32ea7f3aa1ae77c078ca6 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Tue, 18 Mar 2025 14:32:48 +0100 Subject: [PATCH 24/24] use new nop_out --- Python/flowgraph.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 4fee2a5be56bb5..cf3e74005ce1f8 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -1546,8 +1546,7 @@ fold_constant_intrinsic_list_to_tuple(basicblock *bb, int i, assert(consts_found > 0); PyTuple_SET_ITEM(newconst, --consts_found, constant); } - INSTR_SET_OP0(instr, NOP); - INSTR_SET_LOC(instr, NO_LOCATION); + nop_out(&instr, 1); } assert(consts_found == 0); return instr_make_load_const(intrinsic, newconst, consts, const_cache);