From 7bdf28265aa371b39f82dfc6562635801aff15a5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 18 Sep 2018 09:54:26 +0300 Subject: [PATCH] bpo-32455: Add jump parameter to dis.stack_effect(). (GH-6610) Add C API function PyCompile_OpcodeStackEffectWithJump(). --- Doc/library/dis.rst | 11 +++- Include/compile.h | 1 + Lib/test/test__opcode.py | 61 ++++++++++++++----- .../2018-07-08-12-06-18.bpo-32455.KVHlkz.rst | 1 + .../2018-04-26-13-31-10.bpo-32455.KPWg3K.rst | 1 + Modules/_opcode.c | 24 +++++++- Modules/clinic/_opcode.c.h | 20 +++--- Python/compile.c | 6 ++ 8 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2018-07-08-12-06-18.bpo-32455.KVHlkz.rst create mode 100644 Misc/NEWS.d/next/Library/2018-04-26-13-31-10.bpo-32455.KPWg3K.rst diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 95649379ac6..fe9979db0ea 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -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 diff --git a/Include/compile.h b/Include/compile.h index edb961f4d72..2dacfff37f8 100644 --- a/Include/compile.h +++ b/Include/compile.h @@ -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); diff --git a/Lib/test/test__opcode.py b/Lib/test/test__opcode.py index 2af1ee35bff..0fb39eed606 100644 --- a/Lib/test/test__opcode.py +++ b/Lib/test/test__opcode.py @@ -3,32 +3,65 @@ from test.support import import_module 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__": diff --git a/Misc/NEWS.d/next/C API/2018-07-08-12-06-18.bpo-32455.KVHlkz.rst b/Misc/NEWS.d/next/C API/2018-07-08-12-06-18.bpo-32455.KVHlkz.rst new file mode 100644 index 00000000000..f28be876cf3 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2018-07-08-12-06-18.bpo-32455.KVHlkz.rst @@ -0,0 +1 @@ +Added :c:func:`PyCompile_OpcodeStackEffectWithJump`. diff --git a/Misc/NEWS.d/next/Library/2018-04-26-13-31-10.bpo-32455.KPWg3K.rst b/Misc/NEWS.d/next/Library/2018-04-26-13-31-10.bpo-32455.KPWg3K.rst new file mode 100644 index 00000000000..dd873f77bbd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-04-26-13-31-10.bpo-32455.KPWg3K.rst @@ -0,0 +1 @@ +Added *jump* parameter to :func:`dis.stack_effect`. diff --git a/Modules/_opcode.c b/Modules/_opcode.c index f9c1c0108d6..42a8732694a 100644 --- a/Modules/_opcode.c +++ b/Modules/_opcode.c @@ -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, @@ -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"); diff --git a/Modules/clinic/_opcode.c.h b/Modules/clinic/_opcode.c.h index 4d593edfac0..b162d84e5db 100644 --- a/Modules/clinic/_opcode.c.h +++ b/Modules/clinic/_opcode.c.h @@ -3,30 +3,34 @@ preserve [clinic start generated code]*/ PyDoc_STRVAR(_opcode_stack_effect__doc__, -"stack_effect($module, opcode, oparg=None, /)\n" +"stack_effect($module, opcode, oparg=None, /, *, jump=None)\n" "--\n" "\n" "Compute the stack effect of the opcode."); #define _OPCODE_STACK_EFFECT_METHODDEF \ - {"stack_effect", (PyCFunction)_opcode_stack_effect, METH_FASTCALL, _opcode_stack_effect__doc__}, + {"stack_effect", (PyCFunction)_opcode_stack_effect, METH_FASTCALL|METH_KEYWORDS, _opcode_stack_effect__doc__}, static int -_opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg); +_opcode_stack_effect_impl(PyObject *module, int opcode, PyObject *oparg, + PyObject *jump); static PyObject * -_opcode_stack_effect(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +_opcode_stack_effect(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + static const char * const _keywords[] = {"", "", "jump", NULL}; + static _PyArg_Parser _parser = {"i|O$O:stack_effect", _keywords, 0}; int opcode; PyObject *oparg = Py_None; + PyObject *jump = Py_None; int _return_value; - if (!_PyArg_ParseStack(args, nargs, "i|O:stack_effect", - &opcode, &oparg)) { + if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, + &opcode, &oparg, &jump)) { goto exit; } - _return_value = _opcode_stack_effect_impl(module, opcode, oparg); + _return_value = _opcode_stack_effect_impl(module, opcode, oparg, jump); if ((_return_value == -1) && PyErr_Occurred()) { goto exit; } @@ -35,4 +39,4 @@ _opcode_stack_effect(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=577a91c9aa5559a9 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=bbf6c4cfc91edc29 input=a9049054013a1b77]*/ diff --git a/Python/compile.c b/Python/compile.c index ebd73fb7231..707da79ab66 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -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) {