Skip to content

Commit d2bf2d6

Browse files
eendebakptclaude
andcommitted
Analyze exception handlers in optimize_load_fast
Once the SETUP_* pseudo-instructions have been converted, exception handler blocks are only reachable through the per-instruction exception edges. optimize_load_fast() only followed jumps and fallthroughs, so the code in exception handlers was never considered for the LOAD_FAST to LOAD_FAST_BORROW conversion. Push the exception handler blocks onto the work list as well. END_ASYNC_FOR has a jump target that is only used for monitoring, so it must not be treated as a jump, like in calculate_stackdepth(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent e80962a commit d2bf2d6

4 files changed

Lines changed: 75 additions & 7 deletions

File tree

‎Lib/test/test_dis.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,15 +474,15 @@ def foo(a: int, b: str) -> str:
474474
NOT_TAKEN
475475
STORE_FAST 0 (e)
476476
477-
%4d L4: LOAD_FAST 0 (e)
477+
%4d L4: LOAD_FAST_BORROW 0 (e)
478478
LOAD_ATTR 2 (__traceback__)
479479
STORE_FAST 1 (tb)
480480
L5: POP_EXCEPT
481481
LOAD_COMMON_CONSTANT 7 (None)
482482
STORE_FAST 0 (e)
483483
DELETE_FAST 0 (e)
484484
485-
%4d LOAD_FAST 1 (tb)
485+
%4d LOAD_FAST_BORROW 1 (tb)
486486
RETURN_VALUE
487487
488488
-- L6: LOAD_COMMON_CONSTANT 7 (None)
@@ -731,7 +731,7 @@ def _tryfinallyconst(b):
731731
732732
-- L3: PUSH_EXC_INFO
733733
734-
%4d LOAD_FAST 1 (b)
734+
%4d LOAD_FAST_BORROW 1 (b)
735735
PUSH_NULL
736736
CALL 0
737737
POP_TOP

‎Lib/test/test_monitoring.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,8 +2029,8 @@ def func():
20292029
('branch', 'func', 4, 4),
20302030
('line', 'func', 5),
20312031
('line', 'meth', 1),
2032-
('jump', 'func', 5, '[offset=122]'),
2033-
('branch', 'func', '[offset=126]', '[offset=132]'),
2032+
('jump', 'func', 5, '[offset=124]'),
2033+
('branch', 'func', '[offset=128]', '[offset=134]'),
20342034
('line', 'get_events', 11)])
20352035

20362036
self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [
@@ -2044,8 +2044,8 @@ def func():
20442044
('line', 'func', 5),
20452045
('line', 'meth', 1),
20462046
('return', 'meth', None),
2047-
('jump', 'func', 5, '[offset=122]'),
2048-
('branch', 'func', '[offset=126]', '[offset=132]'),
2047+
('jump', 'func', 5, '[offset=124]'),
2048+
('branch', 'func', '[offset=128]', '[offset=134]'),
20492049
('return', 'func', None),
20502050
('line', 'get_events', 11)])
20512051

‎Lib/test/test_peepholer.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,56 @@ def f(self):
11561156
print(heapify_max)
11571157
self.assertInBytecode(f, "LOAD_FAST_BORROW", "self")
11581158

1159+
def test_load_fast_borrow_in_exception_handlers(self):
1160+
def f(x, g, h):
1161+
try:
1162+
return g(x)
1163+
except Exception:
1164+
return h(x)
1165+
self.assertNotInBytecode(f, "LOAD_FAST")
1166+
self.assertInBytecode(f, "LOAD_FAST_BORROW", "h")
1167+
1168+
def g(x, k):
1169+
try:
1170+
k()
1171+
finally:
1172+
k(x)
1173+
self.assertNotInBytecode(g, "LOAD_FAST")
1174+
self.assertInBytecode(g, "LOAD_FAST_BORROW", "x")
1175+
1176+
def h(x, k):
1177+
try:
1178+
k()
1179+
except ValueError:
1180+
try:
1181+
k(x)
1182+
except TypeError:
1183+
k(x, x)
1184+
self.assertNotInBytecode(h, "LOAD_FAST")
1185+
1186+
async def coro(it, k):
1187+
async for v in it:
1188+
k(v)
1189+
self.assertNotInBytecode(coro, "LOAD_FAST")
1190+
1191+
def killed(x, k):
1192+
try:
1193+
k()
1194+
except ValueError:
1195+
# x is rebound while its reference is still on the stack
1196+
k(x, (x := None))
1197+
self.assertInBytecode(killed, "LOAD_FAST", "x")
1198+
self.assertNotInBytecode(killed, "LOAD_FAST_BORROW", "x")
1199+
1200+
def stored(x, k):
1201+
try:
1202+
k()
1203+
except ValueError:
1204+
k(x)
1205+
y = x
1206+
self.assertInBytecode(stored, "LOAD_FAST_BORROW", "x")
1207+
self.assertInBytecode(stored, "LOAD_FAST", "x")
1208+
11591209
class DirectCfgOptimizerTests(CfgOptimizationTestCase):
11601210

11611211
def test_optimize_cfg_const_index_out_of_range(self):

‎Python/flowgraph.c‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2954,6 +2954,13 @@ optimize_load_fast(cfg_builder *g)
29542954
int opcode = instr->i_opcode;
29552955
int oparg = instr->i_oparg;
29562956
assert(opcode != EXTENDED_ARG);
2957+
if (instr->i_except != NULL) {
2958+
// Exception handlers are only reachable through the
2959+
// exception edges once the SETUP_* pseudo-instructions
2960+
// have been converted, so make sure we analyze them too.
2961+
basicblock *handler = instr->i_except;
2962+
load_fast_push_block(&sp, handler, handler->b_startdepth);
2963+
}
29572964
switch (opcode) {
29582965
// Opcodes that load and store locals
29592966
case DELETE_FAST: {
@@ -3061,6 +3068,17 @@ optimize_load_fast(cfg_builder *g)
30613068
break;
30623069
}
30633070

3071+
case END_ASYNC_FOR: {
3072+
// The target of END_ASYNC_FOR is only used for
3073+
// monitoring, it is not a jump (see
3074+
// calculate_stackdepth()).
3075+
assert(_PyOpcode_num_popped(opcode, oparg) == 2);
3076+
assert(_PyOpcode_num_pushed(opcode, oparg) == 0);
3077+
ref_stack_pop(&refs);
3078+
ref_stack_pop(&refs);
3079+
break;
3080+
}
3081+
30643082
case END_SEND: {
30653083
assert(_PyOpcode_num_popped(opcode, oparg) == 3);
30663084
assert(_PyOpcode_num_pushed(opcode, oparg) == 1);

0 commit comments

Comments
 (0)