summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>2023-08-10 12:03:47 (GMT)
committerGitHub <noreply@github.com>2023-08-10 12:03:47 (GMT)
commitbafedfbebd0f21291a7824d54e39ec79f142428b (patch)
tree82ea7fc832b4a1e0e1db11345f0d91ebfb30ec16 /Python
parent494e3d4436774a5ac1a569a635b8c5c881ef1c0c (diff)
downloadcpython-bafedfbebd0f21291a7824d54e39ec79f142428b.zip
cpython-bafedfbebd0f21291a7824d54e39ec79f142428b.tar.gz
cpython-bafedfbebd0f21291a7824d54e39ec79f142428b.tar.bz2
gh-106149: move CFG and basicblock definitions into flowgraph.c, use them as opaque types in compile.c (#107639)
Diffstat (limited to 'Python')
-rw-r--r--Python/compile.c432
-rw-r--r--Python/flowgraph.c437
2 files changed, 465 insertions, 404 deletions
diff --git a/Python/compile.c b/Python/compile.c
index 68af5d6..83cf455 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -70,9 +70,7 @@
&& ((C)->u->u_ste->ste_type == ModuleBlock))
typedef _PyCompilerSrcLocation location;
-typedef _PyCfgInstruction cfg_instr;
-typedef _PyCfgBasicblock basicblock;
-typedef _PyCfgBuilder cfg_builder;
+typedef struct _PyCfgBuilder cfg_builder;
#define LOCATION(LNO, END_LNO, COL, END_COL) \
((const _PyCompilerSrcLocation){(LNO), (END_LNO), (COL), (END_COL)})
@@ -101,7 +99,7 @@ static jump_target_label NO_LABEL = {-1};
}
#define USE_LABEL(C, LBL) \
- RETURN_IF_ERROR(instr_sequence_use_label(INSTR_SEQUENCE(C), (LBL).id))
+ RETURN_IF_ERROR(_PyCompile_InstructionSequence_UseLabel(INSTR_SEQUENCE(C), (LBL).id))
/* fblockinfo tracks the current frame block.
@@ -218,8 +216,9 @@ instr_sequence_new_label(instr_sequence *seq)
return lbl;
}
-static int
-instr_sequence_use_label(instr_sequence *seq, int lbl) {
+int
+_PyCompile_InstructionSequence_UseLabel(instr_sequence *seq, int lbl)
+{
int old_size = seq->s_labelmap_size;
RETURN_IF_ERROR(
_PyCompile_EnsureArrayLargeEnough(lbl,
@@ -238,8 +237,9 @@ instr_sequence_use_label(instr_sequence *seq, int lbl) {
#define MAX_OPCODE 511
-static int
-instr_sequence_addop(instr_sequence *seq, int opcode, int oparg, location loc)
+int
+_PyCompile_InstructionSequence_Addop(instr_sequence *seq, int opcode, int oparg,
+ location loc)
{
assert(0 <= opcode && opcode <= MAX_OPCODE);
assert(IS_WITHIN_OPCODE_RANGE(opcode));
@@ -288,10 +288,12 @@ instr_sequence_fini(instr_sequence *seq) {
seq->s_instrs = NULL;
}
-static int
-instr_sequence_to_cfg(instr_sequence *seq, cfg_builder *g) {
- memset(g, 0, sizeof(cfg_builder));
- RETURN_IF_ERROR(_PyCfgBuilder_Init(g));
+static cfg_builder*
+instr_sequence_to_cfg(instr_sequence *seq) {
+ cfg_builder *g = _PyCfgBuilder_New();
+ if (g == NULL) {
+ return NULL;
+ }
/* There can be more than one label for the same offset. The
* offset2lbl maping selects one of them which we use consistently.
@@ -300,7 +302,7 @@ instr_sequence_to_cfg(instr_sequence *seq, cfg_builder *g) {
int *offset2lbl = PyMem_Malloc(seq->s_used * sizeof(int));
if (offset2lbl == NULL) {
PyErr_NoMemory();
- return ERROR;
+ goto error;
}
for (int i = 0; i < seq->s_used; i++) {
offset2lbl[i] = -1;
@@ -336,23 +338,17 @@ instr_sequence_to_cfg(instr_sequence *seq, cfg_builder *g) {
goto error;
}
}
- PyMem_Free(offset2lbl);
-
- int nblocks = 0;
- for (basicblock *b = g->g_block_list; b != NULL; b = b->b_list) {
- nblocks++;
- }
- if ((size_t)nblocks > SIZE_MAX / sizeof(basicblock *)) {
- PyErr_NoMemory();
- return ERROR;
+ if (_PyCfgBuilder_CheckSize(g) < 0) {
+ goto error;
}
- return SUCCESS;
+ PyMem_Free(offset2lbl);
+ return g;
error:
+ _PyCfgBuilder_Free(g);
PyMem_Free(offset2lbl);
- return ERROR;
+ return NULL;
}
-
/* The following items change on entry and exit of code blocks.
They must be saved and restored when returning to a block.
*/
@@ -920,7 +916,7 @@ codegen_addop_noarg(instr_sequence *seq, int opcode, location loc)
{
assert(!OPCODE_HAS_ARG(opcode));
assert(!IS_ASSEMBLER_OPCODE(opcode));
- return instr_sequence_addop(seq, opcode, 0, loc);
+ return _PyCompile_InstructionSequence_Addop(seq, opcode, 0, loc);
}
static Py_ssize_t
@@ -1153,7 +1149,7 @@ codegen_addop_i(instr_sequence *seq, int opcode, Py_ssize_t oparg, location loc)
int oparg_ = Py_SAFE_DOWNCAST(oparg, Py_ssize_t, int);
assert(!IS_ASSEMBLER_OPCODE(opcode));
- return instr_sequence_addop(seq, opcode, oparg_, loc);
+ return _PyCompile_InstructionSequence_Addop(seq, opcode, oparg_, loc);
}
static int
@@ -1163,7 +1159,7 @@ codegen_addop_j(instr_sequence *seq, location loc,
assert(IS_LABEL(target));
assert(OPCODE_HAS_JUMP(opcode) || IS_BLOCK_PUSH_OPCODE(opcode));
assert(!IS_ASSEMBLER_OPCODE(opcode));
- return instr_sequence_addop(seq, opcode, target.id, loc);
+ return _PyCompile_InstructionSequence_Addop(seq, opcode, target.id, loc);
}
#define RETURN_IF_ERROR_IN_SCOPE(C, CALL) { \
@@ -7492,201 +7488,6 @@ _PyCompile_ConstCacheMergeOne(PyObject *const_cache, PyObject **obj)
return SUCCESS;
}
-
-static int *
-build_cellfixedoffsets(_PyCompile_CodeUnitMetadata *umd)
-{
- int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames);
- int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
- int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars);
-
- int noffsets = ncellvars + nfreevars;
- int *fixed = PyMem_New(int, noffsets);
- if (fixed == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- for (int i = 0; i < noffsets; i++) {
- fixed[i] = nlocals + i;
- }
-
- PyObject *varname, *cellindex;
- Py_ssize_t pos = 0;
- while (PyDict_Next(umd->u_cellvars, &pos, &varname, &cellindex)) {
- PyObject *varindex = PyDict_GetItem(umd->u_varnames, varname);
- if (varindex != NULL) {
- assert(PyLong_AS_LONG(cellindex) < INT_MAX);
- assert(PyLong_AS_LONG(varindex) < INT_MAX);
- int oldindex = (int)PyLong_AS_LONG(cellindex);
- int argoffset = (int)PyLong_AS_LONG(varindex);
- fixed[oldindex] = argoffset;
- }
- }
-
- return fixed;
-}
-
-#define IS_GENERATOR(CF) \
- ((CF) & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR))
-
-static int
-insert_prefix_instructions(_PyCompile_CodeUnitMetadata *umd, basicblock *entryblock,
- int *fixed, int nfreevars, int code_flags)
-{
- assert(umd->u_firstlineno > 0);
-
- /* Add the generator prefix instructions. */
- if (IS_GENERATOR(code_flags)) {
- /* Note that RETURN_GENERATOR + POP_TOP have a net stack effect
- * of 0. This is because RETURN_GENERATOR pushes an element
- * with _PyFrame_StackPush before switching stacks.
- */
- cfg_instr make_gen = {
- .i_opcode = RETURN_GENERATOR,
- .i_oparg = 0,
- .i_loc = LOCATION(umd->u_firstlineno, umd->u_firstlineno, -1, -1),
- .i_target = NULL,
- };
- RETURN_IF_ERROR(_PyBasicblock_InsertInstruction(entryblock, 0, &make_gen));
- cfg_instr pop_top = {
- .i_opcode = POP_TOP,
- .i_oparg = 0,
- .i_loc = NO_LOCATION,
- .i_target = NULL,
- };
- RETURN_IF_ERROR(_PyBasicblock_InsertInstruction(entryblock, 1, &pop_top));
- }
-
- /* Set up cells for any variable that escapes, to be put in a closure. */
- const int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
- if (ncellvars) {
- // umd->u_cellvars has the cells out of order so we sort them
- // before adding the MAKE_CELL instructions. Note that we
- // adjust for arg cells, which come first.
- const int nvars = ncellvars + (int)PyDict_GET_SIZE(umd->u_varnames);
- int *sorted = PyMem_RawCalloc(nvars, sizeof(int));
- if (sorted == NULL) {
- PyErr_NoMemory();
- return ERROR;
- }
- for (int i = 0; i < ncellvars; i++) {
- sorted[fixed[i]] = i + 1;
- }
- for (int i = 0, ncellsused = 0; ncellsused < ncellvars; i++) {
- int oldindex = sorted[i] - 1;
- if (oldindex == -1) {
- continue;
- }
- cfg_instr make_cell = {
- .i_opcode = MAKE_CELL,
- // This will get fixed in offset_derefs().
- .i_oparg = oldindex,
- .i_loc = NO_LOCATION,
- .i_target = NULL,
- };
- if (_PyBasicblock_InsertInstruction(entryblock, ncellsused, &make_cell) < 0) {
- PyMem_RawFree(sorted);
- return ERROR;
- }
- ncellsused += 1;
- }
- PyMem_RawFree(sorted);
- }
-
- if (nfreevars) {
- cfg_instr copy_frees = {
- .i_opcode = COPY_FREE_VARS,
- .i_oparg = nfreevars,
- .i_loc = NO_LOCATION,
- .i_target = NULL,
- };
- RETURN_IF_ERROR(_PyBasicblock_InsertInstruction(entryblock, 0, &copy_frees));
- }
-
- return SUCCESS;
-}
-
-static int
-fix_cell_offsets(_PyCompile_CodeUnitMetadata *umd, basicblock *entryblock, int *fixedmap)
-{
- int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames);
- int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
- int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars);
- int noffsets = ncellvars + nfreevars;
-
- // First deal with duplicates (arg cells).
- int numdropped = 0;
- for (int i = 0; i < noffsets ; i++) {
- if (fixedmap[i] == i + nlocals) {
- fixedmap[i] -= numdropped;
- }
- else {
- // It was a duplicate (cell/arg).
- numdropped += 1;
- }
- }
-
- // Then update offsets, either relative to locals or by cell2arg.
- for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
- for (int i = 0; i < b->b_iused; i++) {
- cfg_instr *inst = &b->b_instr[i];
- // This is called before extended args are generated.
- assert(inst->i_opcode != EXTENDED_ARG);
- int oldoffset = inst->i_oparg;
- switch(inst->i_opcode) {
- case MAKE_CELL:
- case LOAD_CLOSURE:
- case LOAD_DEREF:
- case STORE_DEREF:
- case DELETE_DEREF:
- case LOAD_FROM_DICT_OR_DEREF:
- assert(oldoffset >= 0);
- assert(oldoffset < noffsets);
- assert(fixedmap[oldoffset] >= 0);
- inst->i_oparg = fixedmap[oldoffset];
- }
- }
- }
-
- return numdropped;
-}
-
-
-static int
-prepare_localsplus(_PyCompile_CodeUnitMetadata *umd, cfg_builder *g, int code_flags)
-{
- assert(PyDict_GET_SIZE(umd->u_varnames) < INT_MAX);
- assert(PyDict_GET_SIZE(umd->u_cellvars) < INT_MAX);
- assert(PyDict_GET_SIZE(umd->u_freevars) < INT_MAX);
- int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames);
- int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
- int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars);
- assert(INT_MAX - nlocals - ncellvars > 0);
- assert(INT_MAX - nlocals - ncellvars - nfreevars > 0);
- int nlocalsplus = nlocals + ncellvars + nfreevars;
- int* cellfixedoffsets = build_cellfixedoffsets(umd);
- if (cellfixedoffsets == NULL) {
- return ERROR;
- }
-
-
- // This must be called before fix_cell_offsets().
- if (insert_prefix_instructions(umd, g->g_entryblock, cellfixedoffsets, nfreevars, code_flags)) {
- PyMem_Free(cellfixedoffsets);
- return ERROR;
- }
-
- int numdropped = fix_cell_offsets(umd, g->g_entryblock, cellfixedoffsets);
- PyMem_Free(cellfixedoffsets); // At this point we're done with it.
- cellfixedoffsets = NULL;
- if (numdropped < 0) {
- return ERROR;
- }
-
- nlocalsplus -= numdropped;
- return nlocalsplus;
-}
-
static int
add_return_at_end(struct compiler *c, int addNone)
{
@@ -7700,12 +7501,11 @@ add_return_at_end(struct compiler *c, int addNone)
return SUCCESS;
}
-static int cfg_to_instr_sequence(cfg_builder *g, instr_sequence *seq);
-
static PyCodeObject *
optimize_and_assemble_code_unit(struct compiler_unit *u, PyObject *const_cache,
int code_flags, PyObject *filename)
{
+ cfg_builder *g = NULL;
instr_sequence optimized_instrs;
memset(&optimized_instrs, 0, sizeof(instr_sequence));
@@ -7714,51 +7514,28 @@ optimize_and_assemble_code_unit(struct compiler_unit *u, PyObject *const_cache,
if (consts == NULL) {
goto error;
}
- cfg_builder g;
- if (instr_sequence_to_cfg(&u->u_instr_sequence, &g) < 0) {
+ g = instr_sequence_to_cfg(&u->u_instr_sequence);
+ if (g == NULL) {
goto error;
}
- int nparams = (int)PyList_GET_SIZE(u->u_ste->ste_varnames);
int nlocals = (int)PyDict_GET_SIZE(u->u_metadata.u_varnames);
+ int nparams = (int)PyList_GET_SIZE(u->u_ste->ste_varnames);
assert(u->u_metadata.u_firstlineno);
- if (_PyCfg_OptimizeCodeUnit(&g, consts, const_cache, nlocals,
+
+ if (_PyCfg_OptimizeCodeUnit(g, consts, const_cache, nlocals,
nparams, u->u_metadata.u_firstlineno) < 0) {
goto error;
}
- int stackdepth = _PyCfg_Stackdepth(&g);
- if (stackdepth < 0) {
+ int stackdepth;
+ int nlocalsplus;
+ if (_PyCfg_OptimizedCfgToInstructionSequence(g, &u->u_metadata, code_flags,
+ &stackdepth, &nlocalsplus,
+ &optimized_instrs) < 0) {
goto error;
}
- /* prepare_localsplus adds instructions for generators that push
- * and pop an item on the stack. This assertion makes sure there
- * is space on the stack for that.
- * It should always be true, because a generator must have at
- * least one expression or call to INTRINSIC_STOPITERATION_ERROR,
- * which requires stackspace.
- */
- assert(!(IS_GENERATOR(code_flags) && stackdepth == 0));
-
/** Assembly **/
- int nlocalsplus = prepare_localsplus(&u->u_metadata, &g, code_flags);
- if (nlocalsplus < 0) {
- goto error;
- }
-
- _PyCfg_ConvertPseudoOps(g.g_entryblock);
-
- /* Order of basic blocks must have been determined by now */
-
- if (_PyCfg_ResolveJumps(&g) < 0) {
- goto error;
- }
-
- /* Can't modify the bytecode after computing jump offsets. */
-
- if (cfg_to_instr_sequence(&g, &optimized_instrs) < 0) {
- goto error;
- }
co = _PyAssemble_MakeCodeObject(&u->u_metadata, const_cache, consts,
stackdepth, &optimized_instrs, nlocalsplus,
@@ -7767,7 +7544,7 @@ optimize_and_assemble_code_unit(struct compiler_unit *u, PyObject *const_cache,
error:
Py_XDECREF(consts);
instr_sequence_fini(&optimized_instrs);
- _PyCfgBuilder_Fini(&g);
+ _PyCfgBuilder_Free(g);
return co;
}
@@ -7790,39 +7567,6 @@ optimize_and_assemble(struct compiler *c, int addNone)
return optimize_and_assemble_code_unit(u, const_cache, code_flags, filename);
}
-static int
-cfg_to_instr_sequence(cfg_builder *g, instr_sequence *seq)
-{
- int lbl = 0;
- for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
- b->b_label = (jump_target_label){lbl};
- lbl += b->b_iused;
- }
- for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
- RETURN_IF_ERROR(instr_sequence_use_label(seq, b->b_label.id));
- for (int i = 0; i < b->b_iused; i++) {
- cfg_instr *instr = &b->b_instr[i];
- if (OPCODE_HAS_JUMP(instr->i_opcode)) {
- instr->i_oparg = instr->i_target->b_label.id;
- }
- RETURN_IF_ERROR(
- instr_sequence_addop(seq, instr->i_opcode, instr->i_oparg, instr->i_loc));
-
- _PyCompile_ExceptHandlerInfo *hi = &seq->s_instrs[seq->s_used-1].i_except_handler_info;
- if (instr->i_except != NULL) {
- hi->h_label = instr->i_except->b_label.id;
- hi->h_startdepth = instr->i_except->b_startdepth;
- hi->h_preserve_lasti = instr->i_except->b_preserve_lasti;
- }
- else {
- hi->h_label = -1;
- }
- }
- }
- return SUCCESS;
-}
-
-
/* Access to compiler optimizations for unit tests.
*
* _PyCompile_CodeGen takes and AST, applies code-gen and
@@ -7872,7 +7616,7 @@ instructions_to_instr_sequence(PyObject *instructions, instr_sequence *seq)
for (int i = 0; i < num_insts; i++) {
if (is_target[i]) {
- if (instr_sequence_use_label(seq, i) < 0) {
+ if (_PyCompile_InstructionSequence_UseLabel(seq, i) < 0) {
goto error;
}
}
@@ -7912,7 +7656,7 @@ instructions_to_instr_sequence(PyObject *instructions, instr_sequence *seq)
if (PyErr_Occurred()) {
goto error;
}
- if (instr_sequence_addop(seq, opcode, oparg, loc) < 0) {
+ if (_PyCompile_InstructionSequence_Addop(seq, opcode, oparg, loc) < 0) {
goto error;
}
}
@@ -7923,23 +7667,26 @@ error:
return ERROR;
}
-static int
-instructions_to_cfg(PyObject *instructions, cfg_builder *g)
+static cfg_builder*
+instructions_to_cfg(PyObject *instructions)
{
+ cfg_builder *g = NULL;
instr_sequence seq;
memset(&seq, 0, sizeof(instr_sequence));
if (instructions_to_instr_sequence(instructions, &seq) < 0) {
goto error;
}
- if (instr_sequence_to_cfg(&seq, g) < 0) {
+ g = instr_sequence_to_cfg(&seq);
+ if (g == NULL) {
goto error;
}
instr_sequence_fini(&seq);
- return SUCCESS;
+ return g;
error:
+ _PyCfgBuilder_Free(g);
instr_sequence_fini(&seq);
- return ERROR;
+ return NULL;
}
static PyObject *
@@ -7978,42 +7725,14 @@ error:
static PyObject *
cfg_to_instructions(cfg_builder *g)
{
- PyObject *instructions = PyList_New(0);
- if (instructions == NULL) {
+ instr_sequence seq;
+ memset(&seq, 0, sizeof(seq));
+ if (_PyCfg_ToInstructionSequence(g, &seq) < 0) {
return NULL;
}
- int lbl = 0;
- for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
- b->b_label = (jump_target_label){lbl};
- lbl += b->b_iused;
- }
- for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
- for (int i = 0; i < b->b_iused; i++) {
- cfg_instr *instr = &b->b_instr[i];
- location loc = instr->i_loc;
- int arg = HAS_TARGET(instr->i_opcode) ?
- instr->i_target->b_label.id : instr->i_oparg;
-
- PyObject *inst_tuple = Py_BuildValue(
- "(iiiiii)", instr->i_opcode, arg,
- loc.lineno, loc.end_lineno,
- loc.col_offset, loc.end_col_offset);
- if (inst_tuple == NULL) {
- goto error;
- }
-
- if (PyList_Append(instructions, inst_tuple) != 0) {
- Py_DECREF(inst_tuple);
- goto error;
- }
- Py_DECREF(inst_tuple);
- }
- }
-
- return instructions;
-error:
- Py_DECREF(instructions);
- return NULL;
+ PyObject *res = instr_sequence_to_instructions(&seq);
+ instr_sequence_fini(&seq);
+ return res;
}
// C implementation of inspect.cleandoc()
@@ -8195,34 +7914,36 @@ finally:
PyObject *
_PyCompile_OptimizeCfg(PyObject *instructions, PyObject *consts, int nlocals)
{
+ cfg_builder *g = NULL;
PyObject *res = NULL;
PyObject *const_cache = PyDict_New();
if (const_cache == NULL) {
return NULL;
}
- cfg_builder g;
- if (instructions_to_cfg(instructions, &g) < 0) {
+ g = instructions_to_cfg(instructions);
+ if (g == NULL) {
goto error;
}
int nparams = 0, firstlineno = 1;
- if (_PyCfg_OptimizeCodeUnit(&g, consts, const_cache, nlocals,
+ if (_PyCfg_OptimizeCodeUnit(g, consts, const_cache, nlocals,
nparams, firstlineno) < 0) {
goto error;
}
- res = cfg_to_instructions(&g);
+ res = cfg_to_instructions(g);
error:
Py_DECREF(const_cache);
- _PyCfgBuilder_Fini(&g);
+ _PyCfgBuilder_Free(g);
return res;
}
-int _PyCfg_JumpLabelsToTargets(basicblock *entryblock);
+int _PyCfg_JumpLabelsToTargets(cfg_builder *g);
PyCodeObject *
_PyCompile_Assemble(_PyCompile_CodeUnitMetadata *umd, PyObject *filename,
PyObject *instructions)
{
+ cfg_builder *g = NULL;
PyCodeObject *co = NULL;
instr_sequence optimized_instrs;
memset(&optimized_instrs, 0, sizeof(instr_sequence));
@@ -8232,37 +7953,20 @@ _PyCompile_Assemble(_PyCompile_CodeUnitMetadata *umd, PyObject *filename,
return NULL;
}
- cfg_builder g;
- if (instructions_to_cfg(instructions, &g) < 0) {
+ g = instructions_to_cfg(instructions);
+ if (g == NULL) {
goto error;
}
- if (_PyCfg_JumpLabelsToTargets(g.g_entryblock) < 0) {
- goto error;
- }
-
- int stackdepth = _PyCfg_Stackdepth(&g);
- if (stackdepth < 0) {
+ if (_PyCfg_JumpLabelsToTargets(g) < 0) {
goto error;
}
int code_flags = 0;
- int nlocalsplus = prepare_localsplus(umd, &g, code_flags);
- if (nlocalsplus < 0) {
- goto error;
- }
-
- _PyCfg_ConvertPseudoOps(g.g_entryblock);
-
- /* Order of basic blocks must have been determined by now */
-
- if (_PyCfg_ResolveJumps(&g) < 0) {
- goto error;
- }
-
- /* Can't modify the bytecode after computing jump offsets. */
-
- if (cfg_to_instr_sequence(&g, &optimized_instrs) < 0) {
+ int stackdepth, nlocalsplus;
+ if (_PyCfg_OptimizedCfgToInstructionSequence(g, umd, code_flags,
+ &stackdepth, &nlocalsplus,
+ &optimized_instrs) < 0) {
goto error;
}
@@ -8277,7 +7981,7 @@ _PyCompile_Assemble(_PyCompile_CodeUnitMetadata *umd, PyObject *filename,
error:
Py_DECREF(const_cache);
- _PyCfgBuilder_Fini(&g);
+ _PyCfgBuilder_Free(g);
instr_sequence_fini(&optimized_instrs);
return co;
}
diff --git a/Python/flowgraph.c b/Python/flowgraph.c
index 5b6b3f3..9d78656 100644
--- a/Python/flowgraph.c
+++ b/Python/flowgraph.c
@@ -24,15 +24,75 @@
typedef _PyCompilerSrcLocation location;
typedef _PyCfgJumpTargetLabel jump_target_label;
-typedef _PyCfgBasicblock basicblock;
-typedef _PyCfgBuilder cfg_builder;
-typedef _PyCfgInstruction cfg_instr;
+
+typedef struct _PyCfgInstruction {
+ int i_opcode;
+ int i_oparg;
+ _PyCompilerSrcLocation i_loc;
+ struct _PyCfgBasicblock *i_target; /* target block (if jump instruction) */
+ struct _PyCfgBasicblock *i_except; /* target block when exception is raised */
+} cfg_instr;
+
+typedef struct _PyCfgBasicblock {
+ /* Each basicblock in a compilation unit is linked via b_list in the
+ reverse order that the block are allocated. b_list points to the next
+ block in this list, not to be confused with b_next, which is next by
+ control flow. */
+ struct _PyCfgBasicblock *b_list;
+ /* The label of this block if it is a jump target, -1 otherwise */
+ _PyCfgJumpTargetLabel b_label;
+ /* Exception stack at start of block, used by assembler to create the exception handling table */
+ struct _PyCfgExceptStack *b_exceptstack;
+ /* pointer to an array of instructions, initially NULL */
+ cfg_instr *b_instr;
+ /* If b_next is non-NULL, it is a pointer to the next
+ block reached by normal control flow. */
+ struct _PyCfgBasicblock *b_next;
+ /* number of instructions used */
+ int b_iused;
+ /* length of instruction array (b_instr) */
+ int b_ialloc;
+ /* Used by add_checks_for_loads_of_unknown_variables */
+ uint64_t b_unsafe_locals_mask;
+ /* Number of predecessors that a block has. */
+ int b_predecessors;
+ /* depth of stack upon entry of block, computed by stackdepth() */
+ int b_startdepth;
+ /* Basic block is an exception handler that preserves lasti */
+ unsigned b_preserve_lasti : 1;
+ /* Used by compiler passes to mark whether they have visited a basic block. */
+ unsigned b_visited : 1;
+ /* b_except_handler is used by the cold-detection algorithm to mark exception targets */
+ unsigned b_except_handler : 1;
+ /* b_cold is true if this block is not perf critical (like an exception handler) */
+ unsigned b_cold : 1;
+ /* b_warm is used by the cold-detection algorithm to mark blocks which are definitely not cold */
+ unsigned b_warm : 1;
+} basicblock;
+
+
+struct _PyCfgBuilder {
+ /* The entryblock, at which control flow begins. All blocks of the
+ CFG are reachable through the b_next links */
+ struct _PyCfgBasicblock *g_entryblock;
+ /* Pointer to the most recently allocated block. By following
+ b_list links, you can reach all allocated blocks. */
+ struct _PyCfgBasicblock *g_block_list;
+ /* pointer to the block currently being constructed */
+ struct _PyCfgBasicblock *g_curblock;
+ /* label for the next instruction to be placed */
+ _PyCfgJumpTargetLabel g_current_label;
+};
+
+typedef struct _PyCfgBuilder cfg_builder;
static const jump_target_label NO_LABEL = {-1};
#define SAME_LABEL(L1, L2) ((L1).id == (L2).id)
#define IS_LABEL(L) (!SAME_LABEL((L), (NO_LABEL)))
+#define LOCATION(LNO, END_LNO, COL, END_COL) \
+ ((const _PyCompilerSrcLocation){(LNO), (END_LNO), (COL), (END_COL)})
static inline int
is_block_push(cfg_instr *i)
@@ -50,7 +110,7 @@ is_jump(cfg_instr *i)
#define INSTR_SET_OP1(I, OP, ARG) \
do { \
assert(OPCODE_HAS_ARG(OP)); \
- _PyCfgInstruction *_instr__ptr_ = (I); \
+ cfg_instr *_instr__ptr_ = (I); \
_instr__ptr_->i_opcode = (OP); \
_instr__ptr_->i_oparg = (ARG); \
} while (0);
@@ -59,7 +119,7 @@ is_jump(cfg_instr *i)
#define INSTR_SET_OP0(I, OP) \
do { \
assert(!OPCODE_HAS_ARG(OP)); \
- _PyCfgInstruction *_instr__ptr_ = (I); \
+ cfg_instr *_instr__ptr_ = (I); \
_instr__ptr_->i_opcode = (OP); \
_instr__ptr_->i_oparg = 0; \
} while (0);
@@ -148,8 +208,8 @@ basicblock_last_instr(const basicblock *b) {
}
static inline int
-basicblock_nofallthrough(const _PyCfgBasicblock *b) {
- _PyCfgInstruction *last = basicblock_last_instr(b);
+basicblock_nofallthrough(const basicblock *b) {
+ cfg_instr *last = basicblock_last_instr(b);
return (last &&
(IS_SCOPE_EXIT_OPCODE(last->i_opcode) ||
IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode)));
@@ -175,8 +235,8 @@ copy_basicblock(cfg_builder *g, basicblock *block)
return result;
}
-int
-_PyBasicblock_InsertInstruction(basicblock *block, int pos, cfg_instr *instr) {
+static int
+basicblock_insert_instruction(basicblock *block, int pos, cfg_instr *instr) {
RETURN_IF_ERROR(basicblock_next_instr(block));
for (int i = block->b_iused - 1; i > pos; i--) {
block->b_instr[i] = block->b_instr[i-1];
@@ -311,8 +371,8 @@ cfg_builder_check(cfg_builder *g)
}
#endif
-int
-_PyCfgBuilder_Init(cfg_builder *g)
+static int
+init_cfg_builder(cfg_builder *g)
{
g->g_block_list = NULL;
basicblock *block = cfg_builder_new_block(g);
@@ -324,9 +384,28 @@ _PyCfgBuilder_Init(cfg_builder *g)
return SUCCESS;
}
+cfg_builder *
+_PyCfgBuilder_New(void)
+{
+ cfg_builder *g = PyMem_Malloc(sizeof(cfg_builder));
+ if (g == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ memset(g, 0, sizeof(cfg_builder));
+ if (init_cfg_builder(g) < 0) {
+ PyMem_Free(g);
+ return NULL;
+ }
+ return g;
+}
+
void
-_PyCfgBuilder_Fini(cfg_builder* g)
+_PyCfgBuilder_Free(cfg_builder *g)
{
+ if (g == NULL) {
+ return;
+ }
assert(cfg_builder_check(g));
basicblock *b = g->g_block_list;
while (b != NULL) {
@@ -337,6 +416,21 @@ _PyCfgBuilder_Fini(cfg_builder* g)
PyObject_Free((void *)b);
b = next;
}
+ PyMem_Free(g);
+}
+
+int
+_PyCfgBuilder_CheckSize(cfg_builder *g)
+{
+ int nblocks = 0;
+ for (basicblock *b = g->g_block_list; b != NULL; b = b->b_list) {
+ nblocks++;
+ }
+ if ((size_t)nblocks > SIZE_MAX / sizeof(basicblock *)) {
+ PyErr_NoMemory();
+ return ERROR;
+ }
+ return SUCCESS;
}
int
@@ -450,7 +544,7 @@ normalize_jumps_in_block(cfg_builder *g, basicblock *b) {
static int
-normalize_jumps(_PyCfgBuilder *g)
+normalize_jumps(cfg_builder *g)
{
basicblock *entryblock = g->g_entryblock;
for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
@@ -463,14 +557,6 @@ normalize_jumps(_PyCfgBuilder *g)
return SUCCESS;
}
-int
-_PyCfg_ResolveJumps(_PyCfgBuilder *g)
-{
- RETURN_IF_ERROR(normalize_jumps(g));
- assert(no_redundant_jumps(g));
- return SUCCESS;
-}
-
static int
check_cfg(cfg_builder *g) {
for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
@@ -529,9 +615,9 @@ translate_jump_labels_to_targets(basicblock *entryblock)
}
int
-_PyCfg_JumpLabelsToTargets(basicblock *entryblock)
+_PyCfg_JumpLabelsToTargets(cfg_builder *g)
{
- return translate_jump_labels_to_targets(entryblock);
+ return translate_jump_labels_to_targets(g->g_entryblock);
}
static int
@@ -553,10 +639,14 @@ mark_except_handlers(basicblock *entryblock) {
}
-typedef _PyCfgExceptStack ExceptStack;
+struct _PyCfgExceptStack {
+ basicblock *handlers[CO_MAXBLOCKS+1];
+ int depth;
+};
+
static basicblock *
-push_except_block(ExceptStack *stack, cfg_instr *setup) {
+push_except_block(struct _PyCfgExceptStack *stack, cfg_instr *setup) {
assert(is_block_push(setup));
int opcode = setup->i_opcode;
basicblock * target = setup->i_target;
@@ -568,19 +658,19 @@ push_except_block(ExceptStack *stack, cfg_instr *setup) {
}
static basicblock *
-pop_except_block(ExceptStack *stack) {
+pop_except_block(struct _PyCfgExceptStack *stack) {
assert(stack->depth > 0);
return stack->handlers[--stack->depth];
}
static basicblock *
-except_stack_top(ExceptStack *stack) {
+except_stack_top(struct _PyCfgExceptStack *stack) {
return stack->handlers[stack->depth];
}
-static ExceptStack *
+static struct _PyCfgExceptStack *
make_except_stack(void) {
- ExceptStack *new = PyMem_Malloc(sizeof(ExceptStack));
+ struct _PyCfgExceptStack *new = PyMem_Malloc(sizeof(struct _PyCfgExceptStack));
if (new == NULL) {
PyErr_NoMemory();
return NULL;
@@ -590,14 +680,14 @@ make_except_stack(void) {
return new;
}
-static ExceptStack *
-copy_except_stack(ExceptStack *stack) {
- ExceptStack *copy = PyMem_Malloc(sizeof(ExceptStack));
+static struct _PyCfgExceptStack *
+copy_except_stack(struct _PyCfgExceptStack *stack) {
+ struct _PyCfgExceptStack *copy = PyMem_Malloc(sizeof(struct _PyCfgExceptStack));
if (copy == NULL) {
PyErr_NoMemory();
return NULL;
}
- memcpy(copy, stack, sizeof(ExceptStack));
+ memcpy(copy, stack, sizeof(struct _PyCfgExceptStack));
return copy;
}
@@ -633,8 +723,8 @@ stackdepth_push(basicblock ***sp, basicblock *b, int depth)
/* Find the flow path that needs the largest stack. We assume that
* cycles in the flow graph have no net effect on the stack depth.
*/
-int
-_PyCfg_Stackdepth(cfg_builder *g)
+static int
+calculate_stackdepth(cfg_builder *g)
{
basicblock *entryblock = g->g_entryblock;
for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
@@ -723,7 +813,7 @@ label_exception_targets(basicblock *entryblock) {
if (todo_stack == NULL) {
return ERROR;
}
- ExceptStack *except_stack = make_except_stack();
+ struct _PyCfgExceptStack *except_stack = make_except_stack();
if (except_stack == NULL) {
PyMem_Free(todo_stack);
PyErr_NoMemory();
@@ -747,7 +837,7 @@ label_exception_targets(basicblock *entryblock) {
cfg_instr *instr = &b->b_instr[i];
if (is_block_push(instr)) {
if (!instr->i_target->b_visited) {
- ExceptStack *copy = copy_except_stack(except_stack);
+ struct _PyCfgExceptStack *copy = copy_except_stack(except_stack);
if (copy == NULL) {
goto error;
}
@@ -766,7 +856,7 @@ label_exception_targets(basicblock *entryblock) {
assert(i == b->b_iused -1);
if (!instr->i_target->b_visited) {
if (BB_HAS_FALLTHROUGH(b)) {
- ExceptStack *copy = copy_except_stack(except_stack);
+ struct _PyCfgExceptStack *copy = copy_except_stack(except_stack);
if (copy == NULL) {
goto error;
}
@@ -2103,8 +2193,8 @@ push_cold_blocks_to_end(cfg_builder *g) {
return SUCCESS;
}
-void
-_PyCfg_ConvertPseudoOps(basicblock *entryblock)
+static void
+convert_pseudo_ops(basicblock *entryblock)
{
for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
for (int i = 0; i < b->b_iused; i++) {
@@ -2293,3 +2383,270 @@ _PyCfg_OptimizeCodeUnit(cfg_builder *g, PyObject *consts, PyObject *const_cache,
RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno));
return SUCCESS;
}
+
+static int *
+build_cellfixedoffsets(_PyCompile_CodeUnitMetadata *umd)
+{
+ int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames);
+ int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
+ int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars);
+
+ int noffsets = ncellvars + nfreevars;
+ int *fixed = PyMem_New(int, noffsets);
+ if (fixed == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ for (int i = 0; i < noffsets; i++) {
+ fixed[i] = nlocals + i;
+ }
+
+ PyObject *varname, *cellindex;
+ Py_ssize_t pos = 0;
+ while (PyDict_Next(umd->u_cellvars, &pos, &varname, &cellindex)) {
+ PyObject *varindex = PyDict_GetItem(umd->u_varnames, varname);
+ if (varindex != NULL) {
+ assert(PyLong_AS_LONG(cellindex) < INT_MAX);
+ assert(PyLong_AS_LONG(varindex) < INT_MAX);
+ int oldindex = (int)PyLong_AS_LONG(cellindex);
+ int argoffset = (int)PyLong_AS_LONG(varindex);
+ fixed[oldindex] = argoffset;
+ }
+ }
+
+ return fixed;
+}
+
+#define IS_GENERATOR(CF) \
+ ((CF) & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR))
+
+static int
+insert_prefix_instructions(_PyCompile_CodeUnitMetadata *umd, basicblock *entryblock,
+ int *fixed, int nfreevars, int code_flags)
+{
+ assert(umd->u_firstlineno > 0);
+
+ /* Add the generator prefix instructions. */
+ if (IS_GENERATOR(code_flags)) {
+ /* Note that RETURN_GENERATOR + POP_TOP have a net stack effect
+ * of 0. This is because RETURN_GENERATOR pushes an element
+ * with _PyFrame_StackPush before switching stacks.
+ */
+ cfg_instr make_gen = {
+ .i_opcode = RETURN_GENERATOR,
+ .i_oparg = 0,
+ .i_loc = LOCATION(umd->u_firstlineno, umd->u_firstlineno, -1, -1),
+ .i_target = NULL,
+ };
+ RETURN_IF_ERROR(basicblock_insert_instruction(entryblock, 0, &make_gen));
+ cfg_instr pop_top = {
+ .i_opcode = POP_TOP,
+ .i_oparg = 0,
+ .i_loc = NO_LOCATION,
+ .i_target = NULL,
+ };
+ RETURN_IF_ERROR(basicblock_insert_instruction(entryblock, 1, &pop_top));
+ }
+
+ /* Set up cells for any variable that escapes, to be put in a closure. */
+ const int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
+ if (ncellvars) {
+ // umd->u_cellvars has the cells out of order so we sort them
+ // before adding the MAKE_CELL instructions. Note that we
+ // adjust for arg cells, which come first.
+ const int nvars = ncellvars + (int)PyDict_GET_SIZE(umd->u_varnames);
+ int *sorted = PyMem_RawCalloc(nvars, sizeof(int));
+ if (sorted == NULL) {
+ PyErr_NoMemory();
+ return ERROR;
+ }
+ for (int i = 0; i < ncellvars; i++) {
+ sorted[fixed[i]] = i + 1;
+ }
+ for (int i = 0, ncellsused = 0; ncellsused < ncellvars; i++) {
+ int oldindex = sorted[i] - 1;
+ if (oldindex == -1) {
+ continue;
+ }
+ cfg_instr make_cell = {
+ .i_opcode = MAKE_CELL,
+ // This will get fixed in offset_derefs().
+ .i_oparg = oldindex,
+ .i_loc = NO_LOCATION,
+ .i_target = NULL,
+ };
+ if (basicblock_insert_instruction(entryblock, ncellsused, &make_cell) < 0) {
+ PyMem_RawFree(sorted);
+ return ERROR;
+ }
+ ncellsused += 1;
+ }
+ PyMem_RawFree(sorted);
+ }
+
+ if (nfreevars) {
+ cfg_instr copy_frees = {
+ .i_opcode = COPY_FREE_VARS,
+ .i_oparg = nfreevars,
+ .i_loc = NO_LOCATION,
+ .i_target = NULL,
+ };
+ RETURN_IF_ERROR(basicblock_insert_instruction(entryblock, 0, &copy_frees));
+ }
+
+ return SUCCESS;
+}
+
+static int
+fix_cell_offsets(_PyCompile_CodeUnitMetadata *umd, basicblock *entryblock, int *fixedmap)
+{
+ int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames);
+ int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
+ int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars);
+ int noffsets = ncellvars + nfreevars;
+
+ // First deal with duplicates (arg cells).
+ int numdropped = 0;
+ for (int i = 0; i < noffsets ; i++) {
+ if (fixedmap[i] == i + nlocals) {
+ fixedmap[i] -= numdropped;
+ }
+ else {
+ // It was a duplicate (cell/arg).
+ numdropped += 1;
+ }
+ }
+
+ // Then update offsets, either relative to locals or by cell2arg.
+ for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
+ for (int i = 0; i < b->b_iused; i++) {
+ cfg_instr *inst = &b->b_instr[i];
+ // This is called before extended args are generated.
+ assert(inst->i_opcode != EXTENDED_ARG);
+ int oldoffset = inst->i_oparg;
+ switch(inst->i_opcode) {
+ case MAKE_CELL:
+ case LOAD_CLOSURE:
+ case LOAD_DEREF:
+ case STORE_DEREF:
+ case DELETE_DEREF:
+ case LOAD_FROM_DICT_OR_DEREF:
+ assert(oldoffset >= 0);
+ assert(oldoffset < noffsets);
+ assert(fixedmap[oldoffset] >= 0);
+ inst->i_oparg = fixedmap[oldoffset];
+ }
+ }
+ }
+
+ return numdropped;
+}
+
+static int
+prepare_localsplus(_PyCompile_CodeUnitMetadata *umd, cfg_builder *g, int code_flags)
+{
+ assert(PyDict_GET_SIZE(umd->u_varnames) < INT_MAX);
+ assert(PyDict_GET_SIZE(umd->u_cellvars) < INT_MAX);
+ assert(PyDict_GET_SIZE(umd->u_freevars) < INT_MAX);
+ int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames);
+ int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars);
+ int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars);
+ assert(INT_MAX - nlocals - ncellvars > 0);
+ assert(INT_MAX - nlocals - ncellvars - nfreevars > 0);
+ int nlocalsplus = nlocals + ncellvars + nfreevars;
+ int* cellfixedoffsets = build_cellfixedoffsets(umd);
+ if (cellfixedoffsets == NULL) {
+ return ERROR;
+ }
+
+ // This must be called before fix_cell_offsets().
+ if (insert_prefix_instructions(umd, g->g_entryblock, cellfixedoffsets, nfreevars, code_flags)) {
+ PyMem_Free(cellfixedoffsets);
+ return ERROR;
+ }
+
+ int numdropped = fix_cell_offsets(umd, g->g_entryblock, cellfixedoffsets);
+ PyMem_Free(cellfixedoffsets); // At this point we're done with it.
+ cellfixedoffsets = NULL;
+ if (numdropped < 0) {
+ return ERROR;
+ }
+
+ nlocalsplus -= numdropped;
+ return nlocalsplus;
+}
+
+int
+_PyCfg_ToInstructionSequence(cfg_builder *g, _PyCompile_InstructionSequence *seq)
+{
+ int lbl = 0;
+ for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
+ b->b_label = (jump_target_label){lbl};
+ lbl += b->b_iused;
+ }
+ for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) {
+ RETURN_IF_ERROR(_PyCompile_InstructionSequence_UseLabel(seq, b->b_label.id));
+ for (int i = 0; i < b->b_iused; i++) {
+ cfg_instr *instr = &b->b_instr[i];
+ if (OPCODE_HAS_JUMP(instr->i_opcode)) {
+ instr->i_oparg = instr->i_target->b_label.id;
+ }
+ RETURN_IF_ERROR(
+ _PyCompile_InstructionSequence_Addop(
+ seq, instr->i_opcode, instr->i_oparg, instr->i_loc));
+
+ _PyCompile_ExceptHandlerInfo *hi = &seq->s_instrs[seq->s_used-1].i_except_handler_info;
+ if (instr->i_except != NULL) {
+ hi->h_label = instr->i_except->b_label.id;
+ hi->h_startdepth = instr->i_except->b_startdepth;
+ hi->h_preserve_lasti = instr->i_except->b_preserve_lasti;
+ }
+ else {
+ hi->h_label = -1;
+ }
+ }
+ }
+ return SUCCESS;
+}
+
+
+int
+_PyCfg_OptimizedCfgToInstructionSequence(cfg_builder *g,
+ _PyCompile_CodeUnitMetadata *umd, int code_flags,
+ int *stackdepth, int *nlocalsplus,
+ _PyCompile_InstructionSequence *seq)
+{
+ *stackdepth = calculate_stackdepth(g);
+ if (*stackdepth < 0) {
+ return ERROR;
+ }
+
+ /* prepare_localsplus adds instructions for generators that push
+ * and pop an item on the stack. This assertion makes sure there
+ * is space on the stack for that.
+ * It should always be true, because a generator must have at
+ * least one expression or call to INTRINSIC_STOPITERATION_ERROR,
+ * which requires stackspace.
+ */
+ assert(!(IS_GENERATOR(code_flags) && *stackdepth == 0));
+
+ *nlocalsplus = prepare_localsplus(umd, g, code_flags);
+ if (*nlocalsplus < 0) {
+ return ERROR;
+ }
+
+ convert_pseudo_ops(g->g_entryblock);
+
+ /* Order of basic blocks must have been determined by now */
+
+ RETURN_IF_ERROR(normalize_jumps(g));
+ assert(no_redundant_jumps(g));
+
+ /* Can't modify the bytecode after computing jump offsets. */
+ if (_PyCfg_ToInstructionSequence(g, seq) < 0) {
+ return ERROR;
+ }
+
+ return SUCCESS;
+}
+