def test_split_block_exceptions(): for i in range(2): def raises(x): if x == 1: raise ValueError elif x == 2: raise KeyError return x def catches(x): try: y = x + 1 raises(y) except ValueError: return 0 except KeyError: return 1 return x graph, t = translate(catches, [int]) split_block(t.annotator, graph.startblock, i) checkgraph(graph) interp = LLInterpreter(t.rtyper) result = interp.eval_graph(graph, [0]) assert result == 0 result = interp.eval_graph(graph, [1]) assert result == 1 result = interp.eval_graph(graph, [2]) assert result == 2
def test_split_block_exceptions(): for i in range(2): def raises(x): if x == 1: raise ValueError elif x == 2: raise KeyError return x def catches(x): try: y = x + 1 raises(y) except ValueError: return 0 except KeyError: return 1 return x graph, t = translate(catches, [int]) split_block(graph.startblock, i) checkgraph(graph) interp = LLInterpreter(t.rtyper) result = interp.eval_graph(graph, [0]) assert result == 0 result = interp.eval_graph(graph, [1]) assert result == 1 result = interp.eval_graph(graph, [2]) assert result == 2
def test_split_blocks_simple(): for i in range(4): def f(x, y): z = x + y w = x * y return z + w graph, t = translate(f, [int, int]) split_block(graph.startblock, i) checkgraph(graph) interp = LLInterpreter(t.rtyper) result = interp.eval_graph(graph, [1, 2]) assert result == 5
def test_split_blocks_simple(): for i in range(4): def f(x, y): z = x + y w = x * y return z + w graph, t = translate(f, [int, int]) split_block(t.annotator, graph.startblock, i) checkgraph(graph) interp = LLInterpreter(t.rtyper) result = interp.eval_graph(graph, [1, 2]) assert result == 5
def test_split_blocks_conditional(): for i in range(3): def f(x, y): if x + 12: return y + 1 else: return y + 2 graph, t = translate(f, [int, int]) split_block(graph.startblock, i) checkgraph(graph) interp = LLInterpreter(t.rtyper) result = interp.eval_graph(graph, [-12, 2]) assert result == 4 result = interp.eval_graph(graph, [0, 2]) assert result == 3
def rewire_links(splitblocks, graph): for block, splits in splitblocks.items(): # A splitting position is given by how many operations were # folded with the knowledge of an incoming link's constant. # Various incoming links may cause various splitting positions. # We split the block gradually, starting from the end. splits.sort() splits.reverse() for position, link, constants in splits: assert link.target is block if position == len(block.operations) and block.exitswitch is None: # a split here would leave nothing in the 2nd part, so # directly rewire the links assert len(block.exits) == 1 splitlink = block.exits[0] else: # split the block at the given position splitlink = split_block(block, position) assert list(block.exits) == [splitlink] assert link.target is block assert splitlink.prevblock is block complete_constants(link, constants) args = [constants.get(v, v) for v in splitlink.args] link.args = args link.target = splitlink.target
def split_before_jit_merge_point(graph, portalblock, portalopindex): """Split the block just before the 'jit_merge_point', making sure the input args are in the canonical order. """ # split the block just before the jit_merge_point() if portalopindex > 0: link = split_block(portalblock, portalopindex) portalblock = link.target portalop = portalblock.operations[0] # split again, this time enforcing the order of the live vars # specified by decode_hp_hint_args(). assert portalop.opname == 'jit_marker' assert portalop.args[0].value == 'jit_merge_point' greens_v, reds_v = decode_hp_hint_args(portalop) link = split_block(portalblock, 0, greens_v + reds_v) return link.target
def rewire_links(splitblocks, graph): for block, splits in splitblocks.items(): # A splitting position is given by how many operations were # folded with the knowledge of an incoming link's constant. # Various incoming links may cause various splitting positions. # We split the block gradually, starting from the end. splits.sort() splits.reverse() for position, link, constants in splits: assert link.target is block if position == len(block.operations) and block.exitswitch is None: # a split here would leave nothing in the 2nd part, so # directly rewire the links assert len(block.exits) == 1 splitlink = block.exits[0] else: # split the block at the given position splitlink = split_block(None, block, position) assert list(block.exits) == [splitlink] assert link.target is block assert splitlink.prevblock is block complete_constants(link, constants) args = [constants.get(v, v) for v in splitlink.args] link.args = args link.target = splitlink.target
def test_split_blocks_conditional(): for i in range(3): def f(x, y): if x + 12: return y + 1 else: return y + 2 graph, t = translate(f, [int, int]) split_block(t.annotator, graph.startblock, i) checkgraph(graph) interp = LLInterpreter(t.rtyper) result = interp.eval_graph(graph, [-12, 2]) assert result == 4 result = interp.eval_graph(graph, [0, 2]) assert result == 3
def transform_block(self, graph, block): need_exc_matching = False n_gen_exc_checks = 0 if block is graph.exceptblock: return need_exc_matching, n_gen_exc_checks elif block is graph.returnblock: return need_exc_matching, n_gen_exc_checks last_operation = len(block.operations) - 1 if block.exitswitch == c_last_exception: need_exc_matching = True last_operation -= 1 elif (len(block.exits) == 1 and block.exits[0].target is graph.returnblock and len(block.operations) and (block.exits[0].args[0].concretetype is lltype.Void or block.exits[0].args[0] is block.operations[-1].result) and block.operations[-1].opname not in ('malloc', # special cases 'malloc_nonmovable')): last_operation -= 1 lastblock = block for i in range(last_operation, -1, -1): op = block.operations[i] if not self.raise_analyzer.can_raise(op): continue splitlink = split_block(None, block, i+1) afterblock = splitlink.target if lastblock is block: lastblock = afterblock self.gen_exc_check(block, graph.returnblock, afterblock) n_gen_exc_checks += 1 if need_exc_matching: assert lastblock.exitswitch == c_last_exception if not self.raise_analyzer.can_raise(lastblock.operations[-1]): #print ("operation %s cannot raise, but has exception" # " guarding in graph %s" % (lastblock.operations[-1], # graph)) lastblock.exitswitch = None lastblock.recloseblock(lastblock.exits[0]) lastblock.exits[0].exitcase = None else: self.insert_matching(lastblock, graph) return need_exc_matching, n_gen_exc_checks
def transform_block(self, graph, block): need_exc_matching = False n_gen_exc_checks = 0 if block is graph.exceptblock: return need_exc_matching, n_gen_exc_checks elif block is graph.returnblock: return need_exc_matching, n_gen_exc_checks last_operation = len(block.operations) - 1 if block.canraise: need_exc_matching = True last_operation -= 1 elif (len(block.exits) == 1 and block.exits[0].target is graph.returnblock and len(block.operations) and (block.exits[0].args[0].concretetype is lltype.Void or block.exits[0].args[0] is block.operations[-1].result) and block.operations[-1].opname not in ('malloc', 'malloc_varsize') and not has_llhelper_error_value(graph)): # special cases last_operation -= 1 lastblock = block for i in range(last_operation, -1, -1): op = block.operations[i] if not self.raise_analyzer.can_raise(op): continue splitlink = split_block(block, i + 1) afterblock = splitlink.target if lastblock is block: lastblock = afterblock self.gen_exc_check(graph, block, graph.returnblock, afterblock) n_gen_exc_checks += 1 if need_exc_matching: assert lastblock.canraise if not self.raise_analyzer.can_raise(lastblock.operations[-1]): lastblock.exitswitch = None lastblock.recloseblock(lastblock.exits[0]) lastblock.exits[0].exitcase = None else: self.insert_matching(lastblock, graph) return need_exc_matching, n_gen_exc_checks
def do_inline(self, block, index_operation): splitlink = split_block(block, index_operation) afterblock = splitlink.target # these variables have to be passed along all the links in the inlined # graph because the original function needs them in the blocks after # the inlined function # for every inserted block we need a new copy of these variables, # this copy is created with the method passon_vars self.original_passon_vars = [ arg for arg in block.exits[0].args if isinstance(arg, Variable) ] assert afterblock.operations[0].opname == self.op.opname self.op = afterblock.operations.pop(0) #vars that need to be passed through the blocks of the inlined function linktoinlined = splitlink copiedstartblock = self.copy_block(self.graph_to_inline.startblock) #find args passed to startblock of inlined function passon_args = [] for arg in self.op.args[1:]: if isinstance(arg, Constant): passon_args.append(arg) else: index = afterblock.inputargs.index(arg) passon_args.append(linktoinlined.args[index]) passon_args += self.original_passon_vars #rewire blocks linktoinlined.target = copiedstartblock linktoinlined.args = passon_args afterblock.inputargs = [self.op.result] + afterblock.inputargs if self.graph_to_inline.returnblock in self.entrymap: self.rewire_returnblock(afterblock) if self.graph_to_inline.exceptblock in self.entrymap: self.rewire_exceptblock(afterblock) if self.exception_guarded: assert afterblock.exits[0].exitcase is None afterblock.recloseblock(afterblock.exits[0]) afterblock.exitswitch = None self.search_for_calls(afterblock) self.search_for_calls(block)
def do_inline(self, block, index_operation): splitlink = split_block(None, block, index_operation) afterblock = splitlink.target # these variables have to be passed along all the links in the inlined # graph because the original function needs them in the blocks after # the inlined function # for every inserted block we need a new copy of these variables, # this copy is created with the method passon_vars self.original_passon_vars = [arg for arg in block.exits[0].args if isinstance(arg, Variable)] assert afterblock.operations[0].opname == self.op.opname self.op = afterblock.operations.pop(0) #vars that need to be passed through the blocks of the inlined function linktoinlined = splitlink copiedstartblock = self.copy_block(self.graph_to_inline.startblock) #find args passed to startblock of inlined function passon_args = [] for arg in self.op.args[1:]: if isinstance(arg, Constant): passon_args.append(arg) else: index = afterblock.inputargs.index(arg) passon_args.append(linktoinlined.args[index]) passon_args += self.original_passon_vars #rewire blocks linktoinlined.target = copiedstartblock linktoinlined.args = passon_args afterblock.inputargs = [self.op.result] + afterblock.inputargs if self.graph_to_inline.returnblock in self.entrymap: self.rewire_returnblock(afterblock) if self.graph_to_inline.exceptblock in self.entrymap: self.rewire_exceptblock(afterblock) if self.exception_guarded: assert afterblock.exits[0].exitcase is None afterblock.recloseblock(afterblock.exits[0]) afterblock.exitswitch = None self.search_for_calls(afterblock) self.search_for_calls(block)
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) # 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 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 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