From af98230024f639d4c719b55d0d330912c3ea7b97 Mon Sep 17 00:00:00 2001 From: Arnaud Fontaine Date: Thu, 11 Dec 2014 23:48:22 +0900 Subject: [PATCH] Revert https://hg.python.org/cpython/raw-rev/42faa8054c3d. This fixes http://bugs.python.org/issue20613. --- Lib/compiler/pyassem.py | 257 ++++++++++++++++++++++++++++------------------ Lib/compiler/pycodegen.py | 2 +- 2 files changed, 159 insertions(+), 100 deletions(-) diff --git a/Lib/compiler/pyassem.py b/Lib/compiler/pyassem.py index f52f7d0..4299a17 100644 --- a/Lib/compiler/pyassem.py +++ b/Lib/compiler/pyassem.py @@ -21,7 +21,6 @@ class FlowGraph: if self.current: print "end", repr(self.current) print " next", self.current.next - print " prev", self.current.prev print " ", self.current.get_children() print repr(block) self.current = block @@ -41,12 +40,13 @@ class FlowGraph: if block is None: block = self.newBlock() - # Note: If the current block ends with an unconditional control - # transfer, then it is techically incorrect to add an implicit - # transfer to the block graph. Doing so results in code generation - # for unreachable blocks. That doesn't appear to be very common - # with Python code and since the built-in compiler doesn't optimize - # it out we don't either. + # Note: If the current block ends with an unconditional + # control transfer, then it is incorrect to add an implicit + # transfer to the block graph. The current code requires + # these edges to get the blocks emitted in the right order, + # however. :-( If a client needs to remove these edges, call + # pruneEdges(). + self.current.addNext(block) self.startBlock(block) @@ -69,6 +69,8 @@ class FlowGraph: def emit(self, *inst): if self._debug: print "\t", inst + if inst[0] in ['RETURN_VALUE', 'YIELD_VALUE']: + self.current.addOutEdge(self.exit) if len(inst) == 2 and isinstance(inst[1], Block): self.current.addOutEdge(inst[1]) self.current.emit(inst) @@ -78,9 +80,118 @@ class FlowGraph: i.e. each node appears before all of its successors """ - order = order_blocks(self.entry, self.exit) + # XXX make sure every node that doesn't have an explicit next + # is set so that next points to exit + for b in self.blocks.elements(): + if b is self.exit: + continue + if not b.next: + b.addNext(self.exit) + order = dfs_postorder(self.entry, {}) + order.reverse() + self.fixupOrder(order, self.exit) + # hack alert + if not self.exit in order: + order.append(self.exit) + return order + def fixupOrder(self, blocks, default_next): + """Fixup bad order introduced by DFS.""" + + # XXX This is a total mess. There must be a better way to get + # the code blocks in the right order. + + self.fixupOrderHonorNext(blocks, default_next) + self.fixupOrderForward(blocks, default_next) + + def fixupOrderHonorNext(self, blocks, default_next): + """Fix one problem with DFS. + + The DFS uses child block, but doesn't know about the special + "next" block. As a result, the DFS can order blocks so that a + block isn't next to the right block for implicit control + transfers. + """ + index = {} + for i in range(len(blocks)): + index[blocks[i]] = i + + for i in range(0, len(blocks) - 1): + b = blocks[i] + n = blocks[i + 1] + if not b.next or b.next[0] == default_next or b.next[0] == n: + continue + # The blocks are in the wrong order. Find the chain of + # blocks to insert where they belong. + cur = b + chain = [] + elt = cur + while elt.next and elt.next[0] != default_next: + chain.append(elt.next[0]) + elt = elt.next[0] + # Now remove the blocks in the chain from the current + # block list, so that they can be re-inserted. + l = [] + for b in chain: + assert index[b] > i + l.append((index[b], b)) + l.sort() + l.reverse() + for j, b in l: + del blocks[index[b]] + # Insert the chain in the proper location + blocks[i:i + 1] = [cur] + chain + # Finally, re-compute the block indexes + for i in range(len(blocks)): + index[blocks[i]] = i + + def fixupOrderForward(self, blocks, default_next): + """Make sure all JUMP_FORWARDs jump forward""" + index = {} + chains = [] + cur = [] + for b in blocks: + index[b] = len(chains) + cur.append(b) + if b.next and b.next[0] == default_next: + chains.append(cur) + cur = [] + chains.append(cur) + + while 1: + constraints = [] + + for i in range(len(chains)): + l = chains[i] + for b in l: + for c in b.get_children(): + if index[c] < i: + forward_p = 0 + for inst in b.insts: + if inst[0] == 'JUMP_FORWARD': + if inst[1] == c: + forward_p = 1 + if not forward_p: + continue + constraints.append((index[c], i)) + + if not constraints: + break + + # XXX just do one for now + # do swaps to get things in the right order + goes_before, a_chain = constraints[0] + assert a_chain > goes_before + c = chains[a_chain] + chains.remove(c) + chains.insert(goes_before, c) + + del blocks[:] + for c in chains: + for b in c: + blocks.append(b) + def getBlocks(self): return self.blocks.elements() @@ -94,84 +205,27 @@ class FlowGraph: l.extend(b.getContainedGraphs()) return l - -def order_blocks(start_block, exit_block): - """Order blocks so that they are emitted in the right order""" - # Rules: - # - when a block has a next block, the next block must be emitted just after - # - when a block has followers (relative jumps), it must be emitted before - # them - # - all reachable blocks must be emitted +def dfs_postorder(b, seen): + """Depth-first search of tree rooted at b, return in postorder""" order = [] - - # Find all the blocks to be emitted. - remaining = set() - todo = [start_block] - while todo: - b = todo.pop() - if b in remaining: - continue - remaining.add(b) - for c in b.get_children(): - if c not in remaining: - todo.append(c) - - # A block is dominated by another block if that block must be emitted - # before it. - dominators = {} - for b in remaining: - if __debug__ and b.next: - assert b is b.next[0].prev[0], (b, b.next) - # Make sure every block appears in dominators, even if no - # other block must precede it. - dominators.setdefault(b, set()) - # preceding blocks dominate following blocks - for c in b.get_followers(): - while 1: - dominators.setdefault(c, set()).add(b) - # Any block that has a next pointer leading to c is also - # dominated because the whole chain will be emitted at once. - # Walk backwards and add them all. - if c.prev and c.prev[0] is not b: - c = c.prev[0] - else: - break - - def find_next(): - # Find a block that can be emitted next. - for b in remaining: - for c in dominators[b]: - if c in remaining: - break # can't emit yet, dominated by a remaining block - else: - return b - assert 0, 'circular dependency, cannot find next block' - - b = start_block - while 1: - order.append(b) - remaining.discard(b) - if b.next: - b = b.next[0] + seen[b] = b + for c in b.get_children(): + if c in seen: continue - elif b is not exit_block and not b.has_unconditional_transfer(): - order.append(exit_block) - if not remaining: - break - b = find_next() + order = order + dfs_postorder(c, seen) + order.append(b) return order - class Block: _count = 0 def __init__(self, label=''): self.insts = [] - self.outEdges = set() + self.inEdges = misc.Set() + self.outEdges = misc.Set() self.label = label self.bid = Block._count self.next = [] - self.prev = [] Block._count = Block._count + 1 def __repr__(self): @@ -187,46 +241,51 @@ class Block: def emit(self, inst): op = inst[0] + if op[:4] == 'JUMP': + self.outEdges.add(inst[1]) self.insts.append(inst) def getInstructions(self): return self.insts + def addInEdge(self, block): + self.inEdges.add(block) + def addOutEdge(self, block): self.outEdges.add(block) def addNext(self, block): self.next.append(block) assert len(self.next) == 1, map(str, self.next) - block.prev.append(self) - assert len(block.prev) == 1, map(str, block.prev) - _uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS', - 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP', - ) + _uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS', 'YIELD_VALUE', + 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP') - def has_unconditional_transfer(self): - """Returns True if there is an unconditional transfer to an other block - at the end of this block. This means there is no risk for the bytecode - executer to go past this block's bytecode.""" + def pruneNext(self): + """Remove bogus edge for unconditional transfers + + Each block has a next edge that accounts for implicit control + transfers, e.g. from a JUMP_IF_FALSE to the block that will be + executed if the test is true. + + These edges must remain for the current assembler code to + work. If they are removed, the dfs_postorder gets things in + weird orders. However, they shouldn't be there for other + purposes, e.g. conversion to SSA form. This method will + remove the next edge when it follows an unconditional control + transfer. + """ try: op, arg = self.insts[-1] except (IndexError, ValueError): return - return op in self._uncond_transfer + if op in self._uncond_transfer: + self.next = [] def get_children(self): - return list(self.outEdges) + self.next - - def get_followers(self): - """Get the whole list of followers, including the next block.""" - followers = set(self.next) - # Blocks that must be emitted *after* this one, because of - # bytecode offsets (e.g. relative jumps) pointing to them. - for inst in self.insts: - if inst[0] in PyFlowGraph.hasjrel: - followers.add(inst[1]) - return followers + if self.next and self.next[0] in self.outEdges: + self.outEdges.remove(self.next[0]) + return self.outEdges.elements() + self.next def getContainedGraphs(self): """Return all graphs contained within this block. @@ -387,18 +446,18 @@ class PyFlowGraph(FlowGraph): elif inst[0] != "SET_LINENO": pc = pc + 3 opname = inst[0] - if opname in self.hasjrel: + if self.hasjrel.has_elt(opname): oparg = inst[1] offset = begin[oparg] - pc insts[i] = opname, offset - elif opname in self.hasjabs: + elif self.hasjabs.has_elt(opname): insts[i] = opname, begin[inst[1]] self.stage = FLAT - hasjrel = set() + hasjrel = misc.Set() for i in dis.hasjrel: hasjrel.add(dis.opname[i]) - hasjabs = set() + hasjabs = misc.Set() for i in dis.hasjabs: hasjabs.add(dis.opname[i]) diff --git a/Lib/compiler/pycodegen.py b/Lib/compiler/pycodegen.py index 6515945..3f641ba 100644 --- a/Lib/compiler/pycodegen.py +++ b/Lib/compiler/pycodegen.py @@ -706,7 +706,7 @@ class CodeGenerator: self.startBlock(anchor) self.emit('POP_BLOCK') self.setups.pop() - self.nextBlock(end) + self.startBlock(end) self.emit('LOAD_CONST', None) -- 2.2.0.rc0.207.ga3a616c