Skip to content

Commit 7bdf282

Browse files
bpo-32455: Add jump parameter to dis.stack_effect(). (GH-6610)
Add C API function PyCompile_OpcodeStackEffectWithJump().
1 parent b042cf1 commit 7bdf282

File tree

8 files changed

+99
-26
lines changed

8 files changed

+99
-26
lines changed

Doc/library/dis.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,21 @@ operation is being performed, so the intermediate analysis object isn't useful:
248248
return a list of these offsets.
249249

250250

251-
.. function:: stack_effect(opcode, [oparg])
251+
.. function:: stack_effect(opcode, oparg=None, *, jump=None)
252252

253253
Compute the stack effect of *opcode* with argument *oparg*.
254254

255+
If the code has a jump target and *jump* is ``True``, :func:`~stack_effect`
256+
will return the stack effect of jumping. If *jump* is ``False``,
257+
it will return the stack effect of not jumping. And if *jump* is
258+
``None`` (default), it will return the maximal stack effect of both cases.
259+
255260
.. versionadded:: 3.4
256261

262+
.. versionchanged:: 3.8
263+
Added *jump* parameter.
264+
265+
257266
.. _bytecodes:
258267

259268
Python Bytecode Instructions

Include/compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ PyAPI_FUNC(PyObject*) _Py_Mangle(PyObject *p, PyObject *name);
7575

7676
#define PY_INVALID_STACK_EFFECT INT_MAX
7777
PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);
78+
PyAPI_FUNC(int) PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump);
7879

7980
PyAPI_FUNC(int) _PyAST_Optimize(struct _mod *, PyArena *arena, int optimize);
8081

Lib/test/test__opcode.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,65 @@
33
import unittest
44

55
_opcode = import_module("_opcode")
6+
from _opcode import stack_effect
7+
68

79
class OpcodeTests(unittest.TestCase):
810

911
def test_stack_effect(self):
10-
self.assertEqual(_opcode.stack_effect(dis.opmap['POP_TOP']), -1)
11-
self.assertEqual(_opcode.stack_effect(dis.opmap['DUP_TOP_TWO']), 2)
12-
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
13-
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 1), -1)
14-
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 3), -2)
15-
self.assertRaises(ValueError, _opcode.stack_effect, 30000)
16-
self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['BUILD_SLICE'])
17-
self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['POP_TOP'], 0)
12+
self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1)
13+
self.assertEqual(stack_effect(dis.opmap['DUP_TOP_TWO']), 2)
14+
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
15+
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 1), -1)
16+
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 3), -2)
17+
self.assertRaises(ValueError, stack_effect, 30000)
18+
self.assertRaises(ValueError, stack_effect, dis.opmap['BUILD_SLICE'])
19+
self.assertRaises(ValueError, stack_effect, dis.opmap['POP_TOP'], 0)
1820
# All defined opcodes
1921
for name, code in dis.opmap.items():
2022
with self.subTest(opname=name):
2123
if code < dis.HAVE_ARGUMENT:
22-
_opcode.stack_effect(code)
23-
self.assertRaises(ValueError, _opcode.stack_effect, code, 0)
24+
stack_effect(code)
25+
self.assertRaises(ValueError, stack_effect, code, 0)
2426
else:
25-
_opcode.stack_effect(code, 0)
26-
self.assertRaises(ValueError, _opcode.stack_effect, code)
27+
stack_effect(code, 0)
28+
self.assertRaises(ValueError, stack_effect, code)
2729
# All not defined opcodes
2830
for code in set(range(256)) - set(dis.opmap.values()):
2931
with self.subTest(opcode=code):
30-
self.assertRaises(ValueError, _opcode.stack_effect, code)
31-
self.assertRaises(ValueError, _opcode.stack_effect, code, 0)
32+
self.assertRaises(ValueError, stack_effect, code)
33+
self.assertRaises(ValueError, stack_effect, code, 0)
34+
35+
def test_stack_effect_jump(self):
36+
JUMP_IF_TRUE_OR_POP = dis.opmap['JUMP_IF_TRUE_OR_POP']
37+
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0), 0)
38+
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0, jump=True), 0)
39+
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0, jump=False), -1)
40+
FOR_ITER = dis.opmap['FOR_ITER']
41+
self.assertEqual(stack_effect(FOR_ITER, 0), 1)
42+
self.assertEqual(stack_effect(FOR_ITER, 0, jump=True), -1)
43+
self.assertEqual(stack_effect(FOR_ITER, 0, jump=False), 1)
44+
JUMP_FORWARD = dis.opmap['JUMP_FORWARD']
45+
self.assertEqual(stack_effect(JUMP_FORWARD, 0), 0)
46+
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0)
47+
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0)
48+
# All defined opcodes
49+
has_jump = dis.hasjabs + dis.hasjrel
50+
for name, code in dis.opmap.items():
51+
with self.subTest(opname=name):
52+
if code < dis.HAVE_ARGUMENT:
53+
common = stack_effect(code)
54+
jump = stack_effect(code, jump=True)
55+
nojump = stack_effect(code, jump=False)
56+
else:
57+
common = stack_effect(code, 0)
58+
jump = stack_effect(code, 0, jump=True)
59+
nojump = stack_effect(code, 0, jump=False)
60+
if code in has_jump:
61+
self.assertEqual(common, max(jump, nojump))
62+
else:
63+
self.assertEqual(jump, common)
64+
self.assertEqual(nojump, common)
3265

3366

3467
if __name__ == "__main__":
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added :c:func:`PyCompile_OpcodeStackEffectWithJump`.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added *jump* parameter to :func:`dis.stack_effect`.

Modules/_opcode.c

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@ _opcode.stack_effect -> int
1515
opcode: int
1616
oparg: object = None
1717
/
18+
*
19+
jump: object = None
1820
1921
Compute the stack effect of the opcode.
2022
[clinic start generated code]*/
2123

2224
static int
23-
_opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg)
24-
/*[clinic end generated code: output=ad39467fa3ad22ce input=2d0a9ee53c0418f5]*/
25+
_opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg,
26+
PyObject *jump)
27+
/*[clinic end generated code: output=64a18f2ead954dbb input=461c9d4a44851898]*/
2528
{
2629
int effect;
2730
int oparg_int = 0;
31+
int jump_int;
2832
if (HAS_ARG(opcode)) {
2933
if (oparg == Py_None) {
3034
PyErr_SetString(PyExc_ValueError,
@@ -40,7 +44,21 @@ _opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg)
4044
"stack_effect: opcode does not permit oparg but oparg was specified");
4145
return -1;
4246
}
43-
effect = PyCompile_OpcodeStackEffect(opcode, oparg_int);
47+
if (jump == Py_None) {
48+
jump_int = -1;
49+
}
50+
else if (jump == Py_True) {
51+
jump_int = 1;
52+
}
53+
else if (jump == Py_False) {
54+
jump_int = 0;
55+
}
56+
else {
57+
PyErr_SetString(PyExc_ValueError,
58+
"stack_effect: jump must be False, True or None");
59+
return -1;
60+
}
61+
effect = PyCompile_OpcodeStackEffectWithJump(opcode, oparg_int, jump_int);
4462
if (effect == PY_INVALID_STACK_EFFECT) {
4563
PyErr_SetString(PyExc_ValueError,
4664
"invalid opcode or oparg");

Modules/clinic/_opcode.c.h

Lines changed: 12 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/compile.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,12 @@ stack_effect(int opcode, int oparg, int jump)
11161116
return PY_INVALID_STACK_EFFECT; /* not reachable */
11171117
}
11181118

1119+
int
1120+
PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump)
1121+
{
1122+
return stack_effect(opcode, oparg, jump);
1123+
}
1124+
11191125
int
11201126
PyCompile_OpcodeStackEffect(int opcode, int oparg)
11211127
{

0 commit comments

Comments
 (0)