Skip to content

Commit 3a90797

Browse files
committed
Issue python#19722: Added opcode.stack_effect(), which accurately
computes the stack effect of bytecode instructions.
1 parent 8d0d369 commit 3a90797

8 files changed

Lines changed: 177 additions & 8 deletions

File tree

Doc/library/dis.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,13 @@ object isn't useful:
217217
Detect all offsets in the code object *code* which are jump targets, and
218218
return a list of these offsets.
219219

220+
221+
.. function:: stack_effect(opcode, [oparg])
222+
223+
Compute the stack effect of *opcode* with argument *oparg*.
224+
225+
.. versionadded:: 3.4
226+
220227
.. _bytecodes:
221228

222229
Python Bytecode Instructions

Include/compile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromASTObject(
5454
/* _Py_Mangle is defined in compile.c */
5555
PyAPI_FUNC(PyObject*) _Py_Mangle(PyObject *p, PyObject *name);
5656

57+
#define PY_INVALID_STACK_EFFECT INT_MAX
58+
PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);
59+
5760
#ifdef __cplusplus
5861
}
5962
#endif

Lib/opcode.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@
88
"haslocal", "hascompare", "hasfree", "opname", "opmap",
99
"HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"]
1010

11+
# It's a chicken-and-egg I'm afraid:
12+
# We're imported before _opcode's made.
13+
# With exception unheeded
14+
# (stack_effect is not needed)
15+
# Both our chickens and eggs are allayed.
16+
# --Larry Hastings, 2013/11/23
17+
18+
try:
19+
from _opcode import stack_effect
20+
__all__.append('stack_effect')
21+
except ImportError:
22+
pass
23+
1124
cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is',
1225
'is not', 'exception match', 'BAD')
1326

Lib/test/test__opcode.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import dis
2+
import _opcode
3+
from test.support import run_unittest
4+
import unittest
5+
6+
class OpcodeTests(unittest.TestCase):
7+
8+
def test_stack_effect(self):
9+
self.assertEqual(_opcode.stack_effect(dis.opmap['POP_TOP']), -1)
10+
self.assertEqual(_opcode.stack_effect(dis.opmap['DUP_TOP_TWO']), 2)
11+
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
12+
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 1), -1)
13+
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 3), -2)
14+
self.assertRaises(ValueError, _opcode.stack_effect, 30000)
15+
self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['BUILD_SLICE'])
16+
self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['POP_TOP'], 0)
17+
18+
def test_main():
19+
run_unittest(OpcodeTests)
20+
21+
if __name__ == "__main__":
22+
test_main()

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Core and Builtins
6767

6868
Library
6969
-------
70+
- Issue #19722: Added opcode.stack_effect(), which
71+
computes the stack effect of bytecode instructions.
7072

7173
- Issue #19735: Implement private function ssl._create_stdlib_context() to
7274
create SSLContext objects in Python's stdlib module. It provides a single

Modules/_opcode.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#include "Python.h"
2+
#include "opcode.h"
3+
4+
5+
/*[clinic]
6+
7+
module _opcode
8+
9+
_opcode.stack_effect -> int
10+
11+
opcode: int
12+
13+
[
14+
oparg: int
15+
]
16+
/
17+
18+
Compute the stack effect of the opcode.
19+
[clinic]*/
20+
21+
PyDoc_STRVAR(_opcode_stack_effect__doc__,
22+
"Compute the stack effect of the opcode.\n"
23+
"\n"
24+
"_opcode.stack_effect(opcode, [oparg])");
25+
26+
#define _OPCODE_STACK_EFFECT_METHODDEF \
27+
{"stack_effect", (PyCFunction)_opcode_stack_effect, METH_VARARGS, _opcode_stack_effect__doc__},
28+
29+
static int
30+
_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg);
31+
32+
static PyObject *
33+
_opcode_stack_effect(PyObject *module, PyObject *args)
34+
{
35+
PyObject *return_value = NULL;
36+
int opcode;
37+
int group_right_1 = 0;
38+
int oparg = 0;
39+
int _return_value;
40+
41+
switch (PyTuple_Size(args)) {
42+
case 1:
43+
if (!PyArg_ParseTuple(args, "i:stack_effect", &opcode))
44+
return NULL;
45+
break;
46+
case 2:
47+
if (!PyArg_ParseTuple(args, "ii:stack_effect", &opcode, &oparg))
48+
return NULL;
49+
group_right_1 = 1;
50+
break;
51+
default:
52+
PyErr_SetString(PyExc_TypeError, "_opcode.stack_effect requires 1 to 2 arguments");
53+
return NULL;
54+
}
55+
_return_value = _opcode_stack_effect_impl(module, opcode, group_right_1, oparg);
56+
if ((_return_value == -1) && PyErr_Occurred())
57+
goto exit;
58+
return_value = PyLong_FromLong((long)_return_value);
59+
60+
exit:
61+
return return_value;
62+
}
63+
64+
static int
65+
_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg)
66+
/*[clinic checksum: 2312ded40abc9bcbce718942de21f53e61a2dfd3]*/
67+
{
68+
int effect;
69+
if (HAS_ARG(opcode)) {
70+
if (!group_right_1) {
71+
PyErr_SetString(PyExc_ValueError,
72+
"stack_effect: opcode requires oparg but oparg was not specified");
73+
return -1;
74+
}
75+
}
76+
else if (group_right_1) {
77+
PyErr_SetString(PyExc_ValueError,
78+
"stack_effect: opcode does not permit oparg but oparg was specified");
79+
return -1;
80+
}
81+
effect = PyCompile_OpcodeStackEffect(opcode, oparg);
82+
if (effect == PY_INVALID_STACK_EFFECT) {
83+
PyErr_SetString(PyExc_ValueError,
84+
"invalid opcode or oparg");
85+
return -1;
86+
}
87+
return effect;
88+
}
89+
90+
91+
92+
93+
static PyMethodDef
94+
opcode_functions[] = {
95+
_OPCODE_STACK_EFFECT_METHODDEF
96+
{NULL, NULL, 0, NULL}
97+
};
98+
99+
100+
static struct PyModuleDef opcodemodule = {
101+
PyModuleDef_HEAD_INIT,
102+
"_opcode",
103+
"Opcode support module.",
104+
-1,
105+
opcode_functions,
106+
NULL,
107+
NULL,
108+
NULL,
109+
NULL
110+
};
111+
112+
PyMODINIT_FUNC
113+
PyInit__opcode(void)
114+
{
115+
return PyModule_Create(&opcodemodule);
116+
}

Python/compile.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -853,8 +853,8 @@ compiler_set_lineno(struct compiler *c, int off)
853853
b->b_instr[off].i_lineno = c->u->u_lineno;
854854
}
855855

856-
static int
857-
opcode_stack_effect(int opcode, int oparg)
856+
int
857+
PyCompile_OpcodeStackEffect(int opcode, int oparg)
858858
{
859859
switch (opcode) {
860860
case POP_TOP:
@@ -1044,11 +1044,9 @@ opcode_stack_effect(int opcode, int oparg)
10441044
case DELETE_DEREF:
10451045
return 0;
10461046
default:
1047-
fprintf(stderr, "opcode = %d\n", opcode);
1048-
Py_FatalError("opcode_stack_effect()");
1049-
1047+
return PY_INVALID_STACK_EFFECT;
10501048
}
1051-
return 0; /* not reachable */
1049+
return PY_INVALID_STACK_EFFECT; /* not reachable */
10521050
}
10531051

10541052
/* Add an opcode with no argument.
@@ -3829,15 +3827,21 @@ dfs(struct compiler *c, basicblock *b, struct assembler *a)
38293827
static int
38303828
stackdepth_walk(struct compiler *c, basicblock *b, int depth, int maxdepth)
38313829
{
3832-
int i, target_depth;
3830+
int i, target_depth, effect;
38333831
struct instr *instr;
38343832
if (b->b_seen || b->b_startdepth >= depth)
38353833
return maxdepth;
38363834
b->b_seen = 1;
38373835
b->b_startdepth = depth;
38383836
for (i = 0; i < b->b_iused; i++) {
38393837
instr = &b->b_instr[i];
3840-
depth += opcode_stack_effect(instr->i_opcode, instr->i_oparg);
3838+
effect = PyCompile_OpcodeStackEffect(instr->i_opcode, instr->i_oparg);
3839+
if (effect == PY_INVALID_STACK_EFFECT) {
3840+
fprintf(stderr, "opcode = %d\n", instr->i_opcode);
3841+
Py_FatalError("PyCompile_OpcodeStackEffect()");
3842+
}
3843+
depth += effect;
3844+
38413845
if (depth > maxdepth)
38423846
maxdepth = depth;
38433847
assert(depth >= 0); /* invalid code or bug in stackdepth() */

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,8 @@ def detect_modules(self):
596596
exts.append( Extension('_lsprof', ['_lsprof.c', 'rotatingtree.c']) )
597597
# static Unicode character database
598598
exts.append( Extension('unicodedata', ['unicodedata.c']) )
599+
# _opcode module
600+
exts.append( Extension('_opcode', ['_opcode.c']) )
599601

600602
# Modules with some UNIX dependencies -- on by default:
601603
# (If you have a really backward UNIX, select and socket may not be

0 commit comments

Comments
 (0)