Skip to content

Commit cc75ab7

Browse files
authored
bpo-42246: Eliminate jumps to exit blocks by copying those blocks. (#23251)
* Compiler: eliminate jumps to short exit blocks by copying.
1 parent 750c5ab commit cc75ab7

File tree

6 files changed

+4653
-4655
lines changed

6 files changed

+4653
-4655
lines changed

Lib/test/test_dis.py

Lines changed: 113 additions & 116 deletions
Large diffs are not rendered by default.

Lib/test/test_sys_settrace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ def test_no_jump_over_return_try_finally_in_finally_block(output):
990990
pass
991991
output.append(12)
992992

993-
@jump_test(3, 4, [1], (ValueError, 'unreachable'))
993+
@jump_test(3, 4, [1], (ValueError, 'after'))
994994
def test_no_jump_infinite_while_loop(output):
995995
output.append(1)
996996
while True:

Python/compile.c

Lines changed: 85 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ typedef struct basicblock_ {
9696
/* b_return is true if a RETURN_VALUE opcode is inserted. */
9797
unsigned b_return : 1;
9898
unsigned b_reachable : 1;
99+
/* Basic block has no fall through (it ends with a return, raise or jump) */
100+
unsigned b_nofallthrough : 1;
101+
/* Basic block exits scope (it ends with a return or raise) */
102+
unsigned b_exit : 1;
99103
/* depth of stack upon entry of block, computed by stackdepth() */
100104
int b_startdepth;
101105
/* instruction offset for block, computed by assemble_jump_offsets() */
@@ -5434,28 +5438,14 @@ struct assembler {
54345438
PyObject *a_bytecode; /* string containing bytecode */
54355439
int a_offset; /* offset into bytecode */
54365440
int a_nblocks; /* number of reachable blocks */
5437-
basicblock **a_reverse_postorder; /* list of blocks in dfs postorder */
54385441
PyObject *a_lnotab; /* string containing lnotab */
54395442
int a_lnotab_off; /* offset into lnotab */
54405443
int a_prevlineno; /* lineno of last emitted line in line table */
54415444
int a_lineno; /* lineno of last emitted instruction */
54425445
int a_lineno_start; /* bytecode start offset of current lineno */
5446+
basicblock *a_entry;
54435447
};
54445448

5445-
static void
5446-
dfs(struct compiler *c, basicblock *b, struct assembler *a, int end)
5447-
{
5448-
5449-
/* There is no real depth-first-search to do here because all the
5450-
* blocks are emitted in topological order already, so we just need to
5451-
* follow the b_next pointers and place them in a->a_reverse_postorder in
5452-
* reverse order and make sure that the first one starts at 0. */
5453-
5454-
for (a->a_nblocks = 0; b != NULL; b = b->b_next) {
5455-
a->a_reverse_postorder[a->a_nblocks++] = b;
5456-
}
5457-
}
5458-
54595449
Py_LOCAL_INLINE(void)
54605450
stackdepth_push(basicblock ***sp, basicblock *b, int depth)
54615451
{
@@ -5553,12 +5543,7 @@ assemble_init(struct assembler *a, int nblocks, int firstlineno)
55535543
PyErr_NoMemory();
55545544
return 0;
55555545
}
5556-
a->a_reverse_postorder = (basicblock **)PyObject_Malloc(
5557-
sizeof(basicblock *) * nblocks);
5558-
if (!a->a_reverse_postorder) {
5559-
PyErr_NoMemory();
5560-
return 0;
5561-
}
5546+
55625547
return 1;
55635548
}
55645549

@@ -5567,8 +5552,6 @@ assemble_free(struct assembler *a)
55675552
{
55685553
Py_XDECREF(a->a_bytecode);
55695554
Py_XDECREF(a->a_lnotab);
5570-
if (a->a_reverse_postorder)
5571-
PyObject_Free(a->a_reverse_postorder);
55725555
}
55735556

55745557
static int
@@ -5697,8 +5680,7 @@ assemble_jump_offsets(struct assembler *a, struct compiler *c)
56975680
Replace block pointer with position in bytecode. */
56985681
do {
56995682
totsize = 0;
5700-
for (i = 0; i < a->a_nblocks; i++) {
5701-
b = a->a_reverse_postorder[i];
5683+
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
57025684
bsize = blocksize(b);
57035685
b->b_offset = totsize;
57045686
totsize += bsize;
@@ -5966,7 +5948,7 @@ assemble(struct compiler *c, int addNone)
59665948
{
59675949
basicblock *b, *entryblock;
59685950
struct assembler a;
5969-
int i, j, nblocks;
5951+
int j, nblocks;
59705952
PyCodeObject *co = NULL;
59715953
PyObject *consts = NULL;
59725954

@@ -5997,7 +5979,8 @@ assemble(struct compiler *c, int addNone)
59975979
}
59985980
if (!assemble_init(&a, nblocks, c->u->u_firstlineno))
59995981
goto error;
6000-
dfs(c, entryblock, &a, nblocks);
5982+
a.a_entry = entryblock;
5983+
a.a_nblocks = nblocks;
60015984

60025985
consts = consts_dict_keys_inorder(c->u->u_consts);
60035986
if (consts == NULL) {
@@ -6010,9 +5993,8 @@ assemble(struct compiler *c, int addNone)
60105993
/* Can't modify the bytecode after computing jump offsets. */
60115994
assemble_jump_offsets(&a, c);
60125995

6013-
/* Emit code in reverse postorder from dfs. */
6014-
for (i = 0; i < a.a_nblocks; i++) {
6015-
b = a.a_reverse_postorder[i];
5996+
/* Emit code. */
5997+
for(b = entryblock; b != NULL; b = b->b_next) {
60165998
for (j = 0; j < b->b_iused; j++)
60175999
if (!assemble_emit(&a, &b->b_instr[j]))
60186000
goto error;
@@ -6097,6 +6079,8 @@ fold_tuple_on_constants(struct instr *inst,
60976079
return 0;
60986080
}
60996081

6082+
/* Maximum size of basic block that should be copied in optimizer */
6083+
#define MAX_COPY_SIZE 4
61006084

61016085
/* Optimization */
61026086
static int
@@ -6238,19 +6222,29 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
62386222

62396223
case JUMP_ABSOLUTE:
62406224
case JUMP_FORWARD:
6225+
assert (i == bb->b_iused-1);
62416226
switch(target->i_opcode) {
62426227
case JUMP_FORWARD:
62436228
inst->i_target = target->i_target;
62446229
break;
62456230
case JUMP_ABSOLUTE:
6246-
case RETURN_VALUE:
6247-
case RERAISE:
6248-
case RAISE_VARARGS:
62496231
lineno = inst->i_lineno;
62506232
*inst = *target;
62516233
inst->i_lineno = lineno;
62526234
break;
62536235
}
6236+
if (inst->i_target->b_exit && inst->i_target->b_iused <= MAX_COPY_SIZE) {
6237+
basicblock *to_copy = inst->i_target;
6238+
*inst = to_copy->b_instr[0];
6239+
for (i = 1; i < to_copy->b_iused; i++) {
6240+
int index = compiler_next_instr(bb);
6241+
if (index < 0) {
6242+
return -1;
6243+
}
6244+
bb->b_instr[index] = to_copy->b_instr[i];
6245+
}
6246+
bb->b_exit = 1;
6247+
}
62546248
break;
62556249
}
62566250
}
@@ -6262,65 +6256,75 @@ optimize_basic_block(basicblock *bb, PyObject *consts)
62626256

62636257
static void
62646258
clean_basic_block(basicblock *bb) {
6265-
/* Remove NOPs and any code following a return or re-raise. */
6259+
/* Remove NOPs. */
62666260
int dest = 0;
62676261
int prev_lineno = -1;
62686262
for (int src = 0; src < bb->b_iused; src++) {
62696263
int lineno = bb->b_instr[src].i_lineno;
6270-
switch(bb->b_instr[src].i_opcode) {
6271-
case RETURN_VALUE:
6272-
case RERAISE:
6273-
bb->b_next = NULL;
6274-
bb->b_instr[dest] = bb->b_instr[src];
6275-
dest++;
6276-
goto end;
6277-
case NOP:
6278-
{
6279-
/* Eliminate no-op if it doesn't have a line number, or
6280-
* if the next instruction has same line number or no line number, or
6281-
* if the previous instruction had the same line number. */
6282-
if (lineno < 0) {
6283-
break;
6284-
}
6285-
if (prev_lineno == lineno) {
6286-
break;
6287-
}
6288-
if (src < bb->b_iused - 1) {
6289-
int next_lineno = bb->b_instr[src+1].i_lineno;
6290-
if (next_lineno < 0 || next_lineno == lineno) {
6291-
bb->b_instr[src+1].i_lineno = lineno;
6292-
break;
6293-
}
6294-
}
6264+
if (bb->b_instr[src].i_opcode == NOP) {
6265+
/* Eliminate no-op if it doesn't have a line number, or
6266+
* if the next instruction has same line number or no line number, or
6267+
* if the previous instruction had the same line number. */
6268+
if (lineno < 0) {
6269+
continue;
62956270
}
6296-
/* fallthrough */
6297-
default:
6298-
if (dest != src) {
6299-
bb->b_instr[dest] = bb->b_instr[src];
6271+
if (prev_lineno == lineno) {
6272+
continue;
6273+
}
6274+
if (src < bb->b_iused - 1) {
6275+
int next_lineno = bb->b_instr[src+1].i_lineno;
6276+
if (next_lineno < 0 || next_lineno == lineno) {
6277+
bb->b_instr[src+1].i_lineno = lineno;
6278+
continue;
63006279
}
6301-
dest++;
6302-
prev_lineno = lineno;
6303-
break;
6280+
}
6281+
}
6282+
if (dest != src) {
6283+
bb->b_instr[dest] = bb->b_instr[src];
63046284
}
6285+
dest++;
6286+
prev_lineno = lineno;
63056287
}
6306-
end:
63076288
assert(dest <= bb->b_iused);
63086289
bb->b_iused = dest;
63096290
}
63106291

6292+
static void
6293+
normalise_basic_block(basicblock *bb) {
6294+
/* Remove any code following a return or re-raise,
6295+
and mark those blocks as exit and/or nofallthrough. */
6296+
for (int i = 0; i < bb->b_iused; i++) {
6297+
switch(bb->b_instr[i].i_opcode) {
6298+
case RETURN_VALUE:
6299+
case RAISE_VARARGS:
6300+
case RERAISE:
6301+
bb->b_iused = i+1;
6302+
bb->b_exit = 1;
6303+
bb->b_nofallthrough = 1;
6304+
return;
6305+
case JUMP_ABSOLUTE:
6306+
case JUMP_FORWARD:
6307+
bb->b_iused = i+1;
6308+
bb->b_nofallthrough = 1;
6309+
return;
6310+
}
6311+
}
6312+
}
6313+
6314+
6315+
63116316
static int
63126317
mark_reachable(struct assembler *a) {
63136318
basicblock **stack, **sp;
63146319
sp = stack = (basicblock **)PyObject_Malloc(sizeof(basicblock *) * a->a_nblocks);
63156320
if (stack == NULL) {
63166321
return -1;
63176322
}
6318-
basicblock *entry = a->a_reverse_postorder[0];
6319-
entry->b_reachable = 1;
6320-
*sp++ = entry;
6323+
a->a_entry->b_reachable = 1;
6324+
*sp++ = a->a_entry;
63216325
while (sp > stack) {
63226326
basicblock *b = *(--sp);
6323-
if (b->b_next && b->b_next->b_reachable == 0) {
6327+
if (b->b_next && !b->b_nofallthrough && b->b_next->b_reachable == 0) {
63246328
b->b_next->b_reachable = 1;
63256329
*sp++ = b->b_next;
63266330
}
@@ -6352,20 +6356,23 @@ mark_reachable(struct assembler *a) {
63526356
static int
63536357
optimize_cfg(struct assembler *a, PyObject *consts)
63546358
{
6355-
for (int i = 0; i < a->a_nblocks; i++) {
6356-
if (optimize_basic_block(a->a_reverse_postorder[i], consts)) {
6359+
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
6360+
normalise_basic_block(b);
6361+
}
6362+
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
6363+
if (optimize_basic_block(b, consts)) {
63576364
return -1;
63586365
}
6359-
clean_basic_block(a->a_reverse_postorder[i]);
6360-
assert(a->a_reverse_postorder[i]->b_reachable == 0);
6366+
clean_basic_block(b);
6367+
assert(b->b_reachable == 0);
63616368
}
63626369
if (mark_reachable(a)) {
63636370
return -1;
63646371
}
63656372
/* Delete unreachable instructions */
6366-
for (int i = 0; i < a->a_nblocks; i++) {
6367-
if (a->a_reverse_postorder[i]->b_reachable == 0) {
6368-
a->a_reverse_postorder[i]->b_iused = 0;
6373+
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
6374+
if (b->b_reachable == 0) {
6375+
b->b_iused = 0;
63696376
}
63706377
}
63716378
return 0;

0 commit comments

Comments
 (0)