Skip to content

Commit c71581c

Browse files
authored
bpo-42615: Delete redundant jump instructions that only bypass empty blocks (GH-23733)
* Delete jump instructions that bypass empty blocks * Add news entry * Explicitly check for unconditional jump opcodes Using the is_jump function results in the inclusion of instructions like returns for which this optimization is not really valid. So, instead explicitly check that the instruction is an unconditional jump. * Handle conditional jumps, delete jumps gracefully * Ensure b_nofallthrough and b_reachable are valid * Add test for redundant jumps * Regenerate importlib.h and edit Misc/ACKS * Fix bad whitespace
1 parent 8203c73 commit c71581c

File tree

5 files changed

+174
-101
lines changed

5 files changed

+174
-101
lines changed

Lib/test/test_compile.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,34 @@ def test_big_dict_literal(self):
837837
the_dict = "{" + ",".join(f"{x}:{x}" for x in range(dict_size)) + "}"
838838
self.assertEqual(len(eval(the_dict)), dict_size)
839839

840+
def test_redundant_jump_in_if_else_break(self):
841+
# Check if bytecode containing jumps that simply point to the next line
842+
# is generated around if-else-break style structures. See bpo-42615.
843+
844+
def if_else_break():
845+
val = 1
846+
while True:
847+
if val > 0:
848+
val -= 1
849+
else:
850+
break
851+
val = -1
852+
853+
INSTR_SIZE = 2
854+
HANDLED_JUMPS = (
855+
'POP_JUMP_IF_FALSE',
856+
'POP_JUMP_IF_TRUE',
857+
'JUMP_ABSOLUTE',
858+
'JUMP_FORWARD',
859+
)
860+
861+
for line, instr in enumerate(dis.Bytecode(if_else_break)):
862+
if instr.opname == 'JUMP_FORWARD':
863+
self.assertNotEqual(instr.arg, 0)
864+
elif instr.opname in HANDLED_JUMPS:
865+
self.assertNotEqual(instr.arg, (line + 1)*INSTR_SIZE)
866+
867+
840868
class TestExpressionStackSize(unittest.TestCase):
841869
# These tests check that the computed stack size for a code object
842870
# stays within reasonable bounds (see issue #21523 for an example

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@ Grzegorz Grzywacz
643643
Thomas Guettler
644644
Yuyang Guo
645645
Anuj Gupta
646+
Om Gupta
646647
Michael Guravage
647648
Lars Gustäbel
648649
Thomas Güttler
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove jump commands made redundant by the deletion of unreachable bytecode
2+
blocks

Python/compile.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6437,8 +6437,50 @@ optimize_cfg(struct assembler *a, PyObject *consts)
64376437
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
64386438
if (b->b_reachable == 0) {
64396439
b->b_iused = 0;
6440+
b->b_nofallthrough = 0;
64406441
}
64416442
}
6443+
/* Delete jump instructions made redundant by previous step. If a non-empty
6444+
block ends with a jump instruction, check if the next non-empty block
6445+
reached through normal flow control is the target of that jump. If it
6446+
is, then the jump instruction is redundant and can be deleted.
6447+
*/
6448+
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
6449+
if (b->b_iused > 0) {
6450+
struct instr *b_last_instr = &b->b_instr[b->b_iused - 1];
6451+
if (b_last_instr->i_opcode == POP_JUMP_IF_FALSE ||
6452+
b_last_instr->i_opcode == POP_JUMP_IF_TRUE ||
6453+
b_last_instr->i_opcode == JUMP_ABSOLUTE ||
6454+
b_last_instr->i_opcode == JUMP_FORWARD) {
6455+
basicblock *b_next_act = b->b_next;
6456+
while (b_next_act != NULL && b_next_act->b_iused == 0) {
6457+
b_next_act = b_next_act->b_next;
6458+
}
6459+
if (b_last_instr->i_target == b_next_act) {
6460+
b->b_nofallthrough = 0;
6461+
switch(b_last_instr->i_opcode) {
6462+
case POP_JUMP_IF_FALSE:
6463+
case POP_JUMP_IF_TRUE:
6464+
b_last_instr->i_opcode = POP_TOP;
6465+
b_last_instr->i_target = NULL;
6466+
b_last_instr->i_oparg = 0;
6467+
break;
6468+
case JUMP_ABSOLUTE:
6469+
case JUMP_FORWARD:
6470+
b_last_instr->i_opcode = NOP;
6471+
clean_basic_block(b);
6472+
break;
6473+
}
6474+
/* The blocks after this one are now reachable through it */
6475+
b_next_act = b->b_next;
6476+
while (b_next_act != NULL && b_next_act->b_iused == 0) {
6477+
b_next_act->b_reachable = 1;
6478+
b_next_act = b_next_act->b_next;
6479+
}
6480+
}
6481+
}
6482+
}
6483+
}
64426484
minimize_lineno_table(a);
64436485
return 0;
64446486
}

Python/importlib.h

Lines changed: 101 additions & 101 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)