Skip to content

Commit a7cc7a5

Browse files
Merge pull request MatthieuDartiailh#29 from SnoopyLane/py37
updates for handling Python3.7
2 parents 0c0f371 + a867d51 commit a7cc7a5

6 files changed

Lines changed: 88 additions & 34 deletions

File tree

bytecode/cfg.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,40 +53,34 @@ def _compute_stack_size(block, size, maxsize):
5353
if block.seen or block.startsize >= size:
5454
return maxsize
5555

56+
def update_size(delta, size, maxsize):
57+
size += delta
58+
if size < 0:
59+
msg = 'Failed to compute stacksize, got negative size'
60+
raise RuntimeError(msg)
61+
maxsize = max(maxsize, size)
62+
return size, maxsize
63+
5664
block.seen = True
5765
block.startsize = size
5866

5967
for instr in block:
60-
6168
if isinstance(instr, SetLineno):
6269
continue
6370

64-
size += instr.stack_effect
65-
maxsize = max(maxsize, size)
66-
67-
if size < 0:
68-
msg = 'Failed to compute stacksize, got negative size'
69-
raise RuntimeError(msg)
70-
7171
if instr.has_jump():
72-
target_size = size
73-
74-
if instr.name == 'FOR_ITER':
75-
target_size = size - 2
76-
77-
elif instr.name in {'SETUP_FINALLY', 'SETUP_EXCEPT'}:
78-
target_size = size + 3
79-
maxsize = max(target_size, maxsize)
80-
81-
elif instr.name.startswith('JUMP_IF'):
82-
size -= 1
83-
84-
maxsize = _compute_stack_size(instr.arg, target_size, maxsize)
72+
# first compute the taken-jump path
73+
taken_size, maxsize = update_size(instr.stack_effect(jump=True),
74+
size, maxsize)
75+
maxsize = _compute_stack_size(instr.arg, taken_size, maxsize)
8576

8677
if instr.is_uncond_jump():
8778
block.seen = False
8879
return maxsize
8980

81+
# jump=False: non-taken path of jumps, or any non-jump
82+
size, maxsize = update_size(instr.stack_effect(jump=False),
83+
size, maxsize)
9084
if block.next_block:
9185
maxsize = _compute_stack_size(block.next_block, size, maxsize)
9286

bytecode/instr.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import dis
33
import math
44
import opcode as _opcode
5+
import sys
56
import types
67

78
import bytecode as _bytecode
@@ -144,6 +145,33 @@ def _check_arg_int(name, arg):
144145
% name)
145146

146147

148+
_stack_effects = {
149+
# NOTE: the entries are all 2-tuples. Entry[0/False] is non-taken jumps.
150+
# Entry[1/True] is for taken jumps.
151+
152+
# opcodes not in dis.stack_effect
153+
_opcode.opmap['EXTENDED_ARG']: (0, 0),
154+
_opcode.opmap['NOP']: (0, 0),
155+
156+
# Jump taken/not-taken are different:
157+
_opcode.opmap['JUMP_IF_TRUE_OR_POP']: (-1, 0),
158+
_opcode.opmap['JUMP_IF_FALSE_OR_POP']: (-1, 0),
159+
_opcode.opmap['FOR_ITER']: (1, -1),
160+
_opcode.opmap['SETUP_WITH']: (1, 6),
161+
_opcode.opmap['SETUP_ASYNC_WITH']: (0, 5),
162+
_opcode.opmap['SETUP_EXCEPT']: (0, 6), # as of 3.7, below for <=3.6
163+
_opcode.opmap['SETUP_FINALLY']: (0, 6), # as of 3.7, below for <=3.6
164+
}
165+
166+
# More stack effect values that are unique to the version of Python.
167+
if sys.version_info < (3, 7):
168+
_stack_effects.update({
169+
_opcode.opmap['SETUP_WITH']: (7, 7),
170+
_opcode.opmap['SETUP_EXCEPT']: (6, 9),
171+
_opcode.opmap['SETUP_FINALLY']: (6, 9),
172+
})
173+
174+
147175
class Instr:
148176
"""Abstract instruction."""
149177

@@ -269,15 +297,19 @@ def lineno(self):
269297
def lineno(self, lineno):
270298
self._set(self._name, self._arg, lineno)
271299

272-
@property
273-
def stack_effect(self):
300+
def stack_effect(self, jump=None):
301+
effect = _stack_effects.get(self._opcode, None)
302+
if effect is not None:
303+
return max(effect) if jump is None else effect[jump]
304+
305+
# TODO: if dis.stack_effect ever expands to take the 'jump' parameter
306+
# then we should pass that through, and perhaps remove some of the
307+
# overrides that are set up in _init_stack_effects()
308+
274309
# All opcodes whose arguments are not represented by integers have
275310
# a stack_effect indepent of their argument.
276311
arg = (self._arg if isinstance(self._arg, int) else
277312
0 if self._opcode >= _opcode.HAVE_ARGUMENT else None)
278-
# EXTENDED_ARG has no stack effect but is not supported by dis
279-
if self._name == 'EXTENDED_ARG':
280-
return 0
281313
return dis.stack_effect(self._opcode, arg)
282314

283315
def copy(self):

bytecode/tests/test_cfg.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,6 @@ def test(arg1, *args, **kwargs): # pragma: no cover
521521
with open(arg1) as f:
522522
return f.read()
523523

524-
import dis
525-
dis.dis(test.__code__)
526524
self.check_stack_size(test)
527525

528526
def test_stack_size_computation_try_except(self):

bytecode/tests/test_instr.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,27 @@ def test_const_key_equal(self):
191191
self.assertNotEqual(Instr('LOAD_CONST', frozenset({0})),
192192
Instr('LOAD_CONST', frozenset({0.0})))
193193

194+
def test_stack_effects(self):
195+
# Verify all opcodes are handled and that "jump=None" really returns
196+
# the max of the other cases.
197+
from bytecode.concrete import ConcreteInstr
198+
for name, op in opcode.opmap.items():
199+
# Use ConcreteInstr instead of Instr because it doesn't care what
200+
# kind of argument it is constructed with.
201+
if op < opcode.HAVE_ARGUMENT:
202+
instr = ConcreteInstr(name)
203+
else:
204+
instr = ConcreteInstr(name, 0)
205+
jump = instr.stack_effect(jump=True)
206+
no_jump = instr.stack_effect(jump=False)
207+
max_effect = instr.stack_effect(jump=None)
208+
msg = "op=%s" % name
209+
self.assertEqual(instr.stack_effect(), max_effect, msg)
210+
self.assertEqual(max_effect, max(jump, no_jump), msg)
211+
212+
if not instr.has_jump():
213+
self.assertEqual(jump, no_jump, msg)
214+
194215

195216
if __name__ == "__main__":
196217
unittest.main()

doc/api.rst

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,6 @@ Instr
9393
Operation code (``int``). Setting the operation code updates the
9494
:attr:`name` attribute.
9595

96-
.. attribute:: stack_effect
97-
98-
Operation effect on the stack size as computed by
99-
:func:`dis.stack_effect`.
100-
10196
.. versionchanged:: 0.3
10297
The ``op`` attribute was renamed to :attr:`opcode`.
10398

@@ -158,6 +153,20 @@ Instr
158153
.. versionchanged:: 0.3
159154
The *lineno* parameter has been removed.
160155

156+
.. method:: stack_effect(jump: bool = None) -> int
157+
158+
Operation effect on the stack size as computed by
159+
:func:`dis.stack_effect`.
160+
161+
The *jump* argument takes one of three values. None (the default)
162+
requests the largest stack effect. This works fine with most
163+
instructions. True returns the stack effect for taken branches. False
164+
returns the stack effect for non-taken branches.
165+
166+
.. versionchanged:: 0.8
167+
``stack_method`` was changed from a property to a method in order to
168+
add the keyword argument *jump*.
169+
161170

162171
ConcreteInstr
163172
-------------

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py3, py35, py36, pep8
2+
envlist = py3, py35, py36, py37, pep8
33

44
[testenv]
55
passenv = TOXENV CI TRAVIS TRAVIS_*

0 commit comments

Comments
 (0)