Skip to content

gh-126835: Fold unary & binary complex constant expressions during code generation. #129963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/cpython/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions Lib/test/support/bytecode_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -138,8 +145,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


Expand Down
58 changes: 56 additions & 2 deletions Lib/test/test_compiler_codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -157,3 +157,57 @@ 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

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
""")
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')
11 changes: 7 additions & 4 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -711,18 +711,21 @@ _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]*/

static PyObject *
_testinternalcapi_compiler_codegen_impl(PyObject *module, PyObject *ast,
PyObject *filename, int optimize,
int compile_mode)
/*[clinic end generated code: output=40a68f6e13951cc8 input=a0e00784f1517cd7]*/
int compile_mode, int optimize_ast)
/*[clinic end generated code: output=b4bf87f1213effd1 input=ece7e3ca206d738a]*/
{
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);
}


Expand Down
33 changes: 22 additions & 11 deletions Modules/clinic/_testinternalcapi.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading