diff -r 4efe9c674b04 Lib/compiler/pyassem.py --- a/Lib/compiler/pyassem.py Mon Mar 24 10:34:34 2008 +0100 +++ b/Lib/compiler/pyassem.py Mon Mar 24 13:11:17 2008 +0100 @@ -42,10 +42,8 @@ class FlowGraph: # 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(). + # transfer to the block graph. These edges are removed later by + # calling pruneEdges() (see getBlocksInOrder()). self.current.addNext(block) self.startBlock(block) @@ -69,10 +67,12 @@ 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]) + # Record block emission order dependencies for relative jumps + # (which can only have positive offsets). + if self.hasjrel.has_elt(inst[0]): + self.current.followers.add(inst[1]) self.current.emit(inst) def getBlocksInOrder(self): @@ -80,117 +80,11 @@ class FlowGraph: i.e. each node appears before all of its successors """ - # XXX make sure every node that doesn't have an explicit next - # is set so that next points to exit + # Remove false edges. 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) - + b.pruneNext() + order = order_blocks(self.entry, 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() @@ -205,16 +99,60 @@ class FlowGraph: l.extend(b.getContainedGraphs()) return l -def dfs_postorder(b, seen): - """Depth-first search of tree rooted at b, return in postorder""" + +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 order = [] - seen[b] = b - for c in b.get_children(): - if seen.has_key(c): - continue - order = order + dfs_postorder(c, seen) - order.append(b) + + reachable = set() + blocks = [start_block] + while blocks: + reachable.update(blocks) + blocks = [c for b in blocks for c in b.get_children() + if not c in reachable] + + remaining = set(reachable) + + def fill_todo(): + todo = set(remaining) + if not todo: + return todo + for b in remaining: + # Followers must be processed after their preceders + for c in b.get_followers(): + while 1: + todo.discard(c) + # We must also discard any other block whose next block + # is the block we just discarded! + if c.prev and c.prev[0] is not b: + c = c.prev[0] + else: + break + # If todo is empty, then we have a circular dependency! + assert todo + return todo + + def fill_order(b): + remaining.discard(b) + order.append(b) + if b.next: + fill_order(b.next[0]) + # Append the default exit block if necessary + elif b is not exit_block and not b.has_unconditional_transfer(): + order.append(exit_block) + + fill_order(start_block) + while remaining: + todo = fill_todo() + for b in todo: + fill_order(b) return order + class Block: _count = 0 @@ -222,10 +160,18 @@ class Block: def __init__(self, label=''): self.insts = [] self.inEdges = misc.Set() - self.outEdges = misc.Set() + self.outEdges = set() self.label = label self.bid = Block._count self.next = [] + self.prev = [] + # Blocks that must be emitted *after* this one, because of + # bytecode offsets (e.g. relative jumps) pointing to them. + # NOTE: this might also include the next block if it is explicitly + # referenced by an instruction inside the block. Yet, the next block + # is treated separately from self.followers so that pruneNext() + # can work properly. + self.followers = set() Block._count = Block._count + 1 def __repr__(self): @@ -241,8 +187,6 @@ 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): @@ -254,12 +198,26 @@ class Block: def addOutEdge(self, block): self.outEdges.add(block) - def addNext(self, block): + def addNext(self, block, add_prev=True): self.next.append(block) assert len(self.next) == 1, map(str, self.next) + if add_prev: + block.prev.append(self) + assert len(block.prev) == 1, map(str, block.prev) - _uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS', 'YIELD_VALUE', - 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP') + _uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS', + '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.""" + try: + op, arg = self.insts[-1] + except (IndexError, ValueError): + return + return op in self._uncond_transfer def pruneNext(self): """Remove bogus edge for unconditional transfers @@ -275,17 +233,17 @@ class Block: remove the next edge when it follows an unconditional control transfer. """ - try: - op, arg = self.insts[-1] - except (IndexError, ValueError): - return - if op in self._uncond_transfer: + if self.next and self.has_unconditional_transfer(): + self.outEdges.add(self.next[0]) + self.next[0].prev = [] self.next = [] def get_children(self): - if self.next and self.next[0] in self.outEdges: - self.outEdges.remove(self.next[0]) - return self.outEdges.elements() + self.next + return list(self.outEdges) + self.next + + def get_followers(self): + """Get the whole list of followers, including the next block.""" + return self.followers | set(self.next) def getContainedGraphs(self): """Return all graphs contained within this block. diff -r 4efe9c674b04 Lib/compiler/pycodegen.py --- a/Lib/compiler/pycodegen.py Mon Mar 24 10:34:34 2008 +0100 +++ b/Lib/compiler/pycodegen.py Mon Mar 24 13:11:17 2008 +0100 @@ -681,7 +681,7 @@ class CodeGenerator: self.startBlock(anchor) self.emit('POP_BLOCK') self.setups.pop() - self.startBlock(end) + self.nextBlock(end) self.emit('LOAD_CONST', None)