Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,21 @@ operation is being performed, so the intermediate analysis object isn't useful:
return a list of these offsets.


.. function:: stack_effect(opcode, [oparg])
.. function:: stack_effect(opcode, oparg=None, *, jump=None)

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

If the code has a jump target and *jump* is ``True``, :func:`~stack_effect`
will return the stack effect of jumping. If *jump* is ``False``,
it will return the stack effect of not jumping. And if *jump* is
``None`` (default), it will return the maximal stack effect of both cases.

.. versionadded:: 3.4

.. versionchanged:: 3.8
Added *jump* parameter.


.. _bytecodes:

Python Bytecode Instructions
Expand Down
1 change: 1 addition & 0 deletions Include/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ 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);
PyAPI_FUNC(int) PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump);

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

Expand Down
61 changes: 47 additions & 14 deletions Lib/test/test__opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,65 @@
import unittest

_opcode = import_module("_opcode")
from _opcode import stack_effect


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)
self.assertEqual(stack_effect(dis.opmap['POP_TOP']), -1)
self.assertEqual(stack_effect(dis.opmap['DUP_TOP_TWO']), 2)
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 0), -1)
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 1), -1)
self.assertEqual(stack_effect(dis.opmap['BUILD_SLICE'], 3), -2)
self.assertRaises(ValueError, stack_effect, 30000)
self.assertRaises(ValueError, stack_effect, dis.opmap['BUILD_SLICE'])
self.assertRaises(ValueError, stack_effect, dis.opmap['POP_TOP'], 0)
# All defined opcodes
for name, code in dis.opmap.items():
with self.subTest(opname=name):
if code < dis.HAVE_ARGUMENT:
_opcode.stack_effect(code)
self.assertRaises(ValueError, _opcode.stack_effect, code, 0)
stack_effect(code)
self.assertRaises(ValueError, stack_effect, code, 0)
else:
_opcode.stack_effect(code, 0)
self.assertRaises(ValueError, _opcode.stack_effect, code)
stack_effect(code, 0)
self.assertRaises(ValueError, stack_effect, code)
# All not defined opcodes
for code in set(range(256)) - set(dis.opmap.values()):
with self.subTest(opcode=code):
self.assertRaises(ValueError, _opcode.stack_effect, code)
self.assertRaises(ValueError, _opcode.stack_effect, code, 0)
self.assertRaises(ValueError, stack_effect, code)
self.assertRaises(ValueError, stack_effect, code, 0)

def test_stack_effect_jump(self):
JUMP_IF_TRUE_OR_POP = dis.opmap['JUMP_IF_TRUE_OR_POP']
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0), 0)
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0, jump=True), 0)
self.assertEqual(stack_effect(JUMP_IF_TRUE_OR_POP, 0, jump=False), -1)
FOR_ITER = dis.opmap['FOR_ITER']
self.assertEqual(stack_effect(FOR_ITER, 0), 1)
self.assertEqual(stack_effect(FOR_ITER, 0, jump=True), -1)
self.assertEqual(stack_effect(FOR_ITER, 0, jump=False), 1)
JUMP_FORWARD = dis.opmap['JUMP_FORWARD']
self.assertEqual(stack_effect(JUMP_FORWARD, 0), 0)
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=True), 0)
self.assertEqual(stack_effect(JUMP_FORWARD, 0, jump=False), 0)
# All defined opcodes
has_jump = dis.hasjabs + dis.hasjrel
for name, code in dis.opmap.items():
with self.subTest(opname=name):
if code < dis.HAVE_ARGUMENT:
common = stack_effect(code)
jump = stack_effect(code, jump=True)
nojump = stack_effect(code, jump=False)
else:
common = stack_effect(code, 0)
jump = stack_effect(code, 0, jump=True)
nojump = stack_effect(code, 0, jump=False)
if code in has_jump:
self.assertEqual(common, max(jump, nojump))
else:
self.assertEqual(jump, common)
self.assertEqual(nojump, common)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added :c:func:`PyCompile_OpcodeStackEffectWithJump`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added *jump* parameter to :func:`dis.stack_effect`.
24 changes: 21 additions & 3 deletions Modules/_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ _opcode.stack_effect -> int
opcode: int
oparg: object = None
/
*
jump: object = None

Compute the stack effect of the opcode.
[clinic start generated code]*/

static int
_opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg)
/*[clinic end generated code: output=ad39467fa3ad22ce input=2d0a9ee53c0418f5]*/
_opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg,
PyObject *jump)
/*[clinic end generated code: output=64a18f2ead954dbb input=461c9d4a44851898]*/
{
int effect;
int oparg_int = 0;
int jump_int;
if (HAS_ARG(opcode)) {
if (oparg == Py_None) {
PyErr_SetString(PyExc_ValueError,
Expand All @@ -40,7 +44,21 @@ _opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg)
"stack_effect: opcode does not permit oparg but oparg was specified");
return -1;
}
effect = PyCompile_OpcodeStackEffect(opcode, oparg_int);
if (jump == Py_None) {
jump_int = -1;
}
else if (jump == Py_True) {
jump_int = 1;
}
else if (jump == Py_False) {
jump_int = 0;
}
else {
PyErr_SetString(PyExc_ValueError,
"stack_effect: jump must be False, True or None");
return -1;
}
effect = PyCompile_OpcodeStackEffectWithJump(opcode, oparg_int, jump_int);
if (effect == PY_INVALID_STACK_EFFECT) {
PyErr_SetString(PyExc_ValueError,
"invalid opcode or oparg");
Expand Down
20 changes: 12 additions & 8 deletions Modules/clinic/_opcode.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,12 @@ stack_effect(int opcode, int oparg, int jump)
return PY_INVALID_STACK_EFFECT; /* not reachable */
}

int
PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump)
{
return stack_effect(opcode, oparg, jump);
}

int
PyCompile_OpcodeStackEffect(int opcode, int oparg)
{
Expand Down