def find_predecessors(graph, pending_pred): """Return the set of variables whose content can end up inside one of the 'pending_pred', which is a list of (block, var) tuples. """ entrymap = mkentrymap(graph) if len(entrymap[graph.startblock]) != 1: insert_empty_startblock(graph) entrymap = mkentrymap(graph) pred = set([v for block, v in pending_pred]) def add(block, v): if isinstance(v, Variable): if v not in pred: pending_pred.append((block, v)) pred.add(v) while pending_pred: block, v = pending_pred.pop() if v in block.inputargs: var_index = block.inputargs.index(v) for link in entrymap[block]: prevblock = link.prevblock if prevblock is not None: add(prevblock, link.args[var_index]) else: for op in block.operations: if op.result is v: if is_trivial_rewrite(op): add(block, op.args[0]) break return pred
def transform_graph(self, graph): if graph in self.minimal_transform: if self.minimalgctransformer: self.minimalgctransformer.transform_graph(graph) self.minimal_transform.remove(graph) return if graph in self.seen_graphs: return self.seen_graphs.add(graph) self.links_to_split = {} # link -> vars to pop_alive across the link # for sanity, we need an empty block at the start of the graph inserted_empty_startblock = False if not starts_with_empty_block(graph): insert_empty_startblock(graph) inserted_empty_startblock = True is_borrowed = self.compute_borrowed_vars(graph) try: for block in graph.iterblocks(): self.transform_block(block, is_borrowed) except GCTransformError as e: e.args = ('[function %s]: %s' % (graph.name, e.message),) raise for link, livecounts in self.links_to_split.iteritems(): llops = LowLevelOpList() for var, livecount in livecounts.iteritems(): for i in range(livecount): self.pop_alive(var, llops) for i in range(-livecount): self.push_alive(var, llops) if llops: if link.prevblock.exitswitch is None: link.prevblock.operations.extend(llops) else: insert_empty_block(link, llops) # remove the empty block at the start of the graph, which should # still be empty (but let's check) if starts_with_empty_block(graph) and inserted_empty_startblock: old_startblock = graph.startblock graph.startblock = graph.startblock.exits[0].target checkgraph(graph) self.links_to_split = None v = Variable('vanishing_exc_value') v.concretetype = self.get_lltype_of_exception_value() llops = LowLevelOpList() self.pop_alive(v, llops) graph.exc_cleanup = (v, list(llops)) return is_borrowed # xxx for tests only
def transform_graph(self, graph): if graph in self.minimal_transform: if self.minimalgctransformer: self.minimalgctransformer.transform_graph(graph) self.minimal_transform.remove(graph) return if graph in self.seen_graphs: return self.seen_graphs.add(graph) self.links_to_split = {} # link -> vars to pop_alive across the link # for sanity, we need an empty block at the start of the graph inserted_empty_startblock = False if not starts_with_empty_block(graph): insert_empty_startblock(self.translator.annotator, graph) inserted_empty_startblock = True is_borrowed = self.compute_borrowed_vars(graph) for block in graph.iterblocks(): self.transform_block(block, is_borrowed) for link, livecounts in self.links_to_split.iteritems(): llops = LowLevelOpList() for var, livecount in livecounts.iteritems(): for i in range(livecount): self.pop_alive(var, llops) for i in range(-livecount): self.push_alive(var, llops) if llops: if link.prevblock.exitswitch is None: link.prevblock.operations.extend(llops) else: insert_empty_block(self.translator.annotator, link, llops) # remove the empty block at the start of the graph, which should # still be empty (but let's check) if starts_with_empty_block(graph) and inserted_empty_startblock: old_startblock = graph.startblock graph.startblock = graph.startblock.exits[0].target checkgraph(graph) self.links_to_split = None v = Variable('vanishing_exc_value') v.concretetype = self.get_lltype_of_exception_value() llops = LowLevelOpList() self.pop_alive(v, llops) graph.exc_cleanup = (v, list(llops)) return is_borrowed # xxx for tests only
def tweak_generator_body_graph(Entry, graph): # First, always run simplify_graph in order to reduce the number of # variables passed around simplify_graph(graph) insert_empty_startblock(graph) _insert_reads(graph.startblock, Entry.varnames) Entry.block = graph.startblock # mappings = [Entry] # stopblock = Block([]) op0 = op.simple_call(const(StopIteration)) op1 = op.type(op0.result) stopblock.operations = [op0, op1] stopblock.closeblock(Link([op1.result, op0.result], graph.exceptblock)) # for block in list(graph.iterblocks()): for exit in block.exits: if exit.target is graph.returnblock: exit.args = [] exit.target = stopblock assert block is not stopblock for index in range(len(block.operations) - 1, -1, -1): hlop = block.operations[index] if hlop.opname == 'yield_': [v_yielded_value] = hlop.args del block.operations[index] newlink = split_block(block, index) newblock = newlink.target # class Resume(AbstractPosition): _immutable_ = True block = newblock Resume.__name__ = 'Resume%d' % len(mappings) mappings.append(Resume) varnames = get_variable_names(newlink.args) # _insert_reads(newblock, varnames) # op_resume = op.simple_call(const(Resume)) block.operations.append(op_resume) v_resume = op_resume.result for i, name in enumerate(varnames): block.operations.append( op.setattr(v_resume, const(name), newlink.args[i])) op_pair = op.newtuple(v_resume, v_yielded_value) block.operations.append(op_pair) newlink.args = [op_pair.result] newlink.target = graph.returnblock # regular_entry_block = Block([Variable('entry')]) block = regular_entry_block for Resume in mappings: op_check = op.isinstance(block.inputargs[0], const(Resume)) block.operations.append(op_check) block.exitswitch = op_check.result link1 = Link([block.inputargs[0]], Resume.block) link1.exitcase = True nextblock = Block([Variable('entry')]) link2 = Link([block.inputargs[0]], nextblock) link2.exitcase = False block.closeblock(link1, link2) block = nextblock block.closeblock( Link([ Constant(AssertionError), Constant(AssertionError("bad generator class")) ], graph.exceptblock)) graph.startblock = regular_entry_block graph.signature = Signature(['entry']) graph.defaults = () checkgraph(graph) eliminate_empty_blocks(graph)
def tweak_generator_body_graph(Entry, graph): # First, always run simplify_graph in order to reduce the number of # variables passed around simplify_graph(graph) insert_empty_startblock(None, graph) _insert_reads(graph.startblock, Entry.varnames) Entry.block = graph.startblock # mappings = [Entry] # stopblock = Block([]) op0 = op.simple_call(const(StopIteration)) op1 = op.type(op0.result) stopblock.operations = [op0, op1] stopblock.closeblock(Link([op1.result, op0.result], graph.exceptblock)) # for block in list(graph.iterblocks()): for exit in block.exits: if exit.target is graph.returnblock: exit.args = [] exit.target = stopblock assert block is not stopblock for index in range(len(block.operations)-1, -1, -1): hlop = block.operations[index] if hlop.opname == 'yield_': [v_yielded_value] = hlop.args del block.operations[index] newlink = split_block(None, block, index) newblock = newlink.target # class Resume(AbstractPosition): _immutable_ = True block = newblock Resume.__name__ = 'Resume%d' % len(mappings) mappings.append(Resume) varnames = get_variable_names(newlink.args) # _insert_reads(newblock, varnames) # op_resume = op.simple_call(const(Resume)) block.operations.append(op_resume) v_resume = op_resume.result for i, name in enumerate(varnames): block.operations.append( op.setattr(v_resume, const(name), newlink.args[i])) op_pair = op.newtuple(v_resume, v_yielded_value) block.operations.append(op_pair) newlink.args = [op_pair.result] newlink.target = graph.returnblock # regular_entry_block = Block([Variable('entry')]) block = regular_entry_block for Resume in mappings: op_check = op.simple_call( const(isinstance), block.inputargs[0], const(Resume)) block.operations.append(op_check) block.exitswitch = op_check.result link1 = Link([block.inputargs[0]], Resume.block) link1.exitcase = True nextblock = Block([Variable('entry')]) link2 = Link([block.inputargs[0]], nextblock) link2.exitcase = False block.closeblock(link1, link2) block = nextblock block.closeblock(Link([Constant(AssertionError), Constant(AssertionError("bad generator class"))], graph.exceptblock)) graph.startblock = regular_entry_block graph.signature = Signature(['entry']) graph.defaults = () checkgraph(graph) eliminate_empty_blocks(graph)
def tweak_generator_body_graph(Entry, graph): # First, always run simplify_graph in order to reduce the number of # variables passed around simplify_graph(graph) # assert graph.startblock.operations[0].opname == 'generator_mark' graph.startblock.operations.pop(0) # insert_empty_startblock(None, graph) _insert_reads(graph.startblock, Entry.varnames) Entry.block = graph.startblock # mappings = [Entry] # stopblock = Block([]) v0 = Variable() v1 = Variable() stopblock.operations = [ SpaceOperation('simple_call', [Constant(StopIteration)], v0), SpaceOperation('type', [v0], v1), ] stopblock.closeblock(Link([v1, v0], graph.exceptblock)) # for block in list(graph.iterblocks()): for exit in block.exits: if exit.target is graph.returnblock: exit.args = [] exit.target = stopblock assert block is not stopblock for index in range(len(block.operations)-1, -1, -1): op = block.operations[index] if op.opname == 'yield': [v_yielded_value] = op.args del block.operations[index] newlink = split_block(None, block, index) newblock = newlink.target # class Resume(AbstractPosition): _immutable_ = True block = newblock Resume.__name__ = 'Resume%d' % len(mappings) mappings.append(Resume) varnames = get_variable_names(newlink.args) # _insert_reads(newblock, varnames) # v_resume = Variable('resume') block.operations.append( SpaceOperation('simple_call', [Constant(Resume)], v_resume)) for i, name in enumerate(varnames): block.operations.append( SpaceOperation('setattr', [v_resume, Constant(name), newlink.args[i]], Variable())) v_pair = Variable('pair') block.operations.append( SpaceOperation('newtuple', [v_resume, v_yielded_value], v_pair)) newlink.args = [v_pair] newlink.target = graph.returnblock # regular_entry_block = Block([Variable('entry')]) block = regular_entry_block for Resume in mappings: v_check = Variable() block.operations.append( SpaceOperation('simple_call', [Constant(isinstance), block.inputargs[0], Constant(Resume)], v_check)) block.exitswitch = v_check link1 = Link([block.inputargs[0]], Resume.block) link1.exitcase = True nextblock = Block([Variable('entry')]) link2 = Link([block.inputargs[0]], nextblock) link2.exitcase = False block.closeblock(link1, link2) block = nextblock block.closeblock(Link([Constant(AssertionError), Constant(AssertionError("bad generator class"))], graph.exceptblock)) graph.startblock = regular_entry_block graph.signature = Signature(['entry']) graph.defaults = () checkgraph(graph) eliminate_empty_blocks(graph)
def add_enter_leave_roots_frame(graph, regalloc, c_gcdata): # put 'gc_enter_roots_frame' as late as possible, but before the # first 'gc_save_root' is reached. # # put the 'gc_leave_roots_frame' operations as early as possible, # that is, just after the last 'gc_restore_root' reached. This is # done by putting it along a link, such that the previous block # contains a 'gc_restore_root' and from the next block it is not # possible to reach any extra 'gc_restore_root'; then, as doing # this is not as precise as we'd like, we first break every block # just after their last 'gc_restore_root'. if regalloc is None: return # break blocks after their last 'gc_restore_root', unless they # are already at the last position for block in graph.iterblocks(): ops = block.operations for i in range(len(ops)-1, -1, -1): if ops[i].opname == 'gc_restore_root': if i < len(ops) - 1: split_block(block, i + 1) break # done insert_empty_startblock(graph) entrymap = mkentrymap(graph) # helpers def is_interesting_op(op): if op.opname == 'gc_restore_root': return True if op.opname == 'gc_save_root': # ignore saves that say "everything is free" return not (isinstance(op.args[1], Constant) and isinstance(op.args[1].value, int) and op.args[1].value == bitmask_all_free) return False bitmask_all_free = (1 << regalloc.numcolors) - 1 def insert_along_link(link, opname, args, cache): b2 = link.target if b2 not in cache: newblock = Block([v.copy() for v in b2.inputargs]) newblock.operations.append( SpaceOperation(opname, args, varoftype(lltype.Void))) newblock.closeblock(Link(list(newblock.inputargs), b2)) cache[b2] = newblock link.target = cache[b2] # make a list of blocks with gc_save_root/gc_restore_root in them interesting_blocks = [] for block in graph.iterblocks(): for op in block.operations: if is_interesting_op(op): assert block is not graph.startblock assert block is not graph.returnblock interesting_blocks.append(block) break # interrupt this block, go to the next one # compute the blocks such that 'gc_save_root/gc_restore_root' # exist anywhere before the start of this block before_blocks = set() pending = list(interesting_blocks) seen = set(pending) while pending: block = pending.pop() for link in block.exits: before_blocks.add(link.target) if link.target not in seen: seen.add(link.target) pending.append(link.target) assert graph.startblock not in before_blocks # compute the blocks such that 'gc_save_root/gc_restore_root' # exist anywhere after the start of this block after_blocks = set(interesting_blocks) pending = list(interesting_blocks) while pending: block = pending.pop() for link in entrymap[block]: if link.prevblock is not None: if link.prevblock not in after_blocks: after_blocks.add(link.prevblock) pending.append(link.prevblock) assert graph.returnblock not in after_blocks # this is the set of blocks such that, at the start of the block, # we're "in frame", i.e. there are 'gc_save_root/gc_restore_root' # both before and after the start of the block. inside_blocks = before_blocks & after_blocks inside_or_interesting_blocks = set(interesting_blocks) | inside_blocks # if a block contains gc_save_root/gc_restore_root but is not # an "inside_block", then add gc_enter_roots_frame where needed c_num = Constant(regalloc.numcolors, lltype.Signed) for block in interesting_blocks: if block not in inside_blocks: i = 0 while not is_interesting_op(block.operations[i]): i += 1 block.operations.insert(i, SpaceOperation('gc_enter_roots_frame', [c_gcdata, c_num], varoftype(lltype.Void))) # If a link goes from a "non-inside, non-interesting block" # straight to an "inside_block", insert a gc_enter_roots_frame # along the link. Similarly, if a block is a "inside-or- # interesting_block" and exits with a link going to a # "non-inside_block", then insert a gc_leave_roots_frame along the # link. cache1 = {} cache2 = {} for block in list(graph.iterblocks()): if block not in inside_or_interesting_blocks: for link in block.exits: if link.target in inside_blocks: insert_along_link(link, 'gc_enter_roots_frame', [c_gcdata, c_num], cache1) else: for link in block.exits: if link.target not in inside_blocks: insert_along_link(link, 'gc_leave_roots_frame', [], cache2) # check all blocks not in "inside_block": they might contain a # gc_save_root() that writes the bitmask meaning "everything is # free". Look only before gc_enter_roots_frame, if there is one # in that block. Remove these out-of-frame gc_save_root(). for block in graph.iterblocks(): if block not in inside_blocks: newops = [] for i, op in enumerate(block.operations): if op.opname == 'gc_enter_roots_frame': newops.extend(block.operations[i:]) break if op.opname == 'gc_save_root' and not is_interesting_op(op): pass # don't add in newops else: newops.append(op) if len(newops) < len(block.operations): block.operations = newops join_blocks(graph) # for the extra new blocks made in this function