Skip to content

Commit 9de7ec7

Browse files
committed
http://bugs.python.org/issue4715
This patch by Antoine Pitrou optimizes the bytecode for conditional branches by merging the following "POP_TOP" instruction into the conditional jump. For example, the list comprehension "[x for x in l if not x]" produced the following bytecode: 1 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 23 (to 32) 9 STORE_FAST 1 (x) 12 LOAD_FAST 1 (x) 15 JUMP_IF_TRUE 10 (to 28) 18 POP_TOP 19 LOAD_FAST 1 (x) 22 LIST_APPEND 2 25 JUMP_ABSOLUTE 6 >> 28 POP_TOP 29 JUMP_ABSOLUTE 6 >> 32 RETURN_VALUE but after the patch it produces the following bytecode: 1 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 18 (to 27) 9 STORE_FAST 1 (x) 12 LOAD_FAST 1 (x) 15 POP_JUMP_IF_TRUE 6 18 LOAD_FAST 1 (x) 21 LIST_APPEND 2 24 JUMP_ABSOLUTE 6 >> 27 RETURN_VALUE Notice that not only the code is shorter, but the conditional jump (POP_JUMP_IF_TRUE) jumps right to the start of the loop instead of going through the JUMP_ABSOLUTE at the end. "continue" statements are helped similarly. Furthermore, the old jump opcodes (JUMP_IF_FALSE, JUMP_IF_TRUE) have been replaced by two new opcodes: - JUMP_IF_TRUE_OR_POP, which jumps if true and pops otherwise - JUMP_IF_FALSE_OR_POP, which jumps if false and pops otherwise
1 parent 0a68b01 commit 9de7ec7

9 files changed

Lines changed: 193 additions & 127 deletions

File tree

‎Doc/library/dis.rst‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -595,16 +595,26 @@ the more significant byte last.
595595
Increments bytecode counter by *delta*.
596596

597597

598-
.. opcode:: JUMP_IF_TRUE (delta)
598+
.. opcode:: POP_JUMP_IF_TRUE (target)
599599

600-
If TOS is true, increment the bytecode counter by *delta*. TOS is left on the
601-
stack.
600+
If TOS is true, sets the bytecode counter to *target*. TOS is popped.
602601

603602

604-
.. opcode:: JUMP_IF_FALSE (delta)
603+
.. opcode:: POP_JUMP_IF_FALSE (target)
605604

606-
If TOS is false, increment the bytecode counter by *delta*. TOS is not
607-
changed.
605+
If TOS is false, sets the bytecode counter to *target*. TOS is popped.
606+
607+
608+
.. opcode:: JUMP_IF_TRUE_OR_POP (target)
609+
610+
If TOS is true, sets the bytecode counter to *target* and leaves TOS
611+
on the stack. Otherwise (TOS is false), TOS is popped.
612+
613+
614+
.. opcode:: JUMP_IF_FALSE_OR_POP (target)
615+
616+
If TOS is false, sets the bytecode counter to *target* and leaves
617+
TOS on the stack. Otherwise (TOS is true), TOS is popped.
608618

609619

610620
.. opcode:: JUMP_ABSOLUTE (target)

‎Include/opcode.h‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ extern "C" {
9696
#define IMPORT_FROM 109 /* Index in name list */
9797

9898
#define JUMP_FORWARD 110 /* Number of bytes to skip */
99-
#define JUMP_IF_FALSE 111 /* "" */
100-
#define JUMP_IF_TRUE 112 /* "" */
101-
#define JUMP_ABSOLUTE 113 /* Target byte offset from beginning of code */
99+
#define JUMP_IF_FALSE_OR_POP 111 /* Target byte offset from beginning of code */
100+
#define JUMP_IF_TRUE_OR_POP 112 /* "" */
101+
#define JUMP_ABSOLUTE 113 /* "" */
102+
#define POP_JUMP_IF_FALSE 114 /* "" */
103+
#define POP_JUMP_IF_TRUE 115 /* "" */
102104

103105
#define LOAD_GLOBAL 116 /* Index in name list */
104106

‎Lib/opcode.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,11 @@ def jabs_op(name, op):
131131
name_op('IMPORT_FROM', 109) # Index in name list
132132

133133
jrel_op('JUMP_FORWARD', 110) # Number of bytes to skip
134-
jrel_op('JUMP_IF_FALSE', 111) # ""
135-
jrel_op('JUMP_IF_TRUE', 112) # ""
136-
jabs_op('JUMP_ABSOLUTE', 113) # Target byte offset from beginning of code
134+
jabs_op('JUMP_IF_FALSE_OR_POP', 111) # Target byte offset from beginning of code
135+
jabs_op('JUMP_IF_TRUE_OR_POP', 112) # ""
136+
jabs_op('JUMP_ABSOLUTE', 113) # ""
137+
jabs_op('POP_JUMP_IF_FALSE', 114) # ""
138+
jabs_op('POP_JUMP_IF_TRUE', 115) # ""
137139

138140
name_op('LOAD_GLOBAL', 116) # Index in name list
139141

‎Lib/test/test_peepholer.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ def dis_single(line):
1919
class TestTranforms(unittest.TestCase):
2020

2121
def test_unot(self):
22-
# UNARY_NOT JUMP_IF_FALSE POP_TOP --> JUMP_IF_TRUE POP_TOP'
22+
# UNARY_NOT POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE'
2323
def unot(x):
2424
if not x == 2:
2525
del x
2626
asm = disassemble(unot)
27-
for elem in ('UNARY_NOT', 'JUMP_IF_FALSE'):
27+
for elem in ('UNARY_NOT', 'POP_JUMP_IF_FALSE'):
2828
self.assert_(elem not in asm)
29-
for elem in ('JUMP_IF_TRUE', 'POP_TOP'):
29+
for elem in ('POP_JUMP_IF_TRUE',):
3030
self.assert_(elem in asm)
3131

3232
def test_elim_inversion_of_is_or_in(self):
@@ -64,13 +64,13 @@ def f():
6464
self.assert_('LOAD_GLOBAL' not in disassemble(f))
6565

6666
def test_while_one(self):
67-
# Skip over: LOAD_CONST trueconst JUMP_IF_FALSE xx POP_TOP
67+
# Skip over: LOAD_CONST trueconst POP_JUMP_IF_FALSE xx
6868
def f():
6969
while 1:
7070
pass
7171
return list
7272
asm = disassemble(f)
73-
for elem in ('LOAD_CONST', 'JUMP_IF_FALSE'):
73+
for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'):
7474
self.assert_(elem not in asm)
7575
for elem in ('JUMP_ABSOLUTE',):
7676
self.assert_(elem in asm)

‎Python/ceval.c‎

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
12951295
SETLOCAL(oparg, v);
12961296
FAST_DISPATCH();
12971297

1298-
PREDICTED(POP_TOP);
12991298
TARGET(POP_TOP)
13001299
v = POP();
13011300
Py_DECREF(v);
@@ -2204,8 +2203,8 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
22042203
Py_DECREF(w);
22052204
SET_TOP(x);
22062205
if (x == NULL) break;
2207-
PREDICT(JUMP_IF_FALSE);
2208-
PREDICT(JUMP_IF_TRUE);
2206+
PREDICT(POP_JUMP_IF_FALSE);
2207+
PREDICT(POP_JUMP_IF_TRUE);
22092208
DISPATCH();
22102209

22112210
TARGET(IMPORT_NAME)
@@ -2282,48 +2281,99 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
22822281
JUMPBY(oparg);
22832282
FAST_DISPATCH();
22842283

2285-
PREDICTED_WITH_ARG(JUMP_IF_FALSE);
2286-
TARGET(JUMP_IF_FALSE)
2287-
w = TOP();
2284+
PREDICTED_WITH_ARG(POP_JUMP_IF_FALSE);
2285+
TARGET(POP_JUMP_IF_FALSE)
2286+
w = POP();
22882287
if (w == Py_True) {
2289-
PREDICT(POP_TOP);
2288+
Py_DECREF(w);
22902289
FAST_DISPATCH();
22912290
}
22922291
if (w == Py_False) {
2293-
JUMPBY(oparg);
2292+
Py_DECREF(w);
2293+
JUMPTO(oparg);
22942294
FAST_DISPATCH();
22952295
}
22962296
err = PyObject_IsTrue(w);
2297+
Py_DECREF(w);
22972298
if (err > 0)
22982299
err = 0;
22992300
else if (err == 0)
2300-
JUMPBY(oparg);
2301+
JUMPTO(oparg);
23012302
else
23022303
break;
23032304
DISPATCH();
23042305

2305-
PREDICTED_WITH_ARG(JUMP_IF_TRUE);
2306-
TARGET(JUMP_IF_TRUE)
2307-
w = TOP();
2306+
PREDICTED_WITH_ARG(POP_JUMP_IF_TRUE);
2307+
TARGET(POP_JUMP_IF_TRUE)
2308+
w = POP();
23082309
if (w == Py_False) {
2309-
PREDICT(POP_TOP);
2310+
Py_DECREF(w);
23102311
FAST_DISPATCH();
23112312
}
23122313
if (w == Py_True) {
2313-
JUMPBY(oparg);
2314+
Py_DECREF(w);
2315+
JUMPTO(oparg);
23142316
FAST_DISPATCH();
23152317
}
23162318
err = PyObject_IsTrue(w);
2319+
Py_DECREF(w);
23172320
if (err > 0) {
23182321
err = 0;
2319-
JUMPBY(oparg);
2322+
JUMPTO(oparg);
23202323
}
23212324
else if (err == 0)
23222325
;
23232326
else
23242327
break;
23252328
DISPATCH();
23262329

2330+
TARGET(JUMP_IF_FALSE_OR_POP)
2331+
w = TOP();
2332+
if (w == Py_True) {
2333+
STACKADJ(-1);
2334+
Py_DECREF(w);
2335+
FAST_DISPATCH();
2336+
}
2337+
if (w == Py_False) {
2338+
JUMPTO(oparg);
2339+
FAST_DISPATCH();
2340+
}
2341+
err = PyObject_IsTrue(w);
2342+
if (err > 0) {
2343+
STACKADJ(-1);
2344+
Py_DECREF(w);
2345+
err = 0;
2346+
}
2347+
else if (err == 0)
2348+
JUMPTO(oparg);
2349+
else
2350+
break;
2351+
DISPATCH();
2352+
2353+
TARGET(JUMP_IF_TRUE_OR_POP)
2354+
w = TOP();
2355+
if (w == Py_False) {
2356+
STACKADJ(-1);
2357+
Py_DECREF(w);
2358+
FAST_DISPATCH();
2359+
}
2360+
if (w == Py_True) {
2361+
JUMPTO(oparg);
2362+
FAST_DISPATCH();
2363+
}
2364+
err = PyObject_IsTrue(w);
2365+
if (err > 0) {
2366+
err = 0;
2367+
JUMPTO(oparg);
2368+
}
2369+
else if (err == 0) {
2370+
STACKADJ(-1);
2371+
Py_DECREF(w);
2372+
}
2373+
else
2374+
break;
2375+
DISPATCH();
2376+
23272377
PREDICTED_WITH_ARG(JUMP_ABSOLUTE);
23282378
TARGET(JUMP_ABSOLUTE)
23292379
JUMPTO(oparg);

0 commit comments

Comments
 (0)