diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h index 3c21f83a18b..a1ac034e3e4 100644 --- a/Include/internal/pycore_compile.h +++ b/Include/internal/pycore_compile.h @@ -101,12 +101,6 @@ PyAPI_FUNC(PyObject*) _PyCompile_CodeGen( int optimize, int compile_mode); -// Export for '_testinternalcapi' shared extension -PyAPI_FUNC(PyObject*) _PyCompile_OptimizeCfg( - PyObject *instructions, - PyObject *consts, - int nlocals); - // Export for '_testinternalcapi' shared extension PyAPI_FUNC(PyCodeObject*) _PyCompile_Assemble(_PyCompile_CodeUnitMetadata *umd, PyObject *filename, diff --git a/Include/internal/pycore_flowgraph.h b/Include/internal/pycore_flowgraph.h index 819117b8311..5043260d2fd 100644 --- a/Include/internal/pycore_flowgraph.h +++ b/Include/internal/pycore_flowgraph.h @@ -24,6 +24,7 @@ int _PyCfgBuilder_CheckSize(struct _PyCfgBuilder* g); int _PyCfg_OptimizeCodeUnit(struct _PyCfgBuilder *g, PyObject *consts, PyObject *const_cache, int nlocals, int nparams, int firstlineno); +struct _PyCfgBuilder* _PyCfg_FromInstructionSequence(_PyInstructionSequence *seq); int _PyCfg_ToInstructionSequence(struct _PyCfgBuilder *g, _PyInstructionSequence *seq); int _PyCfg_OptimizedCfgToInstructionSequence(struct _PyCfgBuilder *g, _PyCompile_CodeUnitMetadata *umd, int code_flags, int *stackdepth, int *nlocalsplus, @@ -34,6 +35,12 @@ _PyAssemble_MakeCodeObject(_PyCompile_CodeUnitMetadata *u, PyObject *const_cache PyObject *consts, int maxdepth, _PyInstructionSequence *instrs, int nlocalsplus, int code_flags, PyObject *filename); +// Export for '_testinternalcapi' shared extension +PyAPI_FUNC(PyObject*) _PyCompile_OptimizeCfg( + PyObject *instructions, + PyObject *consts, + int nlocals); + #ifdef __cplusplus } #endif diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 129c1369067..d9b9c999603 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -18,6 +18,7 @@ #include "pycore_context.h" // _PyContext_NewHamtForTests() #include "pycore_dict.h" // _PyManagedDictPointer_GetValues() #include "pycore_fileutils.h" // _Py_normpath() +#include "pycore_flowgraph.h" // _PyCompile_OptimizeCfg() #include "pycore_frame.h" // _PyInterpreterFrame #include "pycore_gc.h" // PyGC_Head #include "pycore_hashtable.h" // _Py_hashtable_new() diff --git a/Python/compile.c b/Python/compile.c index 3a80577e0f2..7d74096fcdf 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -197,47 +197,6 @@ _PyCompile_EnsureArrayLargeEnough(int idx, void **array, int *alloc, return SUCCESS; } -static cfg_builder* -instr_sequence_to_cfg(instr_sequence *seq) { - if (_PyInstructionSequence_ApplyLabelMap(seq) < 0) { - return NULL; - } - cfg_builder *g = _PyCfgBuilder_New(); - if (g == NULL) { - return NULL; - } - for (int i = 0; i < seq->s_used; i++) { - seq->s_instrs[i].i_target = 0; - } - for (int i = 0; i < seq->s_used; i++) { - instruction *instr = &seq->s_instrs[i]; - if (HAS_TARGET(instr->i_opcode)) { - assert(instr->i_oparg >= 0 && instr->i_oparg < seq->s_used); - seq->s_instrs[instr->i_oparg].i_target = 1; - } - } - for (int i = 0; i < seq->s_used; i++) { - instruction *instr = &seq->s_instrs[i]; - if (instr->i_target) { - jump_target_label lbl_ = {i}; - if (_PyCfgBuilder_UseLabel(g, lbl_) < 0) { - goto error; - } - } - int opcode = instr->i_opcode; - int oparg = instr->i_oparg; - if (_PyCfgBuilder_Addop(g, opcode, oparg, instr->i_loc) < 0) { - goto error; - } - } - if (_PyCfgBuilder_CheckSize(g) < 0) { - goto error; - } - return g; -error: - _PyCfgBuilder_Free(g); - return NULL; -} /* The following items change on entry and exit of code blocks. They must be saved and restored when returning to a block. @@ -691,48 +650,6 @@ compiler_set_qualname(struct compiler *c) return SUCCESS; } -/* Return the stack effect of opcode with argument oparg. - - Some opcodes have different stack effect when jump to the target and - when not jump. The 'jump' parameter specifies the case: - - * 0 -- when not jump - * 1 -- when jump - * -1 -- maximal - */ -static int -stack_effect(int opcode, int oparg, int jump) -{ - if (opcode < 0) { - return PY_INVALID_STACK_EFFECT; - } - if ((opcode <= MAX_REAL_OPCODE) && (_PyOpcode_Deopt[opcode] != opcode)) { - // Specialized instructions are not supported. - return PY_INVALID_STACK_EFFECT; - } - int popped = _PyOpcode_num_popped(opcode, oparg); - int pushed = _PyOpcode_num_pushed(opcode, oparg); - if (popped < 0 || pushed < 0) { - return PY_INVALID_STACK_EFFECT; - } - if (IS_BLOCK_PUSH_OPCODE(opcode) && !jump) { - return 0; - } - return pushed - popped; -} - -int -PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump) -{ - return stack_effect(opcode, oparg, jump); -} - -int -PyCompile_OpcodeStackEffect(int opcode, int oparg) -{ - return stack_effect(opcode, oparg, -1); -} - int _PyCompile_OpcodeIsValid(int opcode) { @@ -7592,7 +7509,7 @@ optimize_and_assemble_code_unit(struct compiler_unit *u, PyObject *const_cache, if (consts == NULL) { goto error; } - g = instr_sequence_to_cfg(u->u_instr_sequence); + g = _PyCfg_FromInstructionSequence(u->u_instr_sequence); if (g == NULL) { goto error; } @@ -7645,39 +7562,6 @@ optimize_and_assemble(struct compiler *c, int addNone) return optimize_and_assemble_code_unit(u, const_cache, code_flags, filename); } -/* Access to compiler optimizations for unit tests. - * - * _PyCompile_CodeGen takes and AST, applies code-gen and - * returns the unoptimized CFG as an instruction list. - * - * _PyCompile_OptimizeCfg takes an instruction list, constructs - * a CFG, optimizes it and converts back to an instruction list. - * - * An instruction list is a PyList where each item is either - * a tuple describing a single instruction: - * (opcode, oparg, lineno, end_lineno, col, end_col), or - * a jump target label marking the beginning of a basic block. - */ - - -static PyObject * -cfg_to_instruction_sequence(cfg_builder *g) -{ - instr_sequence *seq = (instr_sequence *)_PyInstructionSequence_New(); - if (seq != NULL) { - if (_PyCfg_ToInstructionSequence(g, seq) < 0) { - goto error; - } - if (_PyInstructionSequence_ApplyLabelMap(seq) < 0) { - goto error; - } - } - return (PyObject*)seq; -error: - PyInstructionSequence_Fini(seq); - return NULL; -} - // C implementation of inspect.cleandoc() // // Difference from inspect.cleandoc(): @@ -7768,6 +7652,12 @@ _PyCompile_CleanDoc(PyObject *doc) return res; } +/* Access to compiler optimizations for unit tests. + * + * _PyCompile_CodeGen takes an AST, applies code-gen and + * returns the unoptimized CFG as an instruction list. + * + */ PyObject * _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags, @@ -7859,35 +7749,6 @@ finally: return res; } -PyObject * -_PyCompile_OptimizeCfg(PyObject *seq, PyObject *consts, int nlocals) -{ - if (!_PyInstructionSequence_Check(seq)) { - PyErr_SetString(PyExc_ValueError, "expected an instruction sequence"); - return NULL; - } - PyObject *const_cache = PyDict_New(); - if (const_cache == NULL) { - return NULL; - } - - PyObject *res = NULL; - cfg_builder *g = instr_sequence_to_cfg((instr_sequence*)seq); - if (g == NULL) { - goto error; - } - int nparams = 0, firstlineno = 1; - if (_PyCfg_OptimizeCodeUnit(g, consts, const_cache, nlocals, - nparams, firstlineno) < 0) { - goto error; - } - res = cfg_to_instruction_sequence(g); -error: - Py_DECREF(const_cache); - _PyCfgBuilder_Free(g); - return res; -} - int _PyCfg_JumpLabelsToTargets(cfg_builder *g); PyCodeObject * @@ -7908,7 +7769,7 @@ _PyCompile_Assemble(_PyCompile_CodeUnitMetadata *umd, PyObject *filename, return NULL; } - g = instr_sequence_to_cfg((instr_sequence*)seq); + g = _PyCfg_FromInstructionSequence((instr_sequence*)seq); if (g == NULL) { goto error; } diff --git a/Python/flowgraph.c b/Python/flowgraph.c index 83768023a4d..b0c8004130f 100644 --- a/Python/flowgraph.c +++ b/Python/flowgraph.c @@ -751,6 +751,36 @@ make_cfg_traversal_stack(basicblock *entryblock) { return stack; } +/* Return the stack effect of opcode with argument oparg. + + Some opcodes have different stack effect when jump to the target and + when not jump. The 'jump' parameter specifies the case: + + * 0 -- when not jump + * 1 -- when jump + * -1 -- maximal + */ +Py_LOCAL(int) +stack_effect(int opcode, int oparg, int jump) +{ + if (opcode < 0) { + return PY_INVALID_STACK_EFFECT; + } + if ((opcode <= MAX_REAL_OPCODE) && (_PyOpcode_Deopt[opcode] != opcode)) { + // Specialized instructions are not supported. + return PY_INVALID_STACK_EFFECT; + } + int popped = _PyOpcode_num_popped(opcode, oparg); + int pushed = _PyOpcode_num_pushed(opcode, oparg); + if (popped < 0 || pushed < 0) { + return PY_INVALID_STACK_EFFECT; + } + if (IS_BLOCK_PUSH_OPCODE(opcode) && !jump) { + return 0; + } + return pushed - popped; +} + Py_LOCAL_INLINE(int) stackdepth_push(basicblock ***sp, basicblock *b, int depth) { @@ -795,8 +825,7 @@ calculate_stackdepth(cfg_builder *g) basicblock *next = b->b_next; for (int i = 0; i < b->b_iused; i++) { cfg_instr *instr = &b->b_instr[i]; - int effect = PyCompile_OpcodeStackEffectWithJump( - instr->i_opcode, instr->i_oparg, 0); + int effect = stack_effect(instr->i_opcode, instr->i_oparg, 0); if (effect == PY_INVALID_STACK_EFFECT) { PyErr_Format(PyExc_SystemError, "Invalid stack effect for opcode=%d, arg=%i", @@ -813,8 +842,7 @@ calculate_stackdepth(cfg_builder *g) maxdepth = new_depth; } if (HAS_TARGET(instr->i_opcode)) { - effect = PyCompile_OpcodeStackEffectWithJump( - instr->i_opcode, instr->i_oparg, 1); + effect = stack_effect(instr->i_opcode, instr->i_oparg, 1); if (effect == PY_INVALID_STACK_EFFECT) { PyErr_Format(PyExc_SystemError, "Invalid stack effect for opcode=%d, arg=%i", @@ -2711,6 +2739,49 @@ prepare_localsplus(_PyCompile_CodeUnitMetadata *umd, cfg_builder *g, int code_fl return nlocalsplus; } +cfg_builder * +_PyCfg_FromInstructionSequence(_PyInstructionSequence *seq) +{ + if (_PyInstructionSequence_ApplyLabelMap(seq) < 0) { + return NULL; + } + cfg_builder *g = _PyCfgBuilder_New(); + if (g == NULL) { + return NULL; + } + for (int i = 0; i < seq->s_used; i++) { + seq->s_instrs[i].i_target = 0; + } + for (int i = 0; i < seq->s_used; i++) { + _PyInstruction *instr = &seq->s_instrs[i]; + if (HAS_TARGET(instr->i_opcode)) { + assert(instr->i_oparg >= 0 && instr->i_oparg < seq->s_used); + seq->s_instrs[instr->i_oparg].i_target = 1; + } + } + for (int i = 0; i < seq->s_used; i++) { + _PyInstruction *instr = &seq->s_instrs[i]; + if (instr->i_target) { + jump_target_label lbl_ = {i}; + if (_PyCfgBuilder_UseLabel(g, lbl_) < 0) { + goto error; + } + } + int opcode = instr->i_opcode; + int oparg = instr->i_oparg; + if (_PyCfgBuilder_Addop(g, opcode, oparg, instr->i_loc) < 0) { + goto error; + } + } + if (_PyCfgBuilder_CheckSize(g) < 0) { + goto error; + } + return g; +error: + _PyCfgBuilder_Free(g); + return NULL; +} + int _PyCfg_ToInstructionSequence(cfg_builder *g, _PyInstructionSequence *seq) { @@ -2742,6 +2813,9 @@ _PyCfg_ToInstructionSequence(cfg_builder *g, _PyInstructionSequence *seq) } } } + if (_PyInstructionSequence_ApplyLabelMap(seq) < 0) { + return ERROR; + } return SUCCESS; } @@ -2796,3 +2870,66 @@ _PyCfg_JumpLabelsToTargets(cfg_builder *g) RETURN_IF_ERROR(label_exception_targets(g->g_entryblock)); return SUCCESS; } + +/* Exported API functions */ + +int +PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump) +{ + return stack_effect(opcode, oparg, jump); +} + +int +PyCompile_OpcodeStackEffect(int opcode, int oparg) +{ + return stack_effect(opcode, oparg, -1); +} + +/* Access to compiler optimizations for unit tests. + + * _PyCompile_OptimizeCfg takes an instruction list, constructs + * a CFG, optimizes it and converts back to an instruction list. + */ + +static PyObject * +cfg_to_instruction_sequence(cfg_builder *g) +{ + _PyInstructionSequence *seq = (_PyInstructionSequence *)_PyInstructionSequence_New(); + if (seq == NULL) { + return NULL; + } + if (_PyCfg_ToInstructionSequence(g, seq) < 0) { + PyInstructionSequence_Fini(seq); + return NULL; + } + return (PyObject*)seq; +} + +PyObject * +_PyCompile_OptimizeCfg(PyObject *seq, PyObject *consts, int nlocals) +{ + if (!_PyInstructionSequence_Check(seq)) { + PyErr_SetString(PyExc_ValueError, "expected an instruction sequence"); + return NULL; + } + PyObject *const_cache = PyDict_New(); + if (const_cache == NULL) { + return NULL; + } + + PyObject *res = NULL; + cfg_builder *g = _PyCfg_FromInstructionSequence((_PyInstructionSequence*)seq); + if (g == NULL) { + goto error; + } + int nparams = 0, firstlineno = 1; + if (_PyCfg_OptimizeCodeUnit(g, consts, const_cache, nlocals, + nparams, firstlineno) < 0) { + goto error; + } + res = cfg_to_instruction_sequence(g); +error: + Py_DECREF(const_cache); + _PyCfgBuilder_Free(g); + return res; +}