Skip to content
This repository was archived by the owner on Mar 2, 2022. It is now read-only.

Commit 32d3a70

Browse files
committed
Match stack depth calculation
Summary: 3.7 adds a new stack depth calculation algorithm which is more precise when accounting for jumps - it's now account for the depth depending on whether or not the jump is taken. It also moves to a non-recursive algorithm as well, which we match for making future changes easier. Test Plan: ./python -m test.test_compiler Test coverage for these specific changes from the the test_sbs_stdlib tests where we logout the stack depth size for all functions in the standard library.
1 parent 676717d commit 32d3a70

File tree

2 files changed

+119
-26
lines changed

2 files changed

+119
-26
lines changed

compiler/pyassem.py

Lines changed: 115 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import types
66
import sys
77

8-
from typing import Any
8+
from typing import Any, List
99
from compiler import misc
1010
from compiler.consts \
1111
import CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS
@@ -375,9 +375,8 @@ def dump(self, io=None):
375375
if io:
376376
sys.stdout = save
377377

378-
def stackdepth_walk(self, block, depth, maxdepth):
378+
def stackdepth_walk(self, block, depth=0, maxdepth=0):
379379
assert block is not None
380-
381380
if block.seen or block.startdepth >= depth:
382381
return maxdepth
383382
block.seen = True
@@ -428,7 +427,7 @@ def computeStackDepth(self):
428427
for block in self.getBlocksInOrder():
429428
# We need to get to the first block which actually has instructions
430429
if block.getInstructions():
431-
self.stacksize = self.stackdepth_walk(block, 0, 0)
430+
self.stacksize = self.stackdepth_walk(block)
432431
break
433432

434433
def flattenGraph(self):
@@ -664,6 +663,79 @@ def getConsts(self):
664663
return tuple(const[1] for const in self.consts)
665664

666665

666+
class PyFlowGraph37(PyFlowGraph):
667+
def push_block(self, worklist: List[Block], block: Block, depth: int):
668+
assert block.startdepth < 0 or block.startdepth >= depth, f"{block!r}: {block.startdepth} vs {depth}"
669+
if block.startdepth < depth:
670+
block.startdepth = depth
671+
worklist.append(block)
672+
673+
def stackdepth_walk(self, block):
674+
maxdepth = 0
675+
worklist = []
676+
self.push_block(worklist, block, 0)
677+
while worklist:
678+
block = worklist.pop()
679+
next = block.next
680+
depth = block.startdepth
681+
assert depth >= 0
682+
683+
for instr in block.getInstructions():
684+
if instr.opname == "SET_LINENO":
685+
continue
686+
687+
effect = STACK_EFFECTS_37.get(instr.opname)
688+
if effect is None:
689+
raise ValueError(f"Error, opcode {instr.opname} was not found, please update STACK_EFFECTS")
690+
if isinstance(effect, int):
691+
delta = effect
692+
else:
693+
delta = effect(instr.oparg, 0)
694+
695+
new_depth = depth + delta
696+
if new_depth > maxdepth:
697+
maxdepth = new_depth
698+
699+
assert depth >= 0
700+
701+
opcode = OPMAP[instr.opname]
702+
if opcode in dis.hasjabs or opcode in dis.hasjrel:
703+
if isinstance(effect, int):
704+
delta = effect
705+
else:
706+
delta = effect(instr.oparg, 1)
707+
708+
target_depth = depth + delta
709+
if target_depth > maxdepth:
710+
maxdepth = target_depth
711+
712+
assert target_depth >= 0
713+
if instr.opname == "CONTINUE_LOOP":
714+
# Pops a variable number of values from the stack,
715+
# but the target should be already proceeding.
716+
assert instr.target.startdepth >= 0
717+
assert instr.target.startdepth <= depth
718+
# remaining code is dead
719+
next = None
720+
break
721+
722+
self.push_block(worklist, instr.target, target_depth)
723+
724+
depth = new_depth
725+
726+
if instr.opname in ("JUMP_ABSOLUTE", "JUMP_FORWARD", "RETURN_VALUE", "RAISE_VARARGS", "BREAK_LOOP"):
727+
# Remaining code is dead
728+
next = None
729+
break
730+
731+
# TODO(dinoviehland): we could save the delta we came up with here and
732+
# reapply it on subsequent walks rather than having to walk all of the
733+
# instructions again.
734+
if next:
735+
self.push_block(worklist, next, depth)
736+
737+
return maxdepth
738+
667739
class LineAddrTable:
668740
"""lnotab
669741
@@ -808,8 +880,8 @@ def getTable(self):
808880

809881
STORE_NAME = -1,
810882
DELETE_NAME = 0,
811-
UNPACK_SEQUENCE = lambda oparg: oparg - 1,
812-
UNPACK_EX = lambda oparg: (oparg & 0xFF) + (oparg >> 8),
883+
UNPACK_SEQUENCE = lambda oparg, jmp = 0: oparg - 1,
884+
UNPACK_EX = lambda oparg, jmp = 0: (oparg & 0xFF) + (oparg >> 8),
813885
FOR_ITER = 1, # or -1, at end of iterator
814886

815887
STORE_ATTR = -2,
@@ -819,20 +891,20 @@ def getTable(self):
819891
LOAD_CONST = 1,
820892
LOAD_NAME = 1,
821893

822-
BUILD_TUPLE = lambda oparg: 1 - oparg,
823-
BUILD_LIST = lambda oparg: 1 - oparg,
824-
BUILD_SET = lambda oparg: 1 - oparg,
825-
BUILD_STRING = lambda oparg: 1 - oparg,
894+
BUILD_TUPLE = lambda oparg, jmp = 0: 1 - oparg,
895+
BUILD_LIST = lambda oparg, jmp = 0: 1 - oparg,
896+
BUILD_SET = lambda oparg, jmp = 0: 1 - oparg,
897+
BUILD_STRING = lambda oparg, jmp = 0: 1 - oparg,
826898

827-
BUILD_LIST_UNPACK = lambda oparg: 1 - oparg,
828-
BUILD_TUPLE_UNPACK = lambda oparg: 1 - oparg,
829-
BUILD_TUPLE_UNPACK_WITH_CALL = lambda oparg: 1 - oparg,
830-
BUILD_SET_UNPACK = lambda oparg: 1 - oparg,
831-
BUILD_MAP_UNPACK = lambda oparg: 1 - oparg,
832-
BUILD_MAP_UNPACK_WITH_CALL = lambda oparg: 1 - oparg,
899+
BUILD_LIST_UNPACK = lambda oparg, jmp = 0: 1 - oparg,
900+
BUILD_TUPLE_UNPACK = lambda oparg, jmp = 0: 1 - oparg,
901+
BUILD_TUPLE_UNPACK_WITH_CALL = lambda oparg, jmp = 0: 1 - oparg,
902+
BUILD_SET_UNPACK = lambda oparg, jmp = 0: 1 - oparg,
903+
BUILD_MAP_UNPACK = lambda oparg, jmp = 0: 1 - oparg,
904+
BUILD_MAP_UNPACK_WITH_CALL = lambda oparg, jmp = 0: 1 - oparg,
833905

834-
BUILD_MAP = lambda oparg: 1 - 2 * oparg,
835-
BUILD_CONST_KEY_MAP = lambda oparg: -oparg,
906+
BUILD_MAP = lambda oparg, jmp = 0: 1 - 2 * oparg,
907+
BUILD_CONST_KEY_MAP = lambda oparg, jmp = 0: -oparg,
836908
LOAD_ATTR = 0,
837909
COMPARE_OP = -1,
838910
IMPORT_NAME = -1,
@@ -860,12 +932,12 @@ def getTable(self):
860932
DELETE_FAST = 0,
861933
STORE_ANNOTATION = -1,
862934

863-
RAISE_VARARGS = lambda oparg: -oparg,
864-
CALL_FUNCTION = lambda oparg: -oparg,
865-
CALL_FUNCTION_KW = lambda oparg: -oparg - 1,
866-
CALL_FUNCTION_EX = lambda oparg: -1 - ((oparg & 0x01) != 0),
867-
MAKE_FUNCTION = lambda oparg: -1 - ((oparg & 0x01) != 0) - ((oparg & 0x02) != 0) - ((oparg & 0x04) != 0) - ((oparg & 0x08) != 0),
868-
BUILD_SLICE = lambda oparg: -2 if oparg == 3 else -1,
935+
RAISE_VARARGS = lambda oparg, jmp = 0: -oparg,
936+
CALL_FUNCTION = lambda oparg, jmp = 0: -oparg,
937+
CALL_FUNCTION_KW = lambda oparg, jmp = 0: -oparg - 1,
938+
CALL_FUNCTION_EX = lambda oparg, jmp = 0: -1 - ((oparg & 0x01) != 0),
939+
MAKE_FUNCTION = lambda oparg, jmp = 0: -1 - ((oparg & 0x01) != 0) - ((oparg & 0x02) != 0) - ((oparg & 0x04) != 0) - ((oparg & 0x08) != 0),
940+
BUILD_SLICE = lambda oparg, jmp = 0: -2 if oparg == 3 else -1,
869941

870942
LOAD_CLOSURE = 1,
871943
LOAD_DEREF = 1,
@@ -880,8 +952,25 @@ def getTable(self):
880952
GET_YIELD_FROM_ITER = 0,
881953
# If there's a fmt_spec on the stack, we go from 2->1,
882954
# else 1->1.
883-
FORMAT_VALUE = lambda oparg: -1 if (oparg & FVS_MASK) == FVS_HAVE_SPEC else 0,
955+
FORMAT_VALUE = lambda oparg, jmp = 0: -1 if (oparg & FVS_MASK) == FVS_HAVE_SPEC else 0,
884956
SET_LINENO = 0,
885957
LOAD_METHOD = 1,
886-
CALL_METHOD = lambda oparg: -oparg - 1,
958+
CALL_METHOD = lambda oparg, jmp = 0: -oparg - 1,
959+
)
960+
961+
STACK_EFFECTS_37 = dict(
962+
STACK_EFFECTS,
963+
SETUP_WITH = lambda oparg, jmp = 0: 6 if jmp else 1,
964+
WITH_CLEANUP_START = 2, # or 1, depending on TOS
965+
WITH_CLEANUP_FINISH = -3,
966+
POP_EXCEPT=-3,
967+
END_FINALLY=-6,
968+
FOR_ITER= lambda oparg, jmp = 0: -1 if jmp > 0 else 1,
969+
JUMP_IF_TRUE_OR_POP=lambda oparg, jmp = 0: 0 if jmp else -1,
970+
JUMP_IF_FALSE_OR_POP=lambda oparg, jmp = 0: 0 if jmp else -1,
971+
SETUP_EXCEPT=lambda oparg, jmp: 6 if jmp else 0,
972+
SETUP_FINALLY=lambda oparg, jmp: 6 if jmp else 0,
973+
CALL_METHOD=lambda oparg, jmp: -oparg-1,
974+
SETUP_ASYNC_WITH=lambda oparg, jmp: (-1 + 6) if jmp else 0,
975+
LOAD_METHOD=1,
887976
)

compiler/pycodegen.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,10 @@ def make_code_gen(cls, name, tree, filename):
19631963
walk(tree, code_gen)
19641964
return code_gen
19651965

1966+
@classmethod
1967+
def flow_graph(cls, name, filename, args=(), kwonlyargs=(), starargs=(), optimized=0, klass=None):
1968+
return pyassem.PyFlowGraph37(name, filename, args, kwonlyargs, starargs, optimized, klass)
1969+
19661970
def visitCall(self, node):
19671971
if (node.keywords or
19681972
not isinstance(node.func, ast.Attribute) or

0 commit comments

Comments
 (0)