Issue #19722: Added opcode.stack_effect(), which accurately

computes the stack effect of bytecode instructions.
This commit is contained in:
Larry Hastings 2013-11-23 14:49:22 -08:00
parent 8d0d369067
commit 3a9079742f
8 changed files with 177 additions and 8 deletions

View File

@ -217,6 +217,13 @@ object isn't useful:
Detect all offsets in the code object *code* which are jump targets, and Detect all offsets in the code object *code* which are jump targets, and
return a list of these offsets. return a list of these offsets.
.. function:: stack_effect(opcode, [oparg])
Compute the stack effect of *opcode* with argument *oparg*.
.. versionadded:: 3.4
.. _bytecodes: .. _bytecodes:
Python Bytecode Instructions Python Bytecode Instructions

View File

@ -54,6 +54,9 @@ PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromASTObject(
/* _Py_Mangle is defined in compile.c */ /* _Py_Mangle is defined in compile.c */
PyAPI_FUNC(PyObject*) _Py_Mangle(PyObject *p, PyObject *name); PyAPI_FUNC(PyObject*) _Py_Mangle(PyObject *p, PyObject *name);
#define PY_INVALID_STACK_EFFECT INT_MAX
PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -8,6 +8,19 @@ __all__ = ["cmp_op", "hasconst", "hasname", "hasjrel", "hasjabs",
"haslocal", "hascompare", "hasfree", "opname", "opmap", "haslocal", "hascompare", "hasfree", "opname", "opmap",
"HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"] "HAVE_ARGUMENT", "EXTENDED_ARG", "hasnargs"]
# It's a chicken-and-egg I'm afraid:
# We're imported before _opcode's made.
# With exception unheeded
# (stack_effect is not needed)
# Both our chickens and eggs are allayed.
# --Larry Hastings, 2013/11/23
try:
from _opcode import stack_effect
__all__.append('stack_effect')
except ImportError:
pass
cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is', cmp_op = ('<', '<=', '==', '!=', '>', '>=', 'in', 'not in', 'is',
'is not', 'exception match', 'BAD') 'is not', 'exception match', 'BAD')

22
Lib/test/test__opcode.py Normal file
View File

@ -0,0 +1,22 @@
import dis
import _opcode
from test.support import run_unittest
import unittest
class OpcodeTests(unittest.TestCase):
def test_stack_effect(self):
self.assertEqual(_opcode.stack_effect(dis.opmap['POP_TOP']), -1)
self.assertEqual(_opcode.stack_effect(dis.opmap['DUP_TOP_TWO']), 2)
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 1), -1)
self.assertEqual(_opcode.stack_effect(dis.opmap['BUILD_SLICE'], 3), -2)
self.assertRaises(ValueError, _opcode.stack_effect, 30000)
self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['BUILD_SLICE'])
self.assertRaises(ValueError, _opcode.stack_effect, dis.opmap['POP_TOP'], 0)
def test_main():
run_unittest(OpcodeTests)
if __name__ == "__main__":
test_main()

View File

@ -67,6 +67,8 @@ Core and Builtins
Library Library
------- -------
- Issue #19722: Added opcode.stack_effect(), which
computes the stack effect of bytecode instructions.
- Issue #19735: Implement private function ssl._create_stdlib_context() to - Issue #19735: Implement private function ssl._create_stdlib_context() to
create SSLContext objects in Python's stdlib module. It provides a single create SSLContext objects in Python's stdlib module. It provides a single

116
Modules/_opcode.c Normal file
View File

@ -0,0 +1,116 @@
#include "Python.h"
#include "opcode.h"
/*[clinic]
module _opcode
_opcode.stack_effect -> int
opcode: int
[
oparg: int
]
/
Compute the stack effect of the opcode.
[clinic]*/
PyDoc_STRVAR(_opcode_stack_effect__doc__,
"Compute the stack effect of the opcode.\n"
"\n"
"_opcode.stack_effect(opcode, [oparg])");
#define _OPCODE_STACK_EFFECT_METHODDEF \
{"stack_effect", (PyCFunction)_opcode_stack_effect, METH_VARARGS, _opcode_stack_effect__doc__},
static int
_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg);
static PyObject *
_opcode_stack_effect(PyObject *module, PyObject *args)
{
PyObject *return_value = NULL;
int opcode;
int group_right_1 = 0;
int oparg = 0;
int _return_value;
switch (PyTuple_Size(args)) {
case 1:
if (!PyArg_ParseTuple(args, "i:stack_effect", &opcode))
return NULL;
break;
case 2:
if (!PyArg_ParseTuple(args, "ii:stack_effect", &opcode, &oparg))
return NULL;
group_right_1 = 1;
break;
default:
PyErr_SetString(PyExc_TypeError, "_opcode.stack_effect requires 1 to 2 arguments");
return NULL;
}
_return_value = _opcode_stack_effect_impl(module, opcode, group_right_1, oparg);
if ((_return_value == -1) && PyErr_Occurred())
goto exit;
return_value = PyLong_FromLong((long)_return_value);
exit:
return return_value;
}
static int
_opcode_stack_effect_impl(PyObject *module, int opcode, int group_right_1, int oparg)
/*[clinic checksum: 2312ded40abc9bcbce718942de21f53e61a2dfd3]*/
{
int effect;
if (HAS_ARG(opcode)) {
if (!group_right_1) {
PyErr_SetString(PyExc_ValueError,
"stack_effect: opcode requires oparg but oparg was not specified");
return -1;
}
}
else if (group_right_1) {
PyErr_SetString(PyExc_ValueError,
"stack_effect: opcode does not permit oparg but oparg was specified");
return -1;
}
effect = PyCompile_OpcodeStackEffect(opcode, oparg);
if (effect == PY_INVALID_STACK_EFFECT) {
PyErr_SetString(PyExc_ValueError,
"invalid opcode or oparg");
return -1;
}
return effect;
}
static PyMethodDef
opcode_functions[] = {
_OPCODE_STACK_EFFECT_METHODDEF
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef opcodemodule = {
PyModuleDef_HEAD_INIT,
"_opcode",
"Opcode support module.",
-1,
opcode_functions,
NULL,
NULL,
NULL,
NULL
};
PyMODINIT_FUNC
PyInit__opcode(void)
{
return PyModule_Create(&opcodemodule);
}

View File

@ -853,8 +853,8 @@ compiler_set_lineno(struct compiler *c, int off)
b->b_instr[off].i_lineno = c->u->u_lineno; b->b_instr[off].i_lineno = c->u->u_lineno;
} }
static int int
opcode_stack_effect(int opcode, int oparg) PyCompile_OpcodeStackEffect(int opcode, int oparg)
{ {
switch (opcode) { switch (opcode) {
case POP_TOP: case POP_TOP:
@ -1044,11 +1044,9 @@ opcode_stack_effect(int opcode, int oparg)
case DELETE_DEREF: case DELETE_DEREF:
return 0; return 0;
default: default:
fprintf(stderr, "opcode = %d\n", opcode); return PY_INVALID_STACK_EFFECT;
Py_FatalError("opcode_stack_effect()");
} }
return 0; /* not reachable */ return PY_INVALID_STACK_EFFECT; /* not reachable */
} }
/* Add an opcode with no argument. /* Add an opcode with no argument.
@ -3829,7 +3827,7 @@ dfs(struct compiler *c, basicblock *b, struct assembler *a)
static int static int
stackdepth_walk(struct compiler *c, basicblock *b, int depth, int maxdepth) stackdepth_walk(struct compiler *c, basicblock *b, int depth, int maxdepth)
{ {
int i, target_depth; int i, target_depth, effect;
struct instr *instr; struct instr *instr;
if (b->b_seen || b->b_startdepth >= depth) if (b->b_seen || b->b_startdepth >= depth)
return maxdepth; return maxdepth;
@ -3837,7 +3835,13 @@ stackdepth_walk(struct compiler *c, basicblock *b, int depth, int maxdepth)
b->b_startdepth = depth; b->b_startdepth = depth;
for (i = 0; i < b->b_iused; i++) { for (i = 0; i < b->b_iused; i++) {
instr = &b->b_instr[i]; instr = &b->b_instr[i];
depth += opcode_stack_effect(instr->i_opcode, instr->i_oparg); effect = PyCompile_OpcodeStackEffect(instr->i_opcode, instr->i_oparg);
if (effect == PY_INVALID_STACK_EFFECT) {
fprintf(stderr, "opcode = %d\n", instr->i_opcode);
Py_FatalError("PyCompile_OpcodeStackEffect()");
}
depth += effect;
if (depth > maxdepth) if (depth > maxdepth)
maxdepth = depth; maxdepth = depth;
assert(depth >= 0); /* invalid code or bug in stackdepth() */ assert(depth >= 0); /* invalid code or bug in stackdepth() */

View File

@ -596,6 +596,8 @@ class PyBuildExt(build_ext):
exts.append( Extension('_lsprof', ['_lsprof.c', 'rotatingtree.c']) ) exts.append( Extension('_lsprof', ['_lsprof.c', 'rotatingtree.c']) )
# static Unicode character database # static Unicode character database
exts.append( Extension('unicodedata', ['unicodedata.c']) ) exts.append( Extension('unicodedata', ['unicodedata.c']) )
# _opcode module
exts.append( Extension('_opcode', ['_opcode.c']) )
# Modules with some UNIX dependencies -- on by default: # Modules with some UNIX dependencies -- on by default:
# (If you have a really backward UNIX, select and socket may not be # (If you have a really backward UNIX, select and socket may not be