From f903eb48f0def2883f8f19c680646d6a4c260977 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Mon, 10 Feb 2025 17:40:06 +0100 Subject: [PATCH 1/7] fold unary & binary complex constant expressions in codegen --- Include/cpython/compile.h | 1 + Lib/test/support/bytecode_helper.py | 4 +- Lib/test/test_compiler_codegen.py | 42 +++++++++- Modules/_testinternalcapi.c | 8 +- Modules/clinic/_testinternalcapi.c.h | 11 ++- Python/codegen.c | 116 +++++++++++++++++++++++++-- Python/compile.c | 3 +- 7 files changed, 169 insertions(+), 16 deletions(-) diff --git a/Include/cpython/compile.h b/Include/cpython/compile.h index cfdb7080d45f2b..3e5ec1c3d89c32 100644 --- a/Include/cpython/compile.h +++ b/Include/cpython/compile.h @@ -20,6 +20,7 @@ #define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000 #define PyCF_ALLOW_INCOMPLETE_INPUT 0x4000 #define PyCF_OPTIMIZED_AST (0x8000 | PyCF_ONLY_AST) +#define PyCF_DONT_OPTIMIZE_AST 0x10000 #define PyCF_COMPILE_MASK (PyCF_ONLY_AST | PyCF_ALLOW_TOP_LEVEL_AWAIT | \ PyCF_TYPE_COMMENTS | PyCF_DONT_IMPLY_DEDENT | \ PyCF_ALLOW_INCOMPLETE_INPUT | PyCF_OPTIMIZED_AST) diff --git a/Lib/test/support/bytecode_helper.py b/Lib/test/support/bytecode_helper.py index f6426c3e285b2d..4390035e753f16 100644 --- a/Lib/test/support/bytecode_helper.py +++ b/Lib/test/support/bytecode_helper.py @@ -138,8 +138,8 @@ def check_instructions(self, insts): @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") class CodegenTestCase(CompilationStepTestCase): - def generate_code(self, ast): - insts, _ = _testinternalcapi.compiler_codegen(ast, "my_file.py", 0) + def generate_code(self, ast, optimize_ast=True): + insts, _ = _testinternalcapi.compiler_codegen(ast, "my_file.py", 0, 0, optimize_ast) return insts diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index cf5e2d901db4de..9ed0ab5cc60ccf 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -15,10 +15,10 @@ def assertInstructionsMatch_recursive(self, insts, expected_insts): for n_insts, n_expected in zip(insts.get_nested(), expected_nested): self.assertInstructionsMatch_recursive(n_insts, n_expected) - def codegen_test(self, snippet, expected_insts): + def codegen_test(self, snippet, expected_insts, optimize_ast=True): import ast a = ast.parse(snippet, "my_file.py", "exec") - insts = self.generate_code(a) + insts = self.generate_code(a, optimize_ast=optimize_ast) self.assertInstructionsMatch_recursive(insts, expected_insts) def test_if_expression(self): @@ -157,3 +157,41 @@ def test_syntax_error__return_not_in_function(self): self.assertIsNone(cm.exception.text) self.assertEqual(cm.exception.offset, 1) self.assertEqual(cm.exception.end_offset, 10) + + def test_dont_optimize_ast_before_codegen(self): + snippet = "1+2" + unoptimized = [ + ('RESUME', 0, 0), + ('LOAD_SMALL_INT', 1, 0), + ('LOAD_SMALL_INT', 2, 0), + ('BINARY_OP', 0, 0), + ('POP_TOP', None, 0), + ('LOAD_CONST', 0, 0), + ('RETURN_VALUE', None, 0), + ] + self.codegen_test(snippet, unoptimized, optimize_ast=False) + + optimized = [ + ('RESUME', 0, 0), + ('NOP', None, 0), + ('LOAD_CONST', 0, 0), + ('RETURN_VALUE', None, 0), + ] + self.codegen_test(snippet, optimized, optimize_ast=True) + + def test_match_case_fold_codegen(self): + snippet = textwrap.dedent(""" + match 0: + case -0: pass # match unary const int + case -0.1: pass # match unary const float + case -0j: pass # match unary const complex + case 1 + 2j: pass # match const int + const complex + case 1 - 2j: pass # match const int - const complex + case 1.1 + 2.1j: pass # match const float + const complex + case 1.1 - 2.1j: pass # match const float - const complex + case -0 + 1j: pass # match unary const int + complex + case -0 - 1j: pass # match unary const int - complex + case -0.1 + 1.1j: pass # match unary const float + complex + case -0.1 - 1.1j: pass # match unary const float - complex + """) + self.codegen_test(snippet, [], optimize_ast=False) diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index e44b629897c58a..b2ead16ab810a0 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -718,11 +718,13 @@ Apply compiler code generation to an AST. static PyObject * _testinternalcapi_compiler_codegen_impl(PyObject *module, PyObject *ast, PyObject *filename, int optimize, - int compile_mode) + int compile_mode, int optimize_ast) /*[clinic end generated code: output=40a68f6e13951cc8 input=a0e00784f1517cd7]*/ { - PyCompilerFlags *flags = NULL; - return _PyCompile_CodeGen(ast, filename, flags, optimize, compile_mode); + PyCompilerFlags flags = _PyCompilerFlags_INIT; + if (!optimize_ast) + flags.cf_flags = PyCF_DONT_OPTIMIZE_AST; + return _PyCompile_CodeGen(ast, filename, &flags, optimize, compile_mode); } diff --git a/Modules/clinic/_testinternalcapi.c.h b/Modules/clinic/_testinternalcapi.c.h index d98d69df22f982..416e3cff0c070e 100644 --- a/Modules/clinic/_testinternalcapi.c.h +++ b/Modules/clinic/_testinternalcapi.c.h @@ -98,7 +98,7 @@ PyDoc_STRVAR(_testinternalcapi_compiler_codegen__doc__, static PyObject * _testinternalcapi_compiler_codegen_impl(PyObject *module, PyObject *ast, PyObject *filename, int optimize, - int compile_mode); + int compile_mode, int optimize_ast); static PyObject * _testinternalcapi_compiler_codegen(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -135,9 +135,10 @@ _testinternalcapi_compiler_codegen(PyObject *module, PyObject *const *args, Py_s PyObject *filename; int optimize; int compile_mode = 0; + int optimize_ast = 1; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, - /*minpos*/ 3, /*maxpos*/ 4, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + /*minpos*/ 3, /*maxpos*/ 5, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!args) { goto exit; } @@ -154,8 +155,12 @@ _testinternalcapi_compiler_codegen(PyObject *module, PyObject *const *args, Py_s if (compile_mode == -1 && PyErr_Occurred()) { goto exit; } + optimize_ast = PyLong_AsInt(args[4]); + if (optimize_ast == -1 && PyErr_Occurred()) { + goto exit; + } skip_optional_pos: - return_value = _testinternalcapi_compiler_codegen_impl(module, ast, filename, optimize, compile_mode); + return_value = _testinternalcapi_compiler_codegen_impl(module, ast, filename, optimize, compile_mode, optimize_ast); exit: return return_value; diff --git a/Python/codegen.c b/Python/codegen.c index cd77b34c06296b..2323c16b0fcc2d 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -5355,9 +5355,114 @@ codegen_slice(compiler *c, expr_ty s) #define WILDCARD_STAR_CHECK(N) \ ((N)->kind == MatchStar_kind && !(N)->v.MatchStar.name) -// Limit permitted subexpressions, even if the parser & AST validator let them through -#define MATCH_VALUE_EXPR(N) \ - ((N)->kind == Constant_kind || (N)->kind == Attribute_kind) +static bool +is_unary_or_complex_expr(expr_ty e) +{ + if (e->kind != UnaryOp_kind) { + return false; + } + if (e->v.UnaryOp.op != USub) { + return false; + } + if (e->v.UnaryOp.operand->kind != Constant_kind) { + return false; + } + PyObject *constant = e->v.UnaryOp.operand->v.Constant.value; + return PyLong_CheckExact(constant) || PyFloat_CheckExact(constant) || PyComplex_CheckExact(constant); +} + +static bool +is_complex_binop_expr(expr_ty e) +{ + if (e->kind != BinOp_kind) { + return false; + } + if (e->v.BinOp.op != Add && e->v.BinOp.op != Sub) { + return false; + } + if (e->v.BinOp.right->kind != Constant_kind) { + return false; + } + if (e->v.BinOp.left->kind != Constant_kind && e->v.BinOp.left->kind != UnaryOp_kind) { + return false; + } + PyObject *leftconst; + if (e->v.BinOp.left->kind == UnaryOp_kind) { + if (e->v.BinOp.left->v.UnaryOp.operand->kind != Constant_kind) { + return false; + } + if (e->v.BinOp.left->v.UnaryOp.op != USub) { + return false; + } + leftconst = e->v.BinOp.left->v.UnaryOp.operand->v.Constant.value; + } + else { + leftconst = e->v.BinOp.left->v.Constant.value; + } + PyObject *rightconst = e->v.BinOp.right->v.Constant.value; + return (PyLong_CheckExact(leftconst) || PyFloat_CheckExact(leftconst)) && PyComplex_CheckExact(rightconst); +} + +static void +fold_node(expr_ty node, PyObject *folded) +{ + assert(node->kind != Constant_kind); + node->kind = Constant_kind; + node->v.Constant.kind = NULL; + node->v.Constant.value = folded; +} + +static int +fold_unary_or_complex_expr(expr_ty e) +{ + assert(e->kind == UnaryOp_kind); + assert(e->v.UnaryOp.op == USub); + assert(e->v.UnaryOp.operand->kind == Constant_kind); + PyObject *operand = e->v.UnaryOp.operand->v.Constant.value; + assert(PyLong_CheckExact(operand) || PyFloat_CheckExact(operand) || PyComplex_CheckExact(operand)); + PyObject* folded = PyNumber_Negative(operand); + if (folded == NULL) { + return ERROR; + } + fold_node(e, folded); + return SUCCESS; +} + +static int +fold_binary_complex_expr(expr_ty e) +{ + assert(e->kind == BinOp_kind); + assert(e->v.BinOp.right->kind == Constant_kind); + assert(e->v.BinOp.left->kind == UnaryOp_kind || e->v.BinOp.left->kind == Constant_kind); + if (e->v.BinOp.left->kind == UnaryOp_kind) { + RETURN_IF_ERROR(fold_unary_or_complex_expr(e->v.BinOp.left)); + } + assert(e->v.BinOp.left->kind == Constant_kind); + operator_ty op = e->v.BinOp.op; + PyObject *left = e->v.BinOp.left->v.Constant.value; + PyObject *right = e->v.BinOp.right->v.Constant.value; + assert(op == Add || op == Sub); + assert(PyLong_CheckExact(left) || PyFloat_CheckExact(left)); + assert(PyComplex_CheckExact(right)); + PyObject *folded = op == Add ? PyNumber_Add(left, right) : PyNumber_Subtract(left, right); + if (folded == NULL) { + return ERROR; + } + fold_node(e, folded); + return SUCCESS; +} + +static int +try_fold_unary_or_binary_complex_const_expr(expr_ty key) +{ + if (is_unary_or_complex_expr(key)) { + return fold_unary_or_complex_expr(key); + } + if (is_complex_binop_expr(key)) { + return fold_binary_complex_expr(key); + } + return SUCCESS; +} // Allocate or resize pc->fail_pop to allow for n items to be popped on failure. static int @@ -5688,7 +5793,7 @@ codegen_pattern_mapping_key(compiler *c, PyObject *seen, pattern_ty p, Py_ssize_ location loc = LOC((pattern_ty) asdl_seq_GET(patterns, i)); return _PyCompile_Error(c, loc, e); } - + RETURN_IF_ERROR(try_fold_unary_or_binary_complex_const_expr(key)); if (key->kind == Constant_kind) { int in_seen = PySet_Contains(seen, key->v.Constant.value); RETURN_IF_ERROR(in_seen); @@ -6022,7 +6127,8 @@ codegen_pattern_value(compiler *c, pattern_ty p, pattern_context *pc) { assert(p->kind == MatchValue_kind); expr_ty value = p->v.MatchValue.value; - if (!MATCH_VALUE_EXPR(value)) { + RETURN_IF_ERROR(try_fold_unary_or_binary_complex_const_expr(value)); + if (value->kind != Constant_kind && value->kind != Attribute_kind) { const char *e = "patterns may only match literals and attribute lookups"; return _PyCompile_Error(c, LOC(p), e); } diff --git a/Python/compile.c b/Python/compile.c index b58c12d4b881ac..e25b59440373f0 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -126,7 +126,8 @@ compiler_setup(compiler *c, mod_ty mod, PyObject *filename, c->c_optimize = (optimize == -1) ? _Py_GetConfig()->optimization_level : optimize; c->c_save_nested_seqs = false; - if (!_PyAST_Optimize(mod, arena, c->c_optimize, merged)) { + int ast_opt = !(flags->cf_flags & PyCF_DONT_OPTIMIZE_AST); + if (ast_opt && !_PyAST_Optimize(mod, arena, c->c_optimize, merged)) { return ERROR; } c->c_st = _PySymtable_Build(mod, filename, &c->c_future); From 185c1c78a5234904296f9deb4fb810bf5574d034 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Mon, 10 Feb 2025 17:46:09 +0100 Subject: [PATCH 2/7] update tests --- Lib/test/test_compiler_codegen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index 9ed0ab5cc60ccf..6024bd1f8d4d28 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -194,4 +194,5 @@ def test_match_case_fold_codegen(self): case -0.1 + 1.1j: pass # match unary const float + complex case -0.1 - 1.1j: pass # match unary const float - complex """) - self.codegen_test(snippet, [], optimize_ast=False) + instrs = [] # TODO + self.codegen_test(snippet, instrs, optimize_ast=False) From ac38c1beb30cdf32e1b03bb26accb069508aa52a Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Mon, 10 Feb 2025 19:17:38 +0100 Subject: [PATCH 3/7] bring back macro --- Python/codegen.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Python/codegen.c b/Python/codegen.c index 2323c16b0fcc2d..dfa54872885b0e 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -5355,6 +5355,10 @@ codegen_slice(compiler *c, expr_ty s) #define WILDCARD_STAR_CHECK(N) \ ((N)->kind == MatchStar_kind && !(N)->v.MatchStar.name) +// Limit permitted subexpressions, even if the parser & AST validator let them through +#define MATCH_VALUE_EXPR(N) \ + ((N)->kind == Constant_kind || (N)->kind == Attribute_kind) + static bool is_unary_or_complex_expr(expr_ty e) { @@ -6128,7 +6132,7 @@ codegen_pattern_value(compiler *c, pattern_ty p, pattern_context *pc) assert(p->kind == MatchValue_kind); expr_ty value = p->v.MatchValue.value; RETURN_IF_ERROR(try_fold_unary_or_binary_complex_const_expr(value)); - if (value->kind != Constant_kind && value->kind != Attribute_kind) { + if (!MATCH_VALUE_EXPR(value)) { const char *e = "patterns may only match literals and attribute lookups"; return _PyCompile_Error(c, LOC(p), e); } From 013a8901f1d0ad59b10a55d502377dd1ca7326e6 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Tue, 11 Feb 2025 09:45:40 +0100 Subject: [PATCH 4/7] add macros --- Python/codegen.c | 154 +++++++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index dfa54872885b0e..fc8339c98c0775 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -5359,72 +5359,86 @@ codegen_slice(compiler *c, expr_ty s) #define MATCH_VALUE_EXPR(N) \ ((N)->kind == Constant_kind || (N)->kind == Attribute_kind) -static bool -is_unary_or_complex_expr(expr_ty e) -{ - if (e->kind != UnaryOp_kind) { - return false; - } - if (e->v.UnaryOp.op != USub) { - return false; - } - if (e->v.UnaryOp.operand->kind != Constant_kind) { - return false; - } - PyObject *constant = e->v.UnaryOp.operand->v.Constant.value; - return PyLong_CheckExact(constant) || PyFloat_CheckExact(constant) || PyComplex_CheckExact(constant); -} +#define IS_CONST_EXPR(N) \ + ((N)->kind == Constant_kind) -static bool -is_complex_binop_expr(expr_ty e) -{ - if (e->kind != BinOp_kind) { - return false; - } - if (e->v.BinOp.op != Add && e->v.BinOp.op != Sub) { - return false; - } - if (e->v.BinOp.right->kind != Constant_kind) { - return false; - } - if (e->v.BinOp.left->kind != Constant_kind && e->v.BinOp.left->kind != UnaryOp_kind) { - return false; - } - PyObject *leftconst; - if (e->v.BinOp.left->kind == UnaryOp_kind) { - if (e->v.BinOp.left->v.UnaryOp.operand->kind != Constant_kind) { - return false; - } - if (e->v.BinOp.left->v.UnaryOp.op != USub) { - return false; - } - leftconst = e->v.BinOp.left->v.UnaryOp.operand->v.Constant.value; - } - else { - leftconst = e->v.BinOp.left->v.Constant.value; - } - PyObject *rightconst = e->v.BinOp.right->v.Constant.value; - return (PyLong_CheckExact(leftconst) || PyFloat_CheckExact(leftconst)) && PyComplex_CheckExact(rightconst); -} +#define CONST_EXPR_VALUE(N) \ + ((N)->v.Constant.value) + +#define IS_COMPLEX_CONST_EXPR(N) \ + (IS_CONST_EXPR(N) && PyComplex_CheckExact(CONST_EXPR_VALUE(N))) + +#define IS_NUMERIC_CONST_EXPR(N) \ + (IS_CONST_EXPR(N) && (PyLong_CheckExact(CONST_EXPR_VALUE(N)) || PyFloat_CheckExact(CONST_EXPR_VALUE(N)))) + +#define IS_UNARY_EXPR(N) \ + ((N)->kind == UnaryOp_kind) + +#define UNARY_EXPR_OP(N) \ + ((N)->v.UnaryOp.op) + +#define UNARY_EXPR_OPERAND(N) \ + ((N)->v.UnaryOp.operand) + +#define UNARY_EXPR_OPERAND_CONST_VALUE(N) \ + (CONST_EXPR_VALUE(UNARY_EXPR_OPERAND(N))) + +#define IS_UNARY_SUB_EXPR(N) \ + (IS_UNARY_EXPR(N) && UNARY_EXPR_OP(N) == USub) + +#define IS_MATCH_NUMERIC_UNARY_CONST_EXPR(N) \ + (IS_UNARY_SUB_EXPR(N) && IS_NUMERIC_CONST_EXPR(UNARY_EXPR_OPERAND(N))) + +#define IS_MATCH_COMPLEX_UNARY_CONST_EXPR(N) \ + (IS_UNARY_SUB_EXPR(N) && IS_COMPLEX_CONST_EXPR(UNARY_EXPR_OPERAND(N))) + +#define IS_MATCH_NUMERIC_OR_COMPLEX_UNARY_CONST_EXPR(N) \ + (IS_MATCH_NUMERIC_UNARY_CONST_EXPR(N) || IS_MATCH_COMPLEX_UNARY_CONST_EXPR(N)) + +#define BINARY_EXPR(N) \ + ((N)->v.BinOp) + +#define BINARY_EXPR_OP(N) \ + (BINARY_EXPR(N).op) + +#define BINARY_EXPR_LEFT(N) \ + (BINARY_EXPR(N).left) + +#define BINARY_EXPR_RIGHT(N) \ + (BINARY_EXPR(N).right) + +#define IS_BINARY_EXPR(N) \ + ((N)->kind == BinOp_kind) + +#define IS_BINARY_ADD_EXPR(N) \ + (IS_BINARY_EXPR(N) && BINARY_EXPR_OP(N) == Add) + +#define IS_BINARY_SUB_EXPR(N) \ + (IS_BINARY_EXPR(N) && BINARY_EXPR_OP(N) == Sub) + +#define IS_MATCH_COMPLEX_BINARY_CONST_EXPR(N) \ + ( \ + (IS_BINARY_ADD_EXPR(N) || IS_BINARY_SUB_EXPR(N)) \ + && (IS_MATCH_NUMERIC_UNARY_CONST_EXPR(BINARY_EXPR_LEFT(N)) || IS_CONST_EXPR(BINARY_EXPR_LEFT(N))) \ + && IS_COMPLEX_CONST_EXPR(BINARY_EXPR_RIGHT(N)) \ + ) static void fold_node(expr_ty node, PyObject *folded) { - assert(node->kind != Constant_kind); + assert(!IS_CONST_EXPR(node)); node->kind = Constant_kind; node->v.Constant.kind = NULL; node->v.Constant.value = folded; } static int -fold_unary_or_complex_expr(expr_ty e) +fold_const_unary_or_complex_expr(expr_ty e) { - assert(e->kind == UnaryOp_kind); - assert(e->v.UnaryOp.op == USub); - assert(e->v.UnaryOp.operand->kind == Constant_kind); - PyObject *operand = e->v.UnaryOp.operand->v.Constant.value; - assert(PyLong_CheckExact(operand) || PyFloat_CheckExact(operand) || PyComplex_CheckExact(operand)); - PyObject* folded = PyNumber_Negative(operand); + assert(IS_MATCH_NUMERIC_OR_COMPLEX_UNARY_CONST_EXPR(e)); + PyObject *constant = UNARY_EXPR_OPERAND_CONST_VALUE(e); + assert(UNARY_EXPR_OP(e) == USub); + PyObject* folded = PyNumber_Negative(constant); if (folded == NULL) { return ERROR; } @@ -5433,21 +5447,19 @@ fold_unary_or_complex_expr(expr_ty e) } static int -fold_binary_complex_expr(expr_ty e) +fold_const_binary_complex_expr(expr_ty e) { - assert(e->kind == BinOp_kind); - assert(e->v.BinOp.right->kind == Constant_kind); - assert(e->v.BinOp.left->kind == UnaryOp_kind || e->v.BinOp.left->kind == Constant_kind); - if (e->v.BinOp.left->kind == UnaryOp_kind) { - RETURN_IF_ERROR(fold_unary_or_complex_expr(e->v.BinOp.left)); + assert(IS_MATCH_COMPLEX_BINARY_CONST_EXPR(e)); + expr_ty left_expr = BINARY_EXPR_LEFT(e); + if (IS_UNARY_EXPR(left_expr)) { + assert(IS_MATCH_NUMERIC_UNARY_CONST_EXPR(left_expr)); + RETURN_IF_ERROR(fold_const_unary_or_complex_expr(left_expr)); } - assert(e->v.BinOp.left->kind == Constant_kind); - operator_ty op = e->v.BinOp.op; - PyObject *left = e->v.BinOp.left->v.Constant.value; - PyObject *right = e->v.BinOp.right->v.Constant.value; + assert(IS_CONST_EXPR(BINARY_EXPR_LEFT(e))); + operator_ty op = BINARY_EXPR_OP(e); + PyObject *left = CONST_EXPR_VALUE(BINARY_EXPR_LEFT(e)); + PyObject *right = CONST_EXPR_VALUE(BINARY_EXPR_RIGHT(e)); assert(op == Add || op == Sub); - assert(PyLong_CheckExact(left) || PyFloat_CheckExact(left)); - assert(PyComplex_CheckExact(right)); PyObject *folded = op == Add ? PyNumber_Add(left, right) : PyNumber_Subtract(left, right); if (folded == NULL) { return ERROR; @@ -5457,13 +5469,13 @@ fold_binary_complex_expr(expr_ty e) } static int -try_fold_unary_or_binary_complex_const_expr(expr_ty key) +try_fold_unary_or_binary_complex_const_expr(expr_ty e) { - if (is_unary_or_complex_expr(key)) { - return fold_unary_or_complex_expr(key); + if (IS_MATCH_NUMERIC_OR_COMPLEX_UNARY_CONST_EXPR(e)) { + return fold_const_unary_or_complex_expr(e); } - if (is_complex_binop_expr(key)) { - return fold_binary_complex_expr(key); + if (IS_MATCH_COMPLEX_BINARY_CONST_EXPR(e)) { + return fold_const_binary_complex_expr(e); } return SUCCESS; } From 8c08228551e24771579c2cb0ae0af53980e6726c Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Tue, 11 Feb 2025 09:58:30 +0100 Subject: [PATCH 5/7] update macros --- Python/codegen.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Python/codegen.c b/Python/codegen.c index fc8339c98c0775..ecec1590812901 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -5386,15 +5386,12 @@ codegen_slice(compiler *c, expr_ty s) #define IS_UNARY_SUB_EXPR(N) \ (IS_UNARY_EXPR(N) && UNARY_EXPR_OP(N) == USub) -#define IS_MATCH_NUMERIC_UNARY_CONST_EXPR(N) \ +#define IS_NUMERIC_UNARY_CONST_EXPR(N) \ (IS_UNARY_SUB_EXPR(N) && IS_NUMERIC_CONST_EXPR(UNARY_EXPR_OPERAND(N))) -#define IS_MATCH_COMPLEX_UNARY_CONST_EXPR(N) \ +#define IS_COMPLEX_UNARY_CONST_EXPR(N) \ (IS_UNARY_SUB_EXPR(N) && IS_COMPLEX_CONST_EXPR(UNARY_EXPR_OPERAND(N))) -#define IS_MATCH_NUMERIC_OR_COMPLEX_UNARY_CONST_EXPR(N) \ - (IS_MATCH_NUMERIC_UNARY_CONST_EXPR(N) || IS_MATCH_COMPLEX_UNARY_CONST_EXPR(N)) - #define BINARY_EXPR(N) \ ((N)->v.BinOp) @@ -5416,10 +5413,13 @@ codegen_slice(compiler *c, expr_ty s) #define IS_BINARY_SUB_EXPR(N) \ (IS_BINARY_EXPR(N) && BINARY_EXPR_OP(N) == Sub) +#define IS_MATCH_NUMERIC_OR_COMPLEX_UNARY_CONST_EXPR(N) \ + (IS_NUMERIC_UNARY_CONST_EXPR(N) || IS_COMPLEX_UNARY_CONST_EXPR(N)) + #define IS_MATCH_COMPLEX_BINARY_CONST_EXPR(N) \ ( \ (IS_BINARY_ADD_EXPR(N) || IS_BINARY_SUB_EXPR(N)) \ - && (IS_MATCH_NUMERIC_UNARY_CONST_EXPR(BINARY_EXPR_LEFT(N)) || IS_CONST_EXPR(BINARY_EXPR_LEFT(N))) \ + && (IS_NUMERIC_UNARY_CONST_EXPR(BINARY_EXPR_LEFT(N)) || IS_CONST_EXPR(BINARY_EXPR_LEFT(N))) \ && IS_COMPLEX_CONST_EXPR(BINARY_EXPR_RIGHT(N)) \ ) @@ -5451,8 +5451,7 @@ fold_const_binary_complex_expr(expr_ty e) { assert(IS_MATCH_COMPLEX_BINARY_CONST_EXPR(e)); expr_ty left_expr = BINARY_EXPR_LEFT(e); - if (IS_UNARY_EXPR(left_expr)) { - assert(IS_MATCH_NUMERIC_UNARY_CONST_EXPR(left_expr)); + if (IS_NUMERIC_UNARY_CONST_EXPR(left_expr)) { RETURN_IF_ERROR(fold_const_unary_or_complex_expr(left_expr)); } assert(IS_CONST_EXPR(BINARY_EXPR_LEFT(e))); From 6aec21e2f988eebf0a0397157411305d0597a108 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Tue, 11 Feb 2025 10:37:45 +0100 Subject: [PATCH 6/7] update tests --- Lib/test/support/bytecode_helper.py | 7 +++++++ Lib/test/test_compiler_codegen.py | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Lib/test/support/bytecode_helper.py b/Lib/test/support/bytecode_helper.py index 4390035e753f16..5c76b624d00c4a 100644 --- a/Lib/test/support/bytecode_helper.py +++ b/Lib/test/support/bytecode_helper.py @@ -89,6 +89,13 @@ def assertInstructionsMatch(self, actual_seq, expected): idx = max([p[0] for p in enumerate(exp) if p[1] != -1]) self.assertEqual(exp[:idx], act[:idx]) + def assertNotInInstructionSequence(self, seq, expected_opcode): + self.assertIn(expected_opcode, dis.opmap) + for instr in seq.get_instructions(): + opcode, *_ = instr + if dis.opmap[expected_opcode] == opcode: + self.fail(f"{expected_opcode} appears in instructions sequence.") + def resolveAndRemoveLabels(self, insts): idx = 0 res = [] diff --git a/Lib/test/test_compiler_codegen.py b/Lib/test/test_compiler_codegen.py index 6024bd1f8d4d28..ba7958f389aa7d 100644 --- a/Lib/test/test_compiler_codegen.py +++ b/Lib/test/test_compiler_codegen.py @@ -193,6 +193,21 @@ def test_match_case_fold_codegen(self): case -0 - 1j: pass # match unary const int - complex case -0.1 + 1.1j: pass # match unary const float + complex case -0.1 - 1.1j: pass # match unary const float - complex + + case {-0: 0}: pass # match unary const int + case {-0.1: 0}: pass # match unary const float + case {-0j: 0}: pass # match unary const complex + case {1 + 2j: 0}: pass # match const int + const complex + case {1 - 2j: 0}: pass # match const int - const complex + case {1.1 + 2.1j: 0}: pass # match const float + const complex + case {1.1 - 2.1j: 0}: pass # match const float - const complex + case {-0 + 1j: 0}: pass # match unary const int + complex + case {-0 - 1j: 0}: pass # match unary const int - complex + case {-0.1 + 1.1j: 0}: pass # match unary const float + complex + case {-0.1 - 1.1j: 0}: pass # match unary const float - complex """) - instrs = [] # TODO - self.codegen_test(snippet, instrs, optimize_ast=False) + import ast + a = ast.parse(snippet, "my_file.py", "exec") + code = self.generate_code(a, optimize_ast=False) + self.assertNotInInstructionSequence(code, 'BINARY_OP') + self.assertNotInInstructionSequence(code, 'UNARY_NEGATIVE') From c17e7101fbcd22813cec305f7241e050bb156e47 Mon Sep 17 00:00:00 2001 From: Yan Yanchii Date: Tue, 11 Feb 2025 10:49:03 +0100 Subject: [PATCH 7/7] regen files --- .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 4 ++++ Modules/_testinternalcapi.c | 3 ++- Modules/clinic/_testinternalcapi.c.h | 24 ++++++++++++------- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 90214a314031d1..3498656a3f6d93 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1124,6 +1124,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(opener)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(operation)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(optimize)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(optimize_ast)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(options)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(order)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(origin)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 97a75d0c46c867..26a357d2fc91ca 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -613,6 +613,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(opener) STRUCT_FOR_ID(operation) STRUCT_FOR_ID(optimize) + STRUCT_FOR_ID(optimize_ast) STRUCT_FOR_ID(options) STRUCT_FOR_ID(order) STRUCT_FOR_ID(origin) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 4f928cc050bf8e..5527dc05e927d1 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1122,6 +1122,7 @@ extern "C" { INIT_ID(opener), \ INIT_ID(operation), \ INIT_ID(optimize), \ + INIT_ID(optimize_ast), \ INIT_ID(options), \ INIT_ID(order), \ INIT_ID(origin), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 5b78d038fc1192..3fdefd51bff291 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -2248,6 +2248,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(optimize_ast); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(options); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index b2ead16ab810a0..85aa6669a1902c 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -711,6 +711,7 @@ _testinternalcapi.compiler_codegen -> object filename: object optimize: int compile_mode: int = 0 + optimize_ast: int = 1 Apply compiler code generation to an AST. [clinic start generated code]*/ @@ -719,7 +720,7 @@ static PyObject * _testinternalcapi_compiler_codegen_impl(PyObject *module, PyObject *ast, PyObject *filename, int optimize, int compile_mode, int optimize_ast) -/*[clinic end generated code: output=40a68f6e13951cc8 input=a0e00784f1517cd7]*/ +/*[clinic end generated code: output=b4bf87f1213effd1 input=ece7e3ca206d738a]*/ { PyCompilerFlags flags = _PyCompilerFlags_INIT; if (!optimize_ast) diff --git a/Modules/clinic/_testinternalcapi.c.h b/Modules/clinic/_testinternalcapi.c.h index 416e3cff0c070e..2390755b453320 100644 --- a/Modules/clinic/_testinternalcapi.c.h +++ b/Modules/clinic/_testinternalcapi.c.h @@ -87,7 +87,8 @@ _testinternalcapi_new_instruction_sequence(PyObject *module, PyObject *Py_UNUSED } PyDoc_STRVAR(_testinternalcapi_compiler_codegen__doc__, -"compiler_codegen($module, /, ast, filename, optimize, compile_mode=0)\n" +"compiler_codegen($module, /, ast, filename, optimize, compile_mode=0,\n" +" optimize_ast=1)\n" "--\n" "\n" "Apply compiler code generation to an AST."); @@ -106,14 +107,14 @@ _testinternalcapi_compiler_codegen(PyObject *module, PyObject *const *args, Py_s PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - #define NUM_KEYWORDS 4 + #define NUM_KEYWORDS 5 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD PyObject *ob_item[NUM_KEYWORDS]; } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(ast), &_Py_ID(filename), &_Py_ID(optimize), &_Py_ID(compile_mode), }, + .ob_item = { &_Py_ID(ast), &_Py_ID(filename), &_Py_ID(optimize), &_Py_ID(compile_mode), &_Py_ID(optimize_ast), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -122,14 +123,14 @@ _testinternalcapi_compiler_codegen(PyObject *module, PyObject *const *args, Py_s # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"ast", "filename", "optimize", "compile_mode", NULL}; + static const char * const _keywords[] = {"ast", "filename", "optimize", "compile_mode", "optimize_ast", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "compiler_codegen", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[4]; + PyObject *argsbuf[5]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 3; PyObject *ast; PyObject *filename; @@ -151,9 +152,14 @@ _testinternalcapi_compiler_codegen(PyObject *module, PyObject *const *args, Py_s if (!noptargs) { goto skip_optional_pos; } - compile_mode = PyLong_AsInt(args[3]); - if (compile_mode == -1 && PyErr_Occurred()) { - goto exit; + if (args[3]) { + compile_mode = PyLong_AsInt(args[3]); + if (compile_mode == -1 && PyErr_Occurred()) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_pos; + } } optimize_ast = PyLong_AsInt(args[4]); if (optimize_ast == -1 && PyErr_Occurred()) { @@ -370,4 +376,4 @@ gh_119213_getargs(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyO exit: return return_value; } -/*[clinic end generated code: output=ec77971c6c2663da input=a9049054013a1b77]*/ +/*[clinic end generated code: output=693e868239038bbd input=a9049054013a1b77]*/