Skip to content

Commit 6b77af2

Browse files
authored
gh-134889: Fix handling of a few opcodes when optimizing LOAD_FAST (#134958)
We were incorrectly handling a few opcodes that leave their operands on the stack. Treat all of these conservatively; assume that they always leave operands on the stack.
1 parent e598eec commit 6b77af2

File tree

5 files changed

+117
-2
lines changed

5 files changed

+117
-2
lines changed

Include/internal/pycore_magic_number.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ Known values:
280280
Python 3.15a0 3650 (Initial version)
281281
Python 3.15a1 3651 (Simplify LOAD_CONST)
282282
Python 3.15a1 3652 (Virtual iterators)
283+
Python 3.15a1 3653 (Fix handling of opcodes that may leave operands on the stack when optimizing LOAD_FAST)
283284
284285
285286
Python 3.16 will start with 3700
@@ -293,7 +294,7 @@ PC/launcher.c must also be updated.
293294
294295
*/
295296

296-
#define PYC_MAGIC_NUMBER 3652
297+
#define PYC_MAGIC_NUMBER 3653
297298
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
298299
(little-endian) and then appending b'\r\n'. */
299300
#define PYC_MAGIC_NUMBER_TOKEN \

Lib/test/test_dis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ async def _asyncwith(c):
606606
POP_TOP
607607
L1: RESUME 0
608608
609-
%4d LOAD_FAST_BORROW 0 (c)
609+
%4d LOAD_FAST 0 (c)
610610
COPY 1
611611
LOAD_SPECIAL 3 (__aexit__)
612612
SWAP 2

Lib/test/test_peepholer.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,6 +2614,90 @@ def test_send(self):
26142614
]
26152615
self.cfg_optimization_test(insts, expected, consts=[None])
26162616

2617+
def test_format_simple(self):
2618+
# FORMAT_SIMPLE will leave its operand on the stack if it's a unicode
2619+
# object. We treat it conservatively and assume that it always leaves
2620+
# its operand on the stack.
2621+
insts = [
2622+
("LOAD_FAST", 0, 1),
2623+
("FORMAT_SIMPLE", None, 2),
2624+
("STORE_FAST", 1, 3),
2625+
]
2626+
self.check(insts, insts)
2627+
2628+
insts = [
2629+
("LOAD_FAST", 0, 1),
2630+
("FORMAT_SIMPLE", None, 2),
2631+
("POP_TOP", None, 3),
2632+
]
2633+
expected = [
2634+
("LOAD_FAST_BORROW", 0, 1),
2635+
("FORMAT_SIMPLE", None, 2),
2636+
("POP_TOP", None, 3),
2637+
]
2638+
self.check(insts, expected)
2639+
2640+
def test_set_function_attribute(self):
2641+
# SET_FUNCTION_ATTRIBUTE leaves the function on the stack
2642+
insts = [
2643+
("LOAD_CONST", 0, 1),
2644+
("LOAD_FAST", 0, 2),
2645+
("SET_FUNCTION_ATTRIBUTE", 2, 3),
2646+
("STORE_FAST", 1, 4),
2647+
("LOAD_CONST", 0, 5),
2648+
("RETURN_VALUE", None, 6)
2649+
]
2650+
self.cfg_optimization_test(insts, insts, consts=[None])
2651+
2652+
insts = [
2653+
("LOAD_CONST", 0, 1),
2654+
("LOAD_FAST", 0, 2),
2655+
("SET_FUNCTION_ATTRIBUTE", 2, 3),
2656+
("RETURN_VALUE", None, 4)
2657+
]
2658+
expected = [
2659+
("LOAD_CONST", 0, 1),
2660+
("LOAD_FAST_BORROW", 0, 2),
2661+
("SET_FUNCTION_ATTRIBUTE", 2, 3),
2662+
("RETURN_VALUE", None, 4)
2663+
]
2664+
self.cfg_optimization_test(insts, expected, consts=[None])
2665+
2666+
def test_get_yield_from_iter(self):
2667+
# GET_YIELD_FROM_ITER may leave its operand on the stack
2668+
insts = [
2669+
("LOAD_FAST", 0, 1),
2670+
("GET_YIELD_FROM_ITER", None, 2),
2671+
("LOAD_CONST", 0, 3),
2672+
send := self.Label(),
2673+
("SEND", end := self.Label(), 5),
2674+
("YIELD_VALUE", 1, 6),
2675+
("RESUME", 2, 7),
2676+
("JUMP", send, 8),
2677+
end,
2678+
("END_SEND", None, 9),
2679+
("LOAD_CONST", 0, 10),
2680+
("RETURN_VALUE", None, 11),
2681+
]
2682+
self.cfg_optimization_test(insts, insts, consts=[None])
2683+
2684+
def test_push_exc_info(self):
2685+
insts = [
2686+
("LOAD_FAST", 0, 1),
2687+
("PUSH_EXC_INFO", None, 2),
2688+
]
2689+
self.check(insts, insts)
2690+
2691+
def test_load_special(self):
2692+
# LOAD_SPECIAL may leave self on the stack
2693+
insts = [
2694+
("LOAD_FAST", 0, 1),
2695+
("LOAD_SPECIAL", 0, 2),
2696+
("STORE_FAST", 1, 3),
2697+
]
2698+
self.check(insts, insts)
2699+
2700+
26172701
def test_del_in_finally(self):
26182702
# This loads `obj` onto the stack, executes `del obj`, then returns the
26192703
# `obj` from the stack. See gh-133371 for more details.
@@ -2630,6 +2714,14 @@ def create_obj():
26302714
gc.collect()
26312715
self.assertEqual(obj, [42])
26322716

2717+
def test_format_simple_unicode(self):
2718+
# Repro from gh-134889
2719+
def f():
2720+
var = f"{1}"
2721+
var = f"{var}"
2722+
return var
2723+
self.assertEqual(f(), "1")
2724+
26332725

26342726

26352727
if __name__ == "__main__":
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix handling of a few opcodes that leave operands on the stack when
2+
optimizing ``LOAD_FAST``.

Python/flowgraph.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,9 +2870,11 @@ optimize_load_fast(cfg_builder *g)
28702870
// how many inputs should be left on the stack.
28712871

28722872
// Opcodes that consume no inputs
2873+
case FORMAT_SIMPLE:
28732874
case GET_ANEXT:
28742875
case GET_ITER:
28752876
case GET_LEN:
2877+
case GET_YIELD_FROM_ITER:
28762878
case IMPORT_FROM:
28772879
case MATCH_KEYS:
28782880
case MATCH_MAPPING:
@@ -2907,6 +2909,16 @@ optimize_load_fast(cfg_builder *g)
29072909
break;
29082910
}
29092911

2912+
case END_SEND:
2913+
case SET_FUNCTION_ATTRIBUTE: {
2914+
assert(_PyOpcode_num_popped(opcode, oparg) == 2);
2915+
assert(_PyOpcode_num_pushed(opcode, oparg) == 1);
2916+
ref tos = ref_stack_pop(&refs);
2917+
ref_stack_pop(&refs);
2918+
PUSH_REF(tos.instr, tos.local);
2919+
break;
2920+
}
2921+
29102922
// Opcodes that consume some inputs and push new values
29112923
case CHECK_EXC_MATCH: {
29122924
ref_stack_pop(&refs);
@@ -2936,6 +2948,14 @@ optimize_load_fast(cfg_builder *g)
29362948
break;
29372949
}
29382950

2951+
case LOAD_SPECIAL:
2952+
case PUSH_EXC_INFO: {
2953+
ref tos = ref_stack_pop(&refs);
2954+
PUSH_REF(i, NOT_LOCAL);
2955+
PUSH_REF(tos.instr, tos.local);
2956+
break;
2957+
}
2958+
29392959
case SEND: {
29402960
load_fast_push_block(&sp, instr->i_target, refs.size);
29412961
ref_stack_pop(&refs);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy