Skip to content

Commit 704fab5

Browse files
author
neil.schemenauer
committed
Overhaul Lib/compiler block ordering. The previous code was filled with
hacks. The new code is based on issue #2472 posted by Antoine Pitrou. I did some further cleanups of the pyassem code and optimized the block ordering pass. git-svn-id: http://svn.python.org/projects/python/trunk@69373 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 5e25ce1 commit 704fab5

2 files changed

Lines changed: 97 additions & 159 deletions

File tree

Lib/compiler/pyassem.py

Lines changed: 96 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def startBlock(self, block):
2121
if self.current:
2222
print "end", repr(self.current)
2323
print " next", self.current.next
24+
print " prev", self.current.prev
2425
print " ", self.current.get_children()
2526
print repr(block)
2627
self.current = block
@@ -40,13 +41,12 @@ def nextBlock(self, block=None):
4041
if block is None:
4142
block = self.newBlock()
4243

43-
# Note: If the current block ends with an unconditional
44-
# control transfer, then it is incorrect to add an implicit
45-
# transfer to the block graph. The current code requires
46-
# these edges to get the blocks emitted in the right order,
47-
# however. :-( If a client needs to remove these edges, call
48-
# pruneEdges().
49-
44+
# Note: If the current block ends with an unconditional control
45+
# transfer, then it is techically incorrect to add an implicit
46+
# transfer to the block graph. Doing so results in code generation
47+
# for unreachable blocks. That doesn't appear to be very common
48+
# with Python code and since the built-in compiler doesn't optimize
49+
# it out we don't either.
5050
self.current.addNext(block)
5151
self.startBlock(block)
5252

@@ -69,8 +69,6 @@ def _disable_debug(self):
6969
def emit(self, *inst):
7070
if self._debug:
7171
print "\t", inst
72-
if inst[0] in ['RETURN_VALUE', 'YIELD_VALUE']:
73-
self.current.addOutEdge(self.exit)
7472
if len(inst) == 2 and isinstance(inst[1], Block):
7573
self.current.addOutEdge(inst[1])
7674
self.current.emit(inst)
@@ -80,118 +78,9 @@ def getBlocksInOrder(self):
8078
8179
i.e. each node appears before all of its successors
8280
"""
83-
# XXX make sure every node that doesn't have an explicit next
84-
# is set so that next points to exit
85-
for b in self.blocks.elements():
86-
if b is self.exit:
87-
continue
88-
if not b.next:
89-
b.addNext(self.exit)
90-
order = dfs_postorder(self.entry, {})
91-
order.reverse()
92-
self.fixupOrder(order, self.exit)
93-
# hack alert
94-
if not self.exit in order:
95-
order.append(self.exit)
96-
81+
order = order_blocks(self.entry, self.exit)
9782
return order
9883

99-
def fixupOrder(self, blocks, default_next):
100-
"""Fixup bad order introduced by DFS."""
101-
102-
# XXX This is a total mess. There must be a better way to get
103-
# the code blocks in the right order.
104-
105-
self.fixupOrderHonorNext(blocks, default_next)
106-
self.fixupOrderForward(blocks, default_next)
107-
108-
def fixupOrderHonorNext(self, blocks, default_next):
109-
"""Fix one problem with DFS.
110-
111-
The DFS uses child block, but doesn't know about the special
112-
"next" block. As a result, the DFS can order blocks so that a
113-
block isn't next to the right block for implicit control
114-
transfers.
115-
"""
116-
index = {}
117-
for i in range(len(blocks)):
118-
index[blocks[i]] = i
119-
120-
for i in range(0, len(blocks) - 1):
121-
b = blocks[i]
122-
n = blocks[i + 1]
123-
if not b.next or b.next[0] == default_next or b.next[0] == n:
124-
continue
125-
# The blocks are in the wrong order. Find the chain of
126-
# blocks to insert where they belong.
127-
cur = b
128-
chain = []
129-
elt = cur
130-
while elt.next and elt.next[0] != default_next:
131-
chain.append(elt.next[0])
132-
elt = elt.next[0]
133-
# Now remove the blocks in the chain from the current
134-
# block list, so that they can be re-inserted.
135-
l = []
136-
for b in chain:
137-
assert index[b] > i
138-
l.append((index[b], b))
139-
l.sort()
140-
l.reverse()
141-
for j, b in l:
142-
del blocks[index[b]]
143-
# Insert the chain in the proper location
144-
blocks[i:i + 1] = [cur] + chain
145-
# Finally, re-compute the block indexes
146-
for i in range(len(blocks)):
147-
index[blocks[i]] = i
148-
149-
def fixupOrderForward(self, blocks, default_next):
150-
"""Make sure all JUMP_FORWARDs jump forward"""
151-
index = {}
152-
chains = []
153-
cur = []
154-
for b in blocks:
155-
index[b] = len(chains)
156-
cur.append(b)
157-
if b.next and b.next[0] == default_next:
158-
chains.append(cur)
159-
cur = []
160-
chains.append(cur)
161-
162-
while 1:
163-
constraints = []
164-
165-
for i in range(len(chains)):
166-
l = chains[i]
167-
for b in l:
168-
for c in b.get_children():
169-
if index[c] < i:
170-
forward_p = 0
171-
for inst in b.insts:
172-
if inst[0] == 'JUMP_FORWARD':
173-
if inst[1] == c:
174-
forward_p = 1
175-
if not forward_p:
176-
continue
177-
constraints.append((index[c], i))
178-
179-
if not constraints:
180-
break
181-
182-
# XXX just do one for now
183-
# do swaps to get things in the right order
184-
goes_before, a_chain = constraints[0]
185-
assert a_chain > goes_before
186-
c = chains[a_chain]
187-
chains.remove(c)
188-
chains.insert(goes_before, c)
189-
190-
del blocks[:]
191-
for c in chains:
192-
for b in c:
193-
blocks.append(b)
194-
19584
def getBlocks(self):
19685
return self.blocks.elements()
19786

@@ -205,27 +94,81 @@ def getContainedGraphs(self):
20594
l.extend(b.getContainedGraphs())
20695
return l
20796

208-
def dfs_postorder(b, seen):
209-
"""Depth-first search of tree rooted at b, return in postorder"""
97+
98+
def order_blocks(start_block, exit_block):
99+
"""Order blocks so that they are emitted in the right order"""
100+
# Rules:
101+
# - when a block has a next block, the next block must be emitted just after
102+
# - when a block has followers (relative jumps), it must be emitted before
103+
# them
104+
# - all reachable blocks must be emitted
210105
order = []
211-
seen[b] = b
212-
for c in b.get_children():
213-
if c in seen:
106+
107+
# Find all the blocks to be emitted.
108+
remaining = set()
109+
todo = [start_block]
110+
while todo:
111+
b = todo.pop()
112+
if b in remaining:
113+
continue
114+
remaining.add(b)
115+
for c in b.get_children():
116+
if c not in remaining:
117+
todo.append(c)
118+
119+
# A block is dominated by another block if that block must be emitted
120+
# before it.
121+
dominators = {}
122+
for b in remaining:
123+
if __debug__ and b.next:
124+
assert b is b.next[0].prev[0], (b, b.next)
125+
# preceeding blocks dominate following blocks
126+
for c in b.get_followers():
127+
while 1:
128+
dominators.setdefault(c, set()).add(b)
129+
# Any block that has a next pointer leading to c is also
130+
# dominated because the whole chain will be emitted at once.
131+
# Walk backwards and add them all.
132+
if c.prev and c.prev[0] is not b:
133+
c = c.prev[0]
134+
else:
135+
break
136+
137+
def find_next():
138+
# Find a block that can be emitted next.
139+
for b in remaining:
140+
for c in dominators[b]:
141+
if c in remaining:
142+
break # can't emit yet, dominated by a remaining block
143+
else:
144+
return b
145+
assert 0, 'circular dependency, cannot find next block'
146+
147+
b = start_block
148+
while 1:
149+
order.append(b)
150+
remaining.discard(b)
151+
if b.next:
152+
b = b.next[0]
214153
continue
215-
order = order + dfs_postorder(c, seen)
216-
order.append(b)
154+
elif b is not exit_block and not b.has_unconditional_transfer():
155+
order.append(exit_block)
156+
if not remaining:
157+
break
158+
b = find_next()
217159
return order
218160

161+
219162
class Block:
220163
_count = 0
221164

222165
def __init__(self, label=''):
223166
self.insts = []
224-
self.inEdges = misc.Set()
225-
self.outEdges = misc.Set()
167+
self.outEdges = set()
226168
self.label = label
227169
self.bid = Block._count
228170
self.next = []
171+
self.prev = []
229172
Block._count = Block._count + 1
230173

231174
def __repr__(self):
@@ -241,51 +184,46 @@ def __str__(self):
241184

242185
def emit(self, inst):
243186
op = inst[0]
244-
if op[:4] == 'JUMP':
245-
self.outEdges.add(inst[1])
246187
self.insts.append(inst)
247188

248189
def getInstructions(self):
249190
return self.insts
250191

251-
def addInEdge(self, block):
252-
self.inEdges.add(block)
253-
254192
def addOutEdge(self, block):
255193
self.outEdges.add(block)
256194

257195
def addNext(self, block):
258196
self.next.append(block)
259197
assert len(self.next) == 1, map(str, self.next)
198+
block.prev.append(self)
199+
assert len(block.prev) == 1, map(str, block.prev)
260200

261-
_uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS', 'YIELD_VALUE',
262-
'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP')
201+
_uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS',
202+
'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP',
203+
)
263204

264-
def pruneNext(self):
265-
"""Remove bogus edge for unconditional transfers
266-
267-
Each block has a next edge that accounts for implicit control
268-
transfers, e.g. from a JUMP_IF_FALSE to the block that will be
269-
executed if the test is true.
270-
271-
These edges must remain for the current assembler code to
272-
work. If they are removed, the dfs_postorder gets things in
273-
weird orders. However, they shouldn't be there for other
274-
purposes, e.g. conversion to SSA form. This method will
275-
remove the next edge when it follows an unconditional control
276-
transfer.
277-
"""
205+
def has_unconditional_transfer(self):
206+
"""Returns True if there is an unconditional transfer to an other block
207+
at the end of this block. This means there is no risk for the bytecode
208+
executer to go past this block's bytecode."""
278209
try:
279210
op, arg = self.insts[-1]
280211
except (IndexError, ValueError):
281212
return
282-
if op in self._uncond_transfer:
283-
self.next = []
213+
return op in self._uncond_transfer
284214

285215
def get_children(self):
286-
if self.next and self.next[0] in self.outEdges:
287-
self.outEdges.remove(self.next[0])
288-
return self.outEdges.elements() + self.next
216+
return list(self.outEdges) + self.next
217+
218+
def get_followers(self):
219+
"""Get the whole list of followers, including the next block."""
220+
followers = set(self.next)
221+
# Blocks that must be emitted *after* this one, because of
222+
# bytecode offsets (e.g. relative jumps) pointing to them.
223+
for inst in self.insts:
224+
if inst[0] in PyFlowGraph.hasjrel:
225+
followers.add(inst[1])
226+
return followers
289227

290228
def getContainedGraphs(self):
291229
"""Return all graphs contained within this block.
@@ -446,18 +384,18 @@ def flattenGraph(self):
446384
elif inst[0] != "SET_LINENO":
447385
pc = pc + 3
448386
opname = inst[0]
449-
if self.hasjrel.has_elt(opname):
387+
if opname in self.hasjrel:
450388
oparg = inst[1]
451389
offset = begin[oparg] - pc
452390
insts[i] = opname, offset
453-
elif self.hasjabs.has_elt(opname):
391+
elif opname in self.hasjabs:
454392
insts[i] = opname, begin[inst[1]]
455393
self.stage = FLAT
456394

457-
hasjrel = misc.Set()
395+
hasjrel = set()
458396
for i in dis.hasjrel:
459397
hasjrel.add(dis.opname[i])
460-
hasjabs = misc.Set()
398+
hasjabs = set()
461399
for i in dis.hasjabs:
462400
hasjabs.add(dis.opname[i])
463401

Lib/compiler/pycodegen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ def visitGenExprInner(self, node):
671671
self.startBlock(anchor)
672672
self.emit('POP_BLOCK')
673673
self.setups.pop()
674-
self.startBlock(end)
674+
self.nextBlock(end)
675675

676676
self.emit('LOAD_CONST', None)
677677

0 commit comments

Comments
 (0)