Skip to content

Commit 3bd6035

Browse files
authored
bpo-42908: Mark cleanup code at end of try-except and with artificial (#24202)
* Mark bytecodes at end of try-except as artificial. * Make sure that the CFG is consistent throughout optimiization. * Extend line-number propagation logic so that implicit returns after 'try-except' or 'with' have the correct line numbers. * Update importlib
1 parent 2396614 commit 3bd6035

File tree

6 files changed

+4473
-4359
lines changed

6 files changed

+4473
-4359
lines changed

Lib/test/test_dis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ def jumpy():
10261026
Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=42, argval=42, argrepr='', offset=36, starts_line=None, is_jump_target=False),
10271027
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=38, starts_line=8, is_jump_target=False),
10281028
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=52, argval=52, argrepr='', offset=40, starts_line=None, is_jump_target=False),
1029-
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=8, argval=8, argrepr='', offset=42, starts_line=None, is_jump_target=True),
1029+
Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=8, argval=8, argrepr='', offset=42, starts_line=7, is_jump_target=True),
10301030
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=44, starts_line=10, is_jump_target=True),
10311031
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=46, starts_line=None, is_jump_target=False),
10321032
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=48, starts_line=None, is_jump_target=False),

Lib/test/test_sys_settrace.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,46 @@ def func():
916916
(7, 'line'),
917917
(7, 'return')])
918918

919+
def test_if_false_in_with(self):
920+
921+
class C:
922+
def __enter__(self):
923+
return self
924+
def __exit__(*args):
925+
pass
926+
927+
def func():
928+
with C():
929+
if False:
930+
pass
931+
932+
self.run_and_compare(func,
933+
[(0, 'call'),
934+
(1, 'line'),
935+
(-5, 'call'),
936+
(-4, 'line'),
937+
(-4, 'return'),
938+
(2, 'line'),
939+
(-3, 'call'),
940+
(-2, 'line'),
941+
(-2, 'return'),
942+
(2, 'return')])
943+
944+
def test_if_false_in_try_except(self):
945+
946+
def func():
947+
try:
948+
if False:
949+
pass
950+
except Exception:
951+
X
952+
953+
self.run_and_compare(func,
954+
[(0, 'call'),
955+
(1, 'line'),
956+
(2, 'line'),
957+
(2, 'return')])
958+
919959

920960
class SkipLineEventsTraceTestCase(TraceTestCase):
921961
"""Repeat the trace tests, but with per-line events skipped"""

Python/compile.c

Lines changed: 104 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ typedef struct basicblock_ {
9595
struct basicblock_ *b_next;
9696
/* b_return is true if a RETURN_VALUE opcode is inserted. */
9797
unsigned b_return : 1;
98-
unsigned b_reachable : 1;
98+
/* Number of predecssors that a block has. */
99+
int b_predecessors;
99100
/* Basic block has no fall through (it ends with a return, raise or jump) */
100101
unsigned b_nofallthrough : 1;
101102
/* Basic block exits scope (it ends with a return or raise) */
@@ -825,6 +826,7 @@ compiler_copy_block(struct compiler *c, basicblock *block)
825826
result->b_instr[n] = block->b_instr[i];
826827
}
827828
result->b_exit = block->b_exit;
829+
result->b_nofallthrough = 1;
828830
return result;
829831
}
830832

@@ -1169,7 +1171,7 @@ PyCompile_OpcodeStackEffect(int opcode, int oparg)
11691171
*/
11701172

11711173
static int
1172-
compiler_addop(struct compiler *c, int opcode)
1174+
compiler_addop_line(struct compiler *c, int opcode, int line)
11731175
{
11741176
basicblock *b;
11751177
struct instr *i;
@@ -1184,10 +1186,23 @@ compiler_addop(struct compiler *c, int opcode)
11841186
i->i_oparg = 0;
11851187
if (opcode == RETURN_VALUE)
11861188
b->b_return = 1;
1187-
i->i_lineno = c->u->u_lineno;
1189+
i->i_lineno = line;
11881190
return 1;
11891191
}
11901192

1193+
static int
1194+
compiler_addop(struct compiler *c, int opcode)
1195+
{
1196+
return compiler_addop_line(c, opcode, c->u->u_lineno);
1197+
}
1198+
1199+
static int
1200+
compiler_addop_noline(struct compiler *c, int opcode)
1201+
{
1202+
return compiler_addop_line(c, opcode, -1);
1203+
}
1204+
1205+
11911206
static Py_ssize_t
11921207
compiler_add_o(PyObject *dict, PyObject *o)
11931208
{
@@ -1448,6 +1463,11 @@ compiler_addop_j_noline(struct compiler *c, int opcode, basicblock *b)
14481463
return 0; \
14491464
}
14501465

1466+
#define ADDOP_NOLINE(C, OP) { \
1467+
if (!compiler_addop_noline((C), (OP))) \
1468+
return 0; \
1469+
}
1470+
14511471
#define ADDOP_IN_SCOPE(C, OP) { \
14521472
if (!compiler_addop((C), (OP))) { \
14531473
compiler_exit_scope(c); \
@@ -2955,9 +2975,7 @@ compiler_try_finally(struct compiler *c, stmt_ty s)
29552975
else {
29562976
VISIT_SEQ(c, stmt, s->v.Try.body);
29572977
}
2958-
/* Mark code as artificial */
2959-
c->u->u_lineno = -1;
2960-
ADDOP(c, POP_BLOCK);
2978+
ADDOP_NOLINE(c, POP_BLOCK);
29612979
compiler_pop_fblock(c, FINALLY_TRY, body);
29622980
VISIT_SEQ(c, stmt, s->v.Try.finalbody);
29632981
ADDOP_JUMP_NOLINE(c, JUMP_FORWARD, exit);
@@ -3019,9 +3037,9 @@ compiler_try_except(struct compiler *c, stmt_ty s)
30193037
if (!compiler_push_fblock(c, TRY_EXCEPT, body, NULL, NULL))
30203038
return 0;
30213039
VISIT_SEQ(c, stmt, s->v.Try.body);
3022-
ADDOP(c, POP_BLOCK);
30233040
compiler_pop_fblock(c, TRY_EXCEPT, body);
3024-
ADDOP_JUMP(c, JUMP_FORWARD, orelse);
3041+
ADDOP_NOLINE(c, POP_BLOCK);
3042+
ADDOP_JUMP_NOLINE(c, JUMP_FORWARD, orelse);
30253043
n = asdl_seq_LEN(s->v.Try.handlers);
30263044
compiler_use_next_block(c, except);
30273045
/* Runtime will push a block here, so we need to account for that */
@@ -4925,6 +4943,9 @@ compiler_with(struct compiler *c, stmt_ty s, int pos)
49254943
else if (!compiler_with(c, s, pos))
49264944
return 0;
49274945

4946+
4947+
/* Mark all following code as artificial */
4948+
c->u->u_lineno = -1;
49284949
ADDOP(c, POP_BLOCK);
49294950
compiler_pop_fblock(c, WITH, block);
49304951

@@ -6396,36 +6417,71 @@ mark_reachable(struct assembler *a) {
63966417
if (stack == NULL) {
63976418
return -1;
63986419
}
6399-
a->a_entry->b_reachable = 1;
6420+
a->a_entry->b_predecessors = 1;
64006421
*sp++ = a->a_entry;
64016422
while (sp > stack) {
64026423
basicblock *b = *(--sp);
6403-
if (b->b_next && !b->b_nofallthrough && b->b_next->b_reachable == 0) {
6404-
b->b_next->b_reachable = 1;
6405-
*sp++ = b->b_next;
6424+
if (b->b_next && !b->b_nofallthrough) {
6425+
if (b->b_next->b_predecessors == 0) {
6426+
*sp++ = b->b_next;
6427+
}
6428+
b->b_next->b_predecessors++;
64066429
}
64076430
for (int i = 0; i < b->b_iused; i++) {
64086431
basicblock *target;
64096432
if (is_jump(&b->b_instr[i])) {
64106433
target = b->b_instr[i].i_target;
6411-
if (target->b_reachable == 0) {
6412-
target->b_reachable = 1;
6434+
if (target->b_predecessors == 0) {
64136435
*sp++ = target;
64146436
}
6437+
target->b_predecessors++;
64156438
}
64166439
}
64176440
}
64186441
PyObject_Free(stack);
64196442
return 0;
64206443
}
64216444

6445+
static void
6446+
eliminate_empty_basic_blocks(basicblock *entry) {
6447+
/* Eliminate empty blocks */
6448+
for (basicblock *b = entry; b != NULL; b = b->b_next) {
6449+
basicblock *next = b->b_next;
6450+
if (next) {
6451+
while (next->b_iused == 0 && next->b_next) {
6452+
next = next->b_next;
6453+
}
6454+
b->b_next = next;
6455+
}
6456+
}
6457+
for (basicblock *b = entry; b != NULL; b = b->b_next) {
6458+
if (b->b_iused == 0) {
6459+
continue;
6460+
}
6461+
if (is_jump(&b->b_instr[b->b_iused-1])) {
6462+
basicblock *target = b->b_instr[b->b_iused-1].i_target;
6463+
while (target->b_iused == 0) {
6464+
target = target->b_next;
6465+
}
6466+
b->b_instr[b->b_iused-1].i_target = target;
6467+
}
6468+
}
6469+
}
6470+
6471+
64226472
/* If an instruction has no line number, but it's predecessor in the BB does,
6423-
* then copy the line number. This reduces the size of the line number table,
6473+
* then copy the line number. If a successor block has no line number, and only
6474+
* one predecessor, then inherit the line number.
6475+
* This ensures that all exit blocks (with one predecessor) receive a line number.
6476+
* Also reduces the size of the line number table,
64246477
* but has no impact on the generated line number events.
64256478
*/
64266479
static void
6427-
minimize_lineno_table(struct assembler *a) {
6480+
propogate_line_numbers(struct assembler *a) {
64286481
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
6482+
if (b->b_iused == 0) {
6483+
continue;
6484+
}
64296485
int prev_lineno = -1;
64306486
for (int i = 0; i < b->b_iused; i++) {
64316487
if (b->b_instr[i].i_lineno < 0) {
@@ -6435,7 +6491,27 @@ minimize_lineno_table(struct assembler *a) {
64356491
prev_lineno = b->b_instr[i].i_lineno;
64366492
}
64376493
}
6438-
6494+
if (!b->b_nofallthrough && b->b_next->b_predecessors == 1) {
6495+
assert(b->b_next->b_iused);
6496+
if (b->b_next->b_instr[0].i_lineno < 0) {
6497+
b->b_next->b_instr[0].i_lineno = prev_lineno;
6498+
}
6499+
}
6500+
if (is_jump(&b->b_instr[b->b_iused-1])) {
6501+
switch (b->b_instr[b->b_iused-1].i_opcode) {
6502+
/* Note: Only actual jumps, not exception handlers */
6503+
case SETUP_ASYNC_WITH:
6504+
case SETUP_WITH:
6505+
case SETUP_FINALLY:
6506+
continue;
6507+
}
6508+
basicblock *target = b->b_instr[b->b_iused-1].i_target;
6509+
if (target->b_predecessors == 1) {
6510+
if (target->b_instr[0].i_lineno < 0) {
6511+
target->b_instr[0].i_lineno = prev_lineno;
6512+
}
6513+
}
6514+
}
64396515
}
64406516
}
64416517

@@ -6456,35 +6532,34 @@ optimize_cfg(struct assembler *a, PyObject *consts)
64566532
return -1;
64576533
}
64586534
clean_basic_block(b);
6459-
assert(b->b_reachable == 0);
6535+
assert(b->b_predecessors == 0);
64606536
}
64616537
if (mark_reachable(a)) {
64626538
return -1;
64636539
}
64646540
/* Delete unreachable instructions */
64656541
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
6466-
if (b->b_reachable == 0) {
6542+
if (b->b_predecessors == 0) {
64676543
b->b_iused = 0;
64686544
b->b_nofallthrough = 0;
64696545
}
64706546
}
6547+
eliminate_empty_basic_blocks(a->a_entry);
64716548
/* Delete jump instructions made redundant by previous step. If a non-empty
64726549
block ends with a jump instruction, check if the next non-empty block
64736550
reached through normal flow control is the target of that jump. If it
64746551
is, then the jump instruction is redundant and can be deleted.
64756552
*/
6553+
int maybe_empty_blocks = 0;
64766554
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
64776555
if (b->b_iused > 0) {
64786556
struct instr *b_last_instr = &b->b_instr[b->b_iused - 1];
64796557
if (b_last_instr->i_opcode == POP_JUMP_IF_FALSE ||
64806558
b_last_instr->i_opcode == POP_JUMP_IF_TRUE ||
64816559
b_last_instr->i_opcode == JUMP_ABSOLUTE ||
64826560
b_last_instr->i_opcode == JUMP_FORWARD) {
6483-
basicblock *b_next_act = b->b_next;
6484-
while (b_next_act != NULL && b_next_act->b_iused == 0) {
6485-
b_next_act = b_next_act->b_next;
6486-
}
6487-
if (b_last_instr->i_target == b_next_act) {
6561+
if (b_last_instr->i_target == b->b_next) {
6562+
assert(b->b_next->b_iused);
64886563
b->b_nofallthrough = 0;
64896564
switch(b_last_instr->i_opcode) {
64906565
case POP_JUMP_IF_FALSE:
@@ -6497,19 +6572,17 @@ optimize_cfg(struct assembler *a, PyObject *consts)
64976572
case JUMP_FORWARD:
64986573
b_last_instr->i_opcode = NOP;
64996574
clean_basic_block(b);
6575+
maybe_empty_blocks = 1;
65006576
break;
65016577
}
6502-
/* The blocks after this one are now reachable through it */
6503-
b_next_act = b->b_next;
6504-
while (b_next_act != NULL && b_next_act->b_iused == 0) {
6505-
b_next_act->b_reachable = 1;
6506-
b_next_act = b_next_act->b_next;
6507-
}
65086578
}
65096579
}
65106580
}
65116581
}
6512-
minimize_lineno_table(a);
6582+
if (maybe_empty_blocks) {
6583+
eliminate_empty_basic_blocks(a->a_entry);
6584+
}
6585+
propogate_line_numbers(a);
65136586
return 0;
65146587
}
65156588

0 commit comments

Comments
 (0)