summaryrefslogtreecommitdiffstats
path: root/Python/compile.c
diff options
context:
space:
mode:
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>2022-06-02 13:13:43 (GMT)
committerGitHub <noreply@github.com>2022-06-02 13:13:43 (GMT)
commit94b1586ca538d24388a159de6dda7eff1c230364 (patch)
tree57b003806e1043cbb901ec5bab309705420b839e /Python/compile.c
parentaf5bb1fba49c1f703890e28f69f0928109ecd815 (diff)
downloadcpython-94b1586ca538d24388a159de6dda7eff1c230364.zip
cpython-94b1586ca538d24388a159de6dda7eff1c230364.tar.gz
cpython-94b1586ca538d24388a159de6dda7eff1c230364.tar.bz2
gh-93356: Lay out exception handling code at end of code unit (GH-92769)
Diffstat (limited to 'Python/compile.c')
-rw-r--r--Python/compile.c262
1 files changed, 220 insertions, 42 deletions
diff --git a/Python/compile.c b/Python/compile.c
index a6788b7..75aa8bb 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -248,6 +248,8 @@ typedef struct basicblock_ {
int b_ialloc;
/* Number of predecssors that a block has. */
int b_predecessors;
+ /* Number of predecssors that a block has as an exception handler. */
+ int b_except_predecessors;
/* depth of stack upon entry of block, computed by stackdepth() */
int b_startdepth;
/* instruction offset for block, computed by assemble_jump_offsets() */
@@ -262,6 +264,10 @@ typedef struct basicblock_ {
unsigned b_exit : 1;
/* b_return is true if a RETURN_VALUE opcode is inserted. */
unsigned b_return : 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;
@@ -7352,6 +7358,155 @@ error:
return -1;
}
+static int
+mark_warm(basicblock *entry) {
+ basicblock **stack = make_cfg_traversal_stack(entry);
+ if (stack == NULL) {
+ return -1;
+ }
+ basicblock **sp = stack;
+
+ *sp++ = entry;
+ entry->b_visited = 1;
+ while (sp > stack) {
+ basicblock *b = *(--sp);
+ assert(!b->b_except_predecessors);
+ b->b_warm = 1;
+ basicblock *next = b->b_next;
+ if (next && !b->b_nofallthrough && !next->b_visited) {
+ *sp++ = next;
+ next->b_visited = 1;
+ }
+ for (int i=0; i < b->b_iused; i++) {
+ struct instr *instr = &b->b_instr[i];
+ if (is_jump(instr) && !instr->i_target->b_visited) {
+ *sp++ = instr->i_target;
+ instr->i_target->b_visited = 1;
+ }
+ }
+ }
+ PyMem_Free(stack);
+ return 0;
+}
+
+static int
+mark_cold(basicblock *entry) {
+ for (basicblock *b = entry; b != NULL; b = b->b_next) {
+ assert(!b->b_cold && !b->b_warm);
+ }
+ if (mark_warm(entry) < 0) {
+ return -1;
+ }
+
+ basicblock **stack = make_cfg_traversal_stack(entry);
+ if (stack == NULL) {
+ return -1;
+ }
+
+ basicblock **sp = stack;
+ for (basicblock *b = entry; b != NULL; b = b->b_next) {
+ if (b->b_except_predecessors) {
+ assert(b->b_except_predecessors == b->b_predecessors);
+ assert(!b->b_warm);
+ *sp++ = b;
+ b->b_visited = 1;
+ }
+ }
+
+ while (sp > stack) {
+ basicblock *b = *(--sp);
+ b->b_cold = 1;
+ basicblock *next = b->b_next;
+ if (next && !b->b_nofallthrough) {
+ if (!next->b_warm && !next->b_visited) {
+ *sp++ = next;
+ next->b_visited = 1;
+ }
+ }
+ for (int i = 0; i < b->b_iused; i++) {
+ struct instr *instr = &b->b_instr[i];
+ if (is_jump(instr)) {
+ assert(i == b->b_iused-1);
+ basicblock *target = b->b_instr[i].i_target;
+ if (!target->b_warm && !target->b_visited) {
+ *sp++ = target;
+ target->b_visited = 1;
+ }
+ }
+ }
+ }
+ PyMem_Free(stack);
+ return 0;
+}
+
+static int
+push_cold_blocks_to_end(struct compiler *c, basicblock *entry, int code_flags) {
+ if (entry->b_next == NULL) {
+ /* single basicblock, no need to reorder */
+ return 0;
+ }
+ if (mark_cold(entry) < 0) {
+ return -1;
+ }
+
+ /* If we have a cold block with fallthrough to a warm block, add */
+ /* an explicit jump instead of fallthrough */
+ for (basicblock *b = entry; b != NULL; b = b->b_next) {
+ if (b->b_cold && !b->b_nofallthrough && b->b_next && b->b_next->b_warm) {
+ basicblock *explicit_jump = compiler_new_block(c);
+ if (explicit_jump == NULL) {
+ return -1;
+ }
+ basicblock_add_jump(explicit_jump, JUMP, -1, 0, 0, 0, b->b_next);
+
+ explicit_jump->b_cold = 1;
+ explicit_jump->b_nofallthrough = 1;
+ explicit_jump->b_next = b->b_next;
+ b->b_next = explicit_jump;
+ }
+ }
+
+ assert(!entry->b_cold); /* First block can't be cold */
+ basicblock *cold_blocks = NULL;
+ basicblock *cold_blocks_tail = NULL;
+
+ basicblock *b = entry;
+ while(b->b_next) {
+ assert(!b->b_cold);
+ while (b->b_next && !b->b_next->b_cold) {
+ b = b->b_next;
+ }
+ if (b->b_next == NULL) {
+ /* no more cold blocks */
+ break;
+ }
+
+ /* b->b_next is the beginning of a cold streak */
+ assert(!b->b_cold && b->b_next->b_cold);
+
+ basicblock *b_end = b->b_next;
+ while (b_end->b_next && b_end->b_next->b_cold) {
+ b_end = b_end->b_next;
+ }
+
+ /* b_end is the end of the cold streak */
+ assert(b_end && b_end->b_cold);
+ assert(b_end->b_next == NULL || !b_end->b_next->b_cold);
+
+ if (cold_blocks == NULL) {
+ cold_blocks = b->b_next;
+ }
+ else {
+ cold_blocks_tail->b_next = b->b_next;
+ }
+ cold_blocks_tail = b_end;
+ b->b_next = b_end->b_next;
+ b_end->b_next = NULL;
+ }
+ assert(b != NULL && b->b_next == NULL);
+ b->b_next = cold_blocks;
+ return 0;
+}
static void
convert_exception_handlers_to_nops(basicblock *entry) {
@@ -8009,7 +8164,7 @@ compute_localsplus_info(struct compiler *c, int nlocalsplus,
static PyCodeObject *
makecode(struct compiler *c, struct assembler *a, PyObject *constslist,
- int maxdepth, int nlocalsplus)
+ int maxdepth, int nlocalsplus, int code_flags)
{
PyCodeObject *co = NULL;
PyObject *names = NULL;
@@ -8025,11 +8180,6 @@ makecode(struct compiler *c, struct assembler *a, PyObject *constslist,
goto error;
}
- int flags = compute_code_flags(c);
- if (flags < 0) {
- goto error;
- }
-
consts = PyList_AsTuple(constslist); /* PyCode_New requires a tuple */
if (consts == NULL) {
goto error;
@@ -8060,7 +8210,7 @@ makecode(struct compiler *c, struct assembler *a, PyObject *constslist,
.filename = c->c_filename,
.name = c->u->u_name,
.qualname = c->u->u_qualname ? c->u->u_qualname : c->u->u_name,
- .flags = flags,
+ .flags = code_flags,
.code = a->a_bytecode,
.firstlineno = c->u->u_firstlineno,
@@ -8118,6 +8268,12 @@ dump_instr(struct instr *i)
if (HAS_ARG(i->i_opcode)) {
sprintf(arg, "arg: %d ", i->i_oparg);
}
+ if (is_jump(i)) {
+ sprintf(arg, "target: %p ", i->i_target);
+ }
+ if (is_block_push(i)) {
+ sprintf(arg, "except_target: %p ", i->i_target);
+ }
fprintf(stderr, "line: %d, opcode: %d %s%s%s\n",
i->i_lineno, i->i_opcode, arg, jabs, jrel);
}
@@ -8126,8 +8282,8 @@ static void
dump_basicblock(const basicblock *b)
{
const char *b_return = b->b_return ? "return " : "";
- fprintf(stderr, "used: %d, depth: %d, offset: %d %s\n",
- b->b_iused, b->b_startdepth, b->b_offset, b_return);
+ fprintf(stderr, "[%d %d %d %p] used: %d, depth: %d, offset: %d %s\n",
+ b->b_cold, b->b_warm, b->b_nofallthrough, b, b->b_iused, b->b_startdepth, b->b_offset, b_return);
if (b->b_instr) {
int i;
for (i = 0; i < b->b_iused; i++) {
@@ -8202,17 +8358,13 @@ insert_instruction(basicblock *block, int pos, struct instr *instr) {
static int
insert_prefix_instructions(struct compiler *c, basicblock *entryblock,
- int *fixed, int nfreevars)
+ int *fixed, int nfreevars, int code_flags)
{
- int flags = compute_code_flags(c);
- if (flags < 0) {
- return -1;
- }
assert(c->u->u_firstlineno > 0);
/* Add the generator prefix instructions. */
- if (flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
+ if (code_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
struct instr make_gen = {
.i_opcode = RETURN_GENERATOR,
.i_oparg = 0,
@@ -8373,6 +8525,38 @@ fix_cell_offsets(struct compiler *c, basicblock *entryblock, int *fixedmap)
static void
propagate_line_numbers(struct assembler *a);
+static void
+eliminate_empty_basic_blocks(basicblock *entry);
+
+
+static void
+remove_redundant_jumps(basicblock *entry) {
+ /* If a non-empty block ends with a jump instruction, check if the next
+ * non-empty block reached through normal flow control is the target
+ * of that jump. If it is, then the jump instruction is redundant and
+ * can be deleted.
+ */
+ int removed = 0;
+ for (basicblock *b = entry; b != NULL; b = b->b_next) {
+ if (b->b_iused > 0) {
+ struct instr *b_last_instr = &b->b_instr[b->b_iused - 1];
+ assert(!IS_ASSEMBLER_OPCODE(b_last_instr->i_opcode));
+ if (b_last_instr->i_opcode == JUMP ||
+ b_last_instr->i_opcode == JUMP_NO_INTERRUPT) {
+ if (b_last_instr->i_target == b->b_next) {
+ assert(b->b_next->b_iused);
+ b->b_nofallthrough = 0;
+ b_last_instr->i_opcode = NOP;
+ removed++;
+ }
+ }
+ }
+ }
+ if (removed) {
+ eliminate_empty_basic_blocks(entry);
+ }
+}
+
static PyCodeObject *
assemble(struct compiler *c, int addNone)
{
@@ -8382,6 +8566,11 @@ assemble(struct compiler *c, int addNone)
PyCodeObject *co = NULL;
PyObject *consts = NULL;
+ int code_flags = compute_code_flags(c);
+ if (code_flags < 0) {
+ return NULL;
+ }
+
/* Make sure every block that falls off the end returns None. */
if (!c->u->u_curblock->b_return) {
UNSET_LOC(c);
@@ -8407,6 +8596,7 @@ assemble(struct compiler *c, int addNone)
for (b = c->u->u_blocks; b != NULL; b = b->b_list) {
nblocks++;
entryblock = b;
+ assert(b->b_warm == 0 && b->b_cold == 0);
}
assert(entryblock != NULL);
@@ -8435,7 +8625,7 @@ assemble(struct compiler *c, int addNone)
}
// This must be called before fix_cell_offsets().
- if (insert_prefix_instructions(c, entryblock, cellfixedoffsets, nfreevars)) {
+ if (insert_prefix_instructions(c, entryblock, cellfixedoffsets, nfreevars, code_flags)) {
goto error;
}
@@ -8482,7 +8672,14 @@ assemble(struct compiler *c, int addNone)
goto error;
}
convert_exception_handlers_to_nops(entryblock);
- for (basicblock *b = a.a_entry; b != NULL; b = b->b_next) {
+
+ if (push_cold_blocks_to_end(c, entryblock, code_flags) < 0) {
+ goto error;
+ }
+
+ remove_redundant_jumps(entryblock);
+ assert(a.a_entry == entryblock);
+ for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
clean_basic_block(b);
}
@@ -8535,7 +8732,7 @@ assemble(struct compiler *c, int addNone)
goto error;
}
- co = makecode(c, &a, consts, maxdepth, nlocalsplus);
+ co = makecode(c, &a, consts, maxdepth, nlocalsplus, code_flags);
error:
Py_XDECREF(consts);
assemble_free(&a);
@@ -9206,6 +9403,11 @@ mark_reachable(struct assembler *a) {
*sp++ = target;
}
target->b_predecessors++;
+ if (is_block_push(instr)) {
+ target->b_except_predecessors++;
+ }
+ assert(target->b_except_predecessors == 0 ||
+ target->b_except_predecessors == target->b_predecessors);
}
}
}
@@ -9328,30 +9530,6 @@ optimize_cfg(struct compiler *c, struct assembler *a, PyObject *consts)
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
clean_basic_block(b);
}
- /* Delete jump instructions made redundant by previous step. If a non-empty
- block ends with a jump instruction, check if the next non-empty block
- reached through normal flow control is the target of that jump. If it
- is, then the jump instruction is redundant and can be deleted.
- */
- int maybe_empty_blocks = 0;
- for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
- if (b->b_iused > 0) {
- struct instr *b_last_instr = &b->b_instr[b->b_iused - 1];
- assert(!IS_ASSEMBLER_OPCODE(b_last_instr->i_opcode));
- if (b_last_instr->i_opcode == JUMP ||
- b_last_instr->i_opcode == JUMP_NO_INTERRUPT) {
- if (b_last_instr->i_target == b->b_next) {
- assert(b->b_next->b_iused);
- b->b_nofallthrough = 0;
- b_last_instr->i_opcode = NOP;
- maybe_empty_blocks = 1;
- }
- }
- }
- }
- if (maybe_empty_blocks) {
- eliminate_empty_basic_blocks(a->a_entry);
- }
return 0;
}