Skip to content

Commit c30e1e9

Browse files
committed
Handle opcodes that leave at least one input on the stack
1 parent 818e94e commit c30e1e9

File tree

2 files changed

+207
-11
lines changed

2 files changed

+207
-11
lines changed

Lib/test/test_peepholer.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,6 +2553,125 @@ def test_unoptimized_if_aliased(self):
25532553
]
25542554
self.check(insts, insts)
25552555

2556+
def test_consume_no_inputs(self):
2557+
insts = [
2558+
("LOAD_FAST", 0, 1),
2559+
("GET_LEN", None, 2),
2560+
("STORE_FAST", 1 , 3),
2561+
("STORE_FAST", 2, 4),
2562+
]
2563+
self.check(insts, insts)
2564+
2565+
def test_consume_some_inputs_no_outputs(self):
2566+
insts = [
2567+
("LOAD_FAST", 0, 1),
2568+
("GET_LEN", None, 2),
2569+
("LIST_APPEND", 0, 3),
2570+
]
2571+
self.check(insts, insts)
2572+
2573+
def test_check_exc_match(self):
2574+
insts = [
2575+
("LOAD_FAST", 0, 1),
2576+
("LOAD_FAST", 1, 2),
2577+
("CHECK_EXC_MATCH", None, 3)
2578+
]
2579+
expected = [
2580+
("LOAD_FAST", 0, 1),
2581+
("LOAD_FAST_BORROW", 1, 2),
2582+
("CHECK_EXC_MATCH", None, 3)
2583+
]
2584+
self.check(insts, expected)
2585+
2586+
def test_for_iter(self):
2587+
insts = [
2588+
("LOAD_FAST", 0, 1),
2589+
top := self.Label(),
2590+
("FOR_ITER", end := self.Label(), 2),
2591+
("STORE_FAST", 2, 3),
2592+
("JUMP", top, 4),
2593+
end,
2594+
("END_FOR", None, 5),
2595+
("POP_TOP", None, 6),
2596+
("LOAD_CONST", 0, 7),
2597+
("RETURN_VALUE", None, 8),
2598+
]
2599+
self.cfg_optimization_test(insts, insts, consts=[None])
2600+
2601+
def test_load_attr(self):
2602+
insts = [
2603+
("LOAD_FAST", 0, 1),
2604+
("LOAD_ATTR", 0, 2),
2605+
]
2606+
expected = [
2607+
("LOAD_FAST_BORROW", 0, 1),
2608+
("LOAD_ATTR", 0, 2),
2609+
]
2610+
self.check(insts, expected)
2611+
2612+
# Method call, leaves self on stack unconsumed
2613+
insts = [
2614+
("LOAD_FAST", 0, 1),
2615+
("LOAD_ATTR", 1, 2),
2616+
]
2617+
expected = [
2618+
("LOAD_FAST", 0, 1),
2619+
("LOAD_ATTR", 1, 2),
2620+
]
2621+
self.check(insts, expected)
2622+
2623+
def test_super_attr(self):
2624+
insts = [
2625+
("LOAD_FAST", 0, 1),
2626+
("LOAD_FAST", 1, 2),
2627+
("LOAD_FAST", 2, 3),
2628+
("LOAD_SUPER_ATTR", 0, 4),
2629+
]
2630+
expected = [
2631+
("LOAD_FAST_BORROW", 0, 1),
2632+
("LOAD_FAST_BORROW", 1, 2),
2633+
("LOAD_FAST_BORROW", 2, 3),
2634+
("LOAD_SUPER_ATTR", 0, 4),
2635+
]
2636+
self.check(insts, expected)
2637+
2638+
# Method call, leaves self on stack unconsumed
2639+
insts = [
2640+
("LOAD_FAST", 0, 1),
2641+
("LOAD_FAST", 1, 2),
2642+
("LOAD_FAST", 2, 3),
2643+
("LOAD_SUPER_ATTR", 1, 4),
2644+
]
2645+
expected = [
2646+
("LOAD_FAST_BORROW", 0, 1),
2647+
("LOAD_FAST_BORROW", 1, 2),
2648+
("LOAD_FAST", 2, 3),
2649+
("LOAD_SUPER_ATTR", 1, 4),
2650+
]
2651+
self.check(insts, expected)
2652+
2653+
def test_send(self):
2654+
insts = [
2655+
("LOAD_FAST", 0, 1),
2656+
("LOAD_FAST", 1, 2),
2657+
("SEND", end := self.Label(), 3),
2658+
("LOAD_CONST", 0, 4),
2659+
("RETURN_VALUE", None, 5),
2660+
end,
2661+
("LOAD_CONST", 0, 6),
2662+
("RETURN_VALUE", None, 7)
2663+
]
2664+
expected = [
2665+
("LOAD_FAST", 0, 1),
2666+
("LOAD_FAST_BORROW", 1, 2),
2667+
("SEND", end := self.Label(), 3),
2668+
("LOAD_CONST", 0, 4),
2669+
("RETURN_VALUE", None, 5),
2670+
end,
2671+
("LOAD_CONST", 0, 6),
2672+
("RETURN_VALUE", None, 7)
2673+
]
2674+
self.cfg_optimization_test(insts, expected, consts=[None])
25562675

25572676

25582677

Python/flowgraph.c

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,17 +2794,7 @@ optimize_load_fast(cfg_builder *g)
27942794
int oparg = instr->i_oparg;
27952795
assert(opcode != EXTENDED_ARG);
27962796
switch (opcode) {
2797-
case COPY: {
2798-
assert(oparg > 0);
2799-
Py_ssize_t idx = refs.size - oparg;
2800-
ref r = ref_stack_at(&refs, idx);
2801-
if (ref_stack_push(&refs, r) < 0) {
2802-
status = ERROR;
2803-
goto done;
2804-
}
2805-
break;
2806-
}
2807-
2797+
// Opcodes that load and store locals
28082798
case LOAD_FAST: {
28092799
PUSH_REF(i, oparg);
28102800
break;
@@ -2847,12 +2837,99 @@ optimize_load_fast(cfg_builder *g)
28472837
break;
28482838
}
28492839

2840+
// Opcodes that shuffle values on the stack
2841+
case COPY: {
2842+
assert(oparg > 0);
2843+
Py_ssize_t idx = refs.size - oparg;
2844+
ref r = ref_stack_at(&refs, idx);
2845+
PUSH_REF(r.instr, r.local);
2846+
break;
2847+
}
2848+
28502849
case SWAP: {
28512850
assert(oparg >= 2);
28522851
ref_stack_swap_top(&refs, oparg);
28532852
break;
28542853
}
28552854

2855+
// We treat opcodes that do not consume all of their inputs on
2856+
// a case by case basis, as we have no generic way of knowing
2857+
// how many inputs should be left on the stack.
2858+
2859+
// Opcodes that consume no inputs
2860+
case GET_ANEXT:
2861+
case GET_LEN:
2862+
case IMPORT_FROM:
2863+
case MATCH_KEYS:
2864+
case MATCH_MAPPING:
2865+
case MATCH_SEQUENCE:
2866+
case WITH_EXCEPT_START: {
2867+
int num_popped = _PyOpcode_num_popped(opcode, oparg);
2868+
int num_pushed = _PyOpcode_num_pushed(opcode, oparg);
2869+
int net_pushed = num_pushed - num_popped;
2870+
assert(net_pushed >= 0);
2871+
for (int i = 0; i < net_pushed; i++) {
2872+
PUSH_REF(i, NOT_LOCAL);
2873+
}
2874+
break;
2875+
}
2876+
2877+
// Opcodes that consume some inputs and push no new values
2878+
case DICT_MERGE:
2879+
case DICT_UPDATE:
2880+
case LIST_APPEND:
2881+
case LIST_EXTEND:
2882+
case MAP_ADD:
2883+
case RERAISE:
2884+
case SET_ADD:
2885+
case SET_UPDATE: {
2886+
int num_popped = _PyOpcode_num_popped(opcode, oparg);
2887+
int num_pushed = _PyOpcode_num_pushed(opcode, oparg);
2888+
int net_popped = num_popped - num_pushed;
2889+
assert(net_popped > 0);
2890+
for (int i = 0; i < net_popped; i++) {
2891+
ref_stack_pop(&refs);
2892+
}
2893+
break;
2894+
}
2895+
2896+
// Opcodes that consume some inputs and push new values
2897+
case CHECK_EXC_MATCH: {
2898+
ref_stack_pop(&refs);
2899+
PUSH_REF(i, NOT_LOCAL);
2900+
break;
2901+
}
2902+
2903+
case FOR_ITER: {
2904+
load_fast_push_block(&sp, instr->i_target, refs.size + 1);
2905+
PUSH_REF(i, NOT_LOCAL);
2906+
break;
2907+
}
2908+
2909+
case LOAD_ATTR:
2910+
case LOAD_SUPER_ATTR: {
2911+
ref self = ref_stack_pop(&refs);
2912+
if (opcode == LOAD_SUPER_ATTR) {
2913+
ref_stack_pop(&refs);
2914+
ref_stack_pop(&refs);
2915+
}
2916+
PUSH_REF(i, NOT_LOCAL);
2917+
if (oparg & 1) {
2918+
// A method call; conservatively assume that self is pushed
2919+
// back onto the stack
2920+
PUSH_REF(self.instr, self.local);
2921+
}
2922+
break;
2923+
}
2924+
2925+
case SEND: {
2926+
load_fast_push_block(&sp, instr->i_target, refs.size);
2927+
ref_stack_pop(&refs);
2928+
PUSH_REF(i, NOT_LOCAL);
2929+
break;
2930+
}
2931+
2932+
// Opcodes that consume all of their inputs
28562933
default: {
28572934
int num_popped = _PyOpcode_num_popped(opcode, oparg);
28582935
int num_pushed = _PyOpcode_num_pushed(opcode, oparg);

0 commit comments

Comments
 (0)