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 insert_link_conversions(self, block, skip=0): # insert the needed conversions on the links can_insert_here = block.exitswitch is None and len(block.exits) == 1 for link in block.exits[skip:]: self._convert_link(block, link) inputargs_reprs = self.setup_block_entry(link.target) newops = self.make_new_lloplist(block) newlinkargs = {} for i in range(len(link.args)): a1 = link.args[i] r_a2 = inputargs_reprs[i] if isinstance(a1, Constant): link.args[i] = inputconst(r_a2, a1.value) continue # the Constant was typed, done if a1 is link.last_exception: r_a1 = self.exceptiondata.r_exception_type elif a1 is link.last_exc_value: r_a1 = self.exceptiondata.r_exception_value else: r_a1 = self.bindingrepr(a1) if r_a1 == r_a2: continue # no conversion needed try: new_a1 = newops.convertvar(a1, r_a1, r_a2) except TyperError, e: self.gottypererror(e, block, link, newops) continue # try other args if new_a1 != a1: newlinkargs[i] = new_a1 if newops: if can_insert_here: block.operations.extend(newops) else: # cannot insert conversion operations around a single # link, unless it is the only exit of this block. # create a new block along the link... newblock = insert_empty_block( self.annotator, link, # ...and store the conversions there. newops=newops) link = newblock.exits[0] for i, new_a1 in newlinkargs.items(): link.args[i] = new_a1
def insert_link_conversions(self, block, skip=0): # insert the needed conversions on the links can_insert_here = block.exitswitch is None and len(block.exits) == 1 for link in block.exits[skip:]: self._convert_link(block, link) inputargs_reprs = self.setup_block_entry(link.target) newops = self.make_new_lloplist(block) newlinkargs = {} for i in range(len(link.args)): a1 = link.args[i] r_a2 = inputargs_reprs[i] if isinstance(a1, Constant): link.args[i] = inputconst(r_a2, a1.value) continue # the Constant was typed, done if a1 is link.last_exception: r_a1 = self.exceptiondata.r_exception_type elif a1 is link.last_exc_value: r_a1 = self.exceptiondata.r_exception_value else: r_a1 = self.bindingrepr(a1) if r_a1 == r_a2: continue # no conversion needed try: new_a1 = newops.convertvar(a1, r_a1, r_a2) except TyperError, e: self.gottypererror(e, block, link, newops) continue # try other args if new_a1 != a1: newlinkargs[i] = new_a1 if newops: if can_insert_here: block.operations.extend(newops) else: # cannot insert conversion operations around a single # link, unless it is the only exit of this block. # create a new block along the link... newblock = insert_empty_block(self.annotator, link, # ...and store the conversions there. newops=newops) link = newblock.exits[0] for i, new_a1 in newlinkargs.items(): link.args[i] = new_a1
def prepare_constant_fold_link(link, constants, splitblocks): block = link.target if not block.operations: # when the target block has no operation, there is nothing we can do # except trying to fold an exitswitch if block.exitswitch is not None and block.exitswitch in constants: llexitvalue = constants[block.exitswitch].value rewire_link_for_known_exitswitch(link, llexitvalue) return folded_count = fold_op_list(block.operations, constants, exit_early=True) n = len(block.operations) if block.canraise: n -= 1 # is the next, non-folded operation an indirect_call? if folded_count < n: nextop = block.operations[folded_count] if nextop.opname == 'indirect_call' and nextop.args[0] in constants: # indirect_call -> direct_call callargs = [constants[nextop.args[0]]] constants1 = constants.copy() complete_constants(link, constants1) for v in nextop.args[1:-1]: callargs.append(constants1.get(v, v)) v_result = Variable(nextop.result) v_result.concretetype = nextop.result.concretetype constants[nextop.result] = v_result callop = SpaceOperation('direct_call', callargs, v_result) newblock = insert_empty_block(link, [callop]) [link] = newblock.exits assert link.target is block folded_count += 1 if folded_count > 0: splits = splitblocks.setdefault(block, []) splits.append((folded_count, link, constants))
def prepare_constant_fold_link(link, constants, splitblocks): block = link.target if not block.operations: # when the target block has no operation, there is nothing we can do # except trying to fold an exitswitch if block.exitswitch is not None and block.exitswitch in constants: llexitvalue = constants[block.exitswitch].value rewire_link_for_known_exitswitch(link, llexitvalue) return folded_count = fold_op_list(block.operations, constants, exit_early=True) n = len(block.operations) if block.exitswitch == c_last_exception: n -= 1 # is the next, non-folded operation an indirect_call? if folded_count < n: nextop = block.operations[folded_count] if nextop.opname == 'indirect_call' and nextop.args[0] in constants: # indirect_call -> direct_call callargs = [constants[nextop.args[0]]] constants1 = constants.copy() complete_constants(link, constants1) for v in nextop.args[1:-1]: callargs.append(constants1.get(v, v)) v_result = Variable(nextop.result) v_result.concretetype = nextop.result.concretetype constants[nextop.result] = v_result callop = SpaceOperation('direct_call', callargs, v_result) newblock = insert_empty_block(None, link, [callop]) [link] = newblock.exits assert link.target is block folded_count += 1 if folded_count > 0: splits = splitblocks.setdefault(block, []) splits.append((folded_count, link, constants))
def run(self, vlist, vmeth, appendblock): # first check that the 'append' method object doesn't escape for hlop in appendblock.operations: if hlop.opname == "simple_call" and hlop.args[0] is vmeth: pass elif vmeth in hlop.args: raise DetectorFailed # used in another operation for link in appendblock.exits: if vmeth in link.args: raise DetectorFailed # escapes to a next block self.vmeth = vmeth self.vlistfamily = self.variable_families.find_rep(vlist) newlistblock = self.newlist_v[self.vlistfamily] self.vlistcone = {newlistblock: True} self.escapes = {self.graph.returnblock: True, self.graph.exceptblock: True} # in which loop are we? for loopnextblock, iterblock, viterfamily in self.loops: # check that the vlist is alive across the loop head block, # which ensures that we have a whole loop where the vlist # doesn't change if not self.vlist_alive(loopnextblock): continue # no - unrelated loop # check that we cannot go from 'newlist' to 'append' without # going through the 'iter' of our loop (and the following 'next'). # This ensures that the lifetime of vlist is cleanly divided in # "before" and "after" the loop... if self.reachable(newlistblock, appendblock, avoid=iterblock): continue # ... with the possible exception of links from the loop # body jumping back to the loop prologue, between 'newlist' and # 'iter', which we must forbid too: if self.reachable(loopnextblock, iterblock, avoid=newlistblock): continue # there must not be a larger number of calls to 'append' than # the number of elements that 'next' returns, so we must ensure # that we cannot go from 'append' to 'append' again without # passing 'next'... if self.reachable(appendblock, appendblock, avoid=loopnextblock): continue # ... and when the iterator is exhausted, we should no longer # reach 'append' at all. stopblocks = [link.target for link in loopnextblock.exits if link.exitcase is not None] accepted = True for stopblock1 in stopblocks: if self.reachable(stopblock1, appendblock, avoid=newlistblock): accepted = False if not accepted: continue # now explicitly find the "loop body" blocks: they are the ones # from which we can reach 'append' without going through 'iter'. # (XXX inefficient) loopbody = {} for block in self.graph.iterblocks(): if self.vlist_alive(block) and self.reachable(block, appendblock, iterblock): loopbody[block] = True # if the 'append' is actually after a 'break' or on a path that # can only end up in a 'break', then it won't be recorded as part # of the loop body at all. This is a strange case where we have # basically proved that the list will be of length 1... too # uncommon to worry about, I suspect if appendblock not in loopbody: continue # This candidate loop is acceptable if the list is not escaping # too early, i.e. in the loop header or in the loop body. loopheader = list(self.enum_blocks_with_vlist_from(newlistblock, avoid=loopnextblock)) assert loopheader[0] is newlistblock escapes = False for block in loopheader + loopbody.keys(): assert self.vlist_alive(block) if self.vlist_escapes(block): escapes = True break if not escapes: break # accept this loop! else: raise DetectorFailed # no suitable loop # Found a suitable loop, let's patch the graph: assert iterblock not in loopbody assert loopnextblock in loopbody for stopblock1 in stopblocks: assert stopblock1 not in loopbody # at StopIteration, the new list is exactly of the same length as # the one we iterate over if it's not possible to skip the appendblock # in the body: exactlength = not self.reachable_within(loopnextblock, loopnextblock, avoid=appendblock, stay_within=loopbody) # - add a hint(vlist, iterable, {'maxlength'}) in the iterblock, # where we can compute the known maximum length link = iterblock.exits[0] vlist = self.contains_vlist(link.args) assert vlist for hlop in iterblock.operations: res = self.variable_families.find_rep(hlop.result) if res is viterfamily: break else: raise AssertionError("lost 'iter' operation") chint = Constant({"maxlength": True}) hint = op.hint(vlist, hlop.args[0], chint) iterblock.operations.append(hint) link.args = list(link.args) for i in range(len(link.args)): if link.args[i] is vlist: link.args[i] = hint.result # - wherever the list exits the loop body, add a 'hint({fence})' for block in loopbody: for link in block.exits: if link.target not in loopbody: vlist = self.contains_vlist(link.args) if vlist is None: continue # list not passed along this link anyway hints = {"fence": True} if exactlength and block is loopnextblock and link.target in stopblocks: hints["exactlength"] = True chints = Constant(hints) newblock = unsimplify.insert_empty_block(None, link) index = link.args.index(vlist) vlist2 = newblock.inputargs[index] vlist3 = Variable(vlist2) newblock.inputargs[index] = vlist3 hint = op.hint(vlist3, chints) hint.result = vlist2 newblock.operations.append(hint)
# the exception cannot actually occur at all. # This is set by calling exception_cannot_occur(). # We just remove all exception links. block.exitswitch = None block.exits = block.exits[:1] else: # We have to split the block in two, with the exception-catching # exitswitch after the llop at 'pos', and the extra operations # in the new part of the block, corresponding to the # no-exception case. See for example test_rlist.test_indexerror # or test_rpbc.test_multiple_ll_one_hl_op. assert 0 <= pos < len(newops) - 1 extraops = block.operations[pos+1:] del block.operations[pos+1:] extrablock = insert_empty_block(self.annotator, noexclink, newops = extraops) if extrablock is None: self.insert_link_conversions(block) else: # skip the extrablock as a link target, its link doesn't need conversions # by construction, OTOH some of involved vars have no annotation # so proceeding with it would kill information self.insert_link_conversions(block, skip=1) # consider it as a link source instead self.insert_link_conversions(extrablock) def _convert_link(self, block, link): if link.exitcase is not None and link.exitcase != 'default': if isinstance(block.exitswitch, Variable):
def gen_exc_check(self, block, returnblock, normalafterblock=None): #var_exc_occured = Variable() #var_exc_occured.concretetype = lltype.Bool #block.operations.append(SpaceOperation("safe_call", [self.rpyexc_occured_ptr], var_exc_occured)) llops = rtyper.LowLevelOpList(None) spaceop = block.operations[-1] alloc_shortcut = self.check_for_alloc_shortcut(spaceop) if alloc_shortcut: var_no_exc = self.gen_nonnull(spaceop.result, llops) else: v_exc_type = self.gen_getfield('exc_type', llops) var_no_exc = self.gen_isnull(v_exc_type, llops) block.operations.extend(llops) block.exitswitch = var_no_exc #exception occurred case b = Block([]) b.operations = [SpaceOperation('debug_record_traceback', [], varoftype(lltype.Void))] l = Link([error_constant(returnblock.inputargs[0].concretetype)], returnblock) b.closeblock(l) l = Link([], b) l.exitcase = l.llexitcase = False #non-exception case l0 = block.exits[0] l0.exitcase = l0.llexitcase = True block.recloseblock(l0, l) insert_zeroing_op = False if spaceop.opname == 'malloc': flavor = spaceop.args[1].value['flavor'] if flavor == 'gc': insert_zeroing_op = True elif spaceop.opname == 'malloc_nonmovable': # xxx we cannot insert zero_gc_pointers_inside after # malloc_nonmovable, because it can return null. For now # we simply always force the zero=True flag on # malloc_nonmovable. c_flags = spaceop.args[1] c_flags.value = c_flags.value.copy() spaceop.args[1].value['zero'] = True # NB. when inserting more special-cases here, keep in mind that # you also need to list the opnames in transform_block() # (see "special cases") if insert_zeroing_op: if normalafterblock is None: normalafterblock = insert_empty_block(None, l0) v_result = spaceop.result if v_result in l0.args: result_i = l0.args.index(v_result) v_result_after = normalafterblock.inputargs[result_i] else: v_result_after = copyvar(None, v_result) l0.args.append(v_result) normalafterblock.inputargs.append(v_result_after) normalafterblock.operations.insert( 0, SpaceOperation('zero_gc_pointers_inside', [v_result_after], varoftype(lltype.Void)))
def move_pushes_earlier(graph, regalloc): """gc_push_roots and gc_pop_roots are pushes/pops to the shadowstack, immediately enclosing the operation that needs them (typically a call). Here, we try to move individual pushes earlier. Should run after expand_push_roots(), but before expand_pop_roots(), so that it sees individual 'gc_save_root' operations but bulk 'gc_pop_roots' operations. """ # Concrete example (assembler tested on x86-64 gcc 5.3 and clang 3.7): # # ----original---- ----move_pushes_earlier---- # # while (a > 10) { *foo = b; # *foo = b; while (a > 10) { # a = g(a); a = g(a); # b = *foo; b = *foo; # // *foo = b; # } } # return b; return b; # # => the store and the => the store is before, and gcc/clang # load are in the loop, moves the load after the loop # even in the assembler (the commented-out '*foo=b' is removed # here, but gcc/clang would also remove it) # Draft of the algorithm: see shadowcolor.txt if not regalloc: return entrymap = mkentrymap(graph) assert len(entrymap[graph.startblock]) == 1 inputvars = {} # {inputvar: (its block, its index in inputargs)} for block in graph.iterblocks(): for i, v in enumerate(block.inputargs): inputvars[v] = (block, i) Plist = [] for index in range(regalloc.numcolors): U = UnionFind() S = set() for block in graph.iterblocks(): for op in reversed(block.operations): if op.opname == 'gc_pop_roots': break else: continue # no gc_pop_roots in this block for v in op.args: if isinstance(v, Variable) and regalloc.checkcolor(v, index): break else: continue # no variable goes into index i succ = set() pending_succ = [(block, v)] while pending_succ: block1, v1 = pending_succ.pop() assert regalloc.checkcolor(v1, index) for op1 in block1.operations: if is_trivial_rewrite(op1) and op1.args[0] is v1: if regalloc.checkcolor(op1.result, index): pending_succ.append((block1, op1.result)) for link1 in block1.exits: for i2, v2 in enumerate(link1.args): if v2 is not v1: continue block2 = link1.target w2 = block2.inputargs[i2] if w2 in succ or not regalloc.checkcolor(w2, index): continue succ.add(w2) for op2 in block2.operations: if op2.opname in ('gc_save_root', 'gc_pop_roots'): break else: pending_succ.append((block2, w2)) U.union_list(list(succ)) S.update(succ) G = defaultdict(set) for block in graph.iterblocks(): found = False for opindex, op in enumerate(block.operations): if op.opname == 'gc_save_root': if (isinstance(op.args[1], Constant) and op.args[1].concretetype == lltype.Signed): break elif op.args[0].value == index: found = True break if not found or not isinstance(op.args[1], Variable): continue # no matching gc_save_root in this block key = (block, op) pred = set() pending_pred = [(block, op.args[1], opindex)] while pending_pred: block1, v1, opindex1 = pending_pred.pop() assert regalloc.getcolor(v1) == index for i in range(opindex1-1, -1, -1): op1 = block1.operations[i] if op1.opname == 'gc_pop_roots': break # stop if op1.result is v1: if not is_trivial_rewrite(op1): break # stop if not regalloc.checkcolor(op1.args[0], index): break # stop v1 = op1.args[0] else: varindex = block1.inputargs.index(v1) if v1 in pred: continue # already done pred.add(v1) for link1 in entrymap[block1]: prevblock1 = link1.prevblock if prevblock1 is not None: w1 = link1.args[varindex] if isinstance(w1, Variable) and w1 not in pred: if regalloc.checkcolor(w1, index): pending_pred.append((prevblock1, w1, len(prevblock1.operations))) U.union_list(list(pred)) for v1 in pred: G[v1].add(key) M = S.intersection(G) parts_target = {} for v in M: vp = U.find_rep(v) if vp not in parts_target: new_part = (index, set(), set()) # (index, # subset P of variables, # set of (block, gc_save_root)) Plist.append(new_part) parts_target[vp] = new_part part = parts_target[vp] part[1].add(v) part[2].update(G[v]) # Sort P so that it prefers places that would avoid multiple # gcsaveroots (smaller 'heuristic' result, so first in sorted # order); but also prefers smaller overall pieces, because it # might be possible to remove several small-scale pieces instead # of one big-scale one. def heuristic((index, P, gcsaveroots)): return float(len(P)) / len(gcsaveroots) Plist.sort(key=heuristic) variables_along_changes = {} live_at_start_of_block = set() # set of (block, index) insert_gc_push_root = defaultdict(list) for index, P, gcsaveroots in Plist: # if this Plist entry is not valid any more because of changes # done by the previous entries, drop it if any((inputvars[v][0], index) in live_at_start_of_block for v in P): continue if any(op not in block.operations for block, op in gcsaveroots): continue for v in P: assert regalloc.getcolor(v) == index assert v not in variables_along_changes success_count = 0 mark = [] for v in P: block, varindex = inputvars[v] for link in entrymap[block]: w = link.args[varindex] if link.prevblock is not None: prevoperations = link.prevblock.operations else: prevoperations = [] for op in reversed(prevoperations): if op.opname == 'gc_pop_roots': # it is possible to have gc_pop_roots() without # w in the args, if w is the result of the call # that comes just before. if (isinstance(w, Variable) and w in op.args and regalloc.checkcolor(w, index)): success_count += 1 else: mark.append((index, link, varindex)) break if op.result is w: if is_trivial_rewrite(op) and ( regalloc.checkcolor(op.args[0], index)): w = op.args[0] else: mark.append((index, link, varindex)) break else: if not isinstance(w, Variable) or w not in P: mark.append((index, link, varindex)) if success_count > 0: for block, op in gcsaveroots: newops = list(block.operations) newops.remove(op) block.operations = newops for index, link, varindex in mark: insert_gc_push_root[link].append((index, link.args[varindex])) for v in P: block, varindex = inputvars[v] variables_along_changes[v] = block, index live_at_start_of_block.add((block, index)) for link in insert_gc_push_root: newops = [_gc_save_root(index, v) for index, v in sorted(insert_gc_push_root[link])] insert_empty_block(link, newops=newops)
def run(self, vlist, vmeth, appendblock): # first check that the 'append' method object doesn't escape for hlop in appendblock.operations: if hlop.opname == 'simple_call' and hlop.args[0] is vmeth: pass elif vmeth in hlop.args: raise DetectorFailed # used in another operation for link in appendblock.exits: if vmeth in link.args: raise DetectorFailed # escapes to a next block self.vmeth = vmeth self.vlistfamily = self.variable_families.find_rep(vlist) newlistblock = self.newlist_v[self.vlistfamily] self.vlistcone = {newlistblock: True} self.escapes = { self.graph.returnblock: True, self.graph.exceptblock: True } # in which loop are we? for loopnextblock, iterblock, viterfamily in self.loops: # check that the vlist is alive across the loop head block, # which ensures that we have a whole loop where the vlist # doesn't change if not self.vlist_alive(loopnextblock): continue # no - unrelated loop # check that we cannot go from 'newlist' to 'append' without # going through the 'iter' of our loop (and the following 'next'). # This ensures that the lifetime of vlist is cleanly divided in # "before" and "after" the loop... if self.reachable(newlistblock, appendblock, avoid=iterblock): continue # ... with the possible exception of links from the loop # body jumping back to the loop prologue, between 'newlist' and # 'iter', which we must forbid too: if self.reachable(loopnextblock, iterblock, avoid=newlistblock): continue # there must not be a larger number of calls to 'append' than # the number of elements that 'next' returns, so we must ensure # that we cannot go from 'append' to 'append' again without # passing 'next'... if self.reachable(appendblock, appendblock, avoid=loopnextblock): continue # ... and when the iterator is exhausted, we should no longer # reach 'append' at all. stopblocks = [ link.target for link in loopnextblock.exits if link.exitcase is not None ] accepted = True for stopblock1 in stopblocks: if self.reachable(stopblock1, appendblock, avoid=newlistblock): accepted = False if not accepted: continue # now explicitly find the "loop body" blocks: they are the ones # from which we can reach 'append' without going through 'iter'. # (XXX inefficient) loopbody = {} for block in self.graph.iterblocks(): if (self.vlist_alive(block) and self.reachable(block, appendblock, iterblock)): loopbody[block] = True # if the 'append' is actually after a 'break' or on a path that # can only end up in a 'break', then it won't be recorded as part # of the loop body at all. This is a strange case where we have # basically proved that the list will be of length 1... too # uncommon to worry about, I suspect if appendblock not in loopbody: continue # This candidate loop is acceptable if the list is not escaping # too early, i.e. in the loop header or in the loop body. loopheader = list( self.enum_blocks_with_vlist_from(newlistblock, avoid=loopnextblock)) assert loopheader[0] is newlistblock escapes = False for block in loopheader + loopbody.keys(): assert self.vlist_alive(block) if self.vlist_escapes(block): escapes = True break if not escapes: break # accept this loop! else: raise DetectorFailed # no suitable loop # Found a suitable loop, let's patch the graph: assert iterblock not in loopbody assert loopnextblock in loopbody for stopblock1 in stopblocks: assert stopblock1 not in loopbody # at StopIteration, the new list is exactly of the same length as # the one we iterate over if it's not possible to skip the appendblock # in the body: exactlength = not self.reachable_within(loopnextblock, loopnextblock, avoid=appendblock, stay_within=loopbody) # - add a hint(vlist, iterable, {'maxlength'}) in the iterblock, # where we can compute the known maximum length # - new in June 2017: we do that only if 'exactlength' is True. # found some real use cases where the over-allocation scheme # was over-allocating far too much: the loop would only append # an item to the list after 'if some rare condition:'. By # dropping this hint, we disable preallocation and cause the # append() to be done by checking the size, but still, after # the loop, we will turn the list into a fixed-size one. # ('maxlength_inexact' is never processed elsewhere; the hint # is still needed to prevent this function from being entered # in an infinite loop) link = iterblock.exits[0] vlist = self.contains_vlist(link.args) assert vlist for hlop in iterblock.operations: res = self.variable_families.find_rep(hlop.result) if res is viterfamily: break else: raise AssertionError("lost 'iter' operation") chint = Constant( {'maxlength' if exactlength else 'maxlength_inexact': True}) hint = op.hint(vlist, hlop.args[0], chint) iterblock.operations.append(hint) link.args = list(link.args) for i in range(len(link.args)): if link.args[i] is vlist: link.args[i] = hint.result # - wherever the list exits the loop body, add a 'hint({fence})' for block in loopbody: for link in block.exits: if link.target not in loopbody: vlist = self.contains_vlist(link.args) if vlist is None: continue # list not passed along this link anyway hints = {'fence': True} if (exactlength and block is loopnextblock and link.target in stopblocks): hints['exactlength'] = True chints = Constant(hints) newblock = unsimplify.insert_empty_block(link) index = link.args.index(vlist) vlist2 = newblock.inputargs[index] vlist3 = Variable(vlist2) newblock.inputargs[index] = vlist3 hint = op.hint(vlist3, chints) hint.result = vlist2 newblock.operations.append(hint)
def gen_exc_check(self, block, returnblock, normalafterblock=None): llops = rtyper.LowLevelOpList(None) spaceop = block.operations[-1] alloc_shortcut = self.check_for_alloc_shortcut(spaceop) if alloc_shortcut: var_no_exc = self.gen_nonnull(spaceop.result, llops) else: v_exc_type = self.gen_getfield('exc_type', llops) var_no_exc = self.gen_isnull(v_exc_type, llops) # # We could add a "var_no_exc is likely true" hint, but it seems # not to help, so it was commented out again. #var_no_exc = llops.genop('likely', [var_no_exc], lltype.Bool) block.operations.extend(llops) block.exitswitch = var_no_exc #exception occurred case b = Block([]) b.operations = [SpaceOperation('debug_record_traceback', [], varoftype(lltype.Void))] l = Link([error_constant(returnblock.inputargs[0].concretetype)], returnblock) b.closeblock(l) l = Link([], b) l.exitcase = l.llexitcase = False #non-exception case l0 = block.exits[0] l0.exitcase = l0.llexitcase = True block.recloseblock(l0, l) insert_zeroing_op = False if spaceop.opname in ['malloc','malloc_varsize']: flavor = spaceop.args[1].value['flavor'] if flavor == 'gc': insert_zeroing_op = True true_zero = spaceop.args[1].value.get('zero', False) # NB. when inserting more special-cases here, keep in mind that # you also need to list the opnames in transform_block() # (see "special cases") if insert_zeroing_op: if normalafterblock is None: normalafterblock = insert_empty_block(l0) v_result = spaceop.result if v_result in l0.args: result_i = l0.args.index(v_result) v_result_after = normalafterblock.inputargs[result_i] else: v_result_after = v_result.copy() l0.args.append(v_result) normalafterblock.inputargs.append(v_result_after) if true_zero: opname = "zero_everything_inside" else: opname = "zero_gc_pointers_inside" normalafterblock.operations.insert( 0, SpaceOperation(opname, [v_result_after], varoftype(lltype.Void)))
if pos == "removed": # the exception cannot actually occur at all. # This is set by calling exception_cannot_occur(). # We just remove all exception links. block.exitswitch = None block.exits = block.exits[:1] else: # We have to split the block in two, with the exception-catching # exitswitch after the llop at 'pos', and the extra operations # in the new part of the block, corresponding to the # no-exception case. See for example test_rlist.test_indexerror # or test_rpbc.test_multiple_ll_one_hl_op. assert 0 <= pos < len(newops) - 1 extraops = block.operations[pos+1:] del block.operations[pos+1:] extrablock = insert_empty_block(noexclink, newops=extraops) if extrablock is None: self.insert_link_conversions(block) else: # skip the extrablock as a link target, its link doesn't need conversions # by construction, OTOH some of involved vars have no annotation # so proceeding with it would kill information self.insert_link_conversions(block, skip=1) # consider it as a link source instead self.insert_link_conversions(extrablock) def _convert_link(self, block, link): if link.exitcase is not None and link.exitcase != 'default': if isinstance(block.exitswitch, Variable): r_case = self.bindingrepr(block.exitswitch)
def insert_links(graph): # insert a new empty block along every link, as a place to put the forcings for link in list(graph.iterlinks()): unsimplify.insert_empty_block(link)
def specialize_block(self, block): graph = self.annotator.annotated[block] if graph not in self.annotator.fixed_graphs: self.annotator.fixed_graphs[graph] = True # make sure that the return variables of all graphs # are concretetype'd self.setconcretetype(graph.getreturnvar()) # give the best possible types to the input args try: self.setup_block_entry(block) except TyperError as e: self.gottypererror(e, block, "block-entry") raise # specialize all the operations, as far as possible if block.operations == (): # return or except block return newops = self.make_new_lloplist(block) varmapping = {} for v in block.getvariables(): varmapping[v] = v # records existing Variables for hop in self.highlevelops(block, newops): try: hop.setup() # this is called from here to catch TyperErrors... self.translate_hl_to_ll(hop, varmapping) except TyperError as e: self.gottypererror(e, block, hop.spaceop) raise block.operations[:] = newops block.renamevariables(varmapping) extrablock = None pos = newops.llop_raising_exceptions if (pos is not None and pos != len(newops) - 1): # this is for the case where the llop that raises the exceptions # is not the last one in the list. assert block.canraise noexclink = block.exits[0] assert noexclink.exitcase is None if pos == "removed": # the exception cannot actually occur at all. # This is set by calling exception_cannot_occur(). # We just remove all exception links. block.exitswitch = None block.exits = block.exits[:1] else: # We have to split the block in two, with the exception-catching # exitswitch after the llop at 'pos', and the extra operations # in the new part of the block, corresponding to the # no-exception case. See for example test_rlist.test_indexerror # or test_rpbc.test_multiple_ll_one_hl_op. assert 0 <= pos < len(newops) - 1 extraops = block.operations[pos+1:] del block.operations[pos+1:] extrablock = insert_empty_block(noexclink, newops=extraops) if extrablock is None: self.insert_link_conversions(block) else: # skip the extrablock as a link target, its link doesn't need conversions # by construction, OTOH some of involved vars have no annotation # so proceeding with it would kill information self.insert_link_conversions(block, skip=1) # consider it as a link source instead self.insert_link_conversions(extrablock)
# the exception cannot actually occur at all. # This is set by calling exception_cannot_occur(). # We just remove all exception links. block.exitswitch = None block.exits = block.exits[:1] else: # We have to split the block in two, with the exception-catching # exitswitch after the llop at 'pos', and the extra operations # in the new part of the block, corresponding to the # no-exception case. See for example test_rlist.test_indexerror # or test_rpbc.test_multiple_ll_one_hl_op. assert 0 <= pos < len(newops) - 1 extraops = block.operations[pos + 1:] del block.operations[pos + 1:] extrablock = insert_empty_block(self.annotator, noexclink, newops=extraops) if extrablock is None: self.insert_link_conversions(block) else: # skip the extrablock as a link target, its link doesn't need conversions # by construction, OTOH some of involved vars have no annotation # so proceeding with it would kill information self.insert_link_conversions(block, skip=1) # consider it as a link source instead self.insert_link_conversions(extrablock) def _convert_link(self, block, link): if link.exitcase is not None and link.exitcase != 'default': if isinstance(block.exitswitch, Variable):
def gen_exc_check(self, block, returnblock, normalafterblock=None): llops = rtyper.LowLevelOpList(None) spaceop = block.operations[-1] alloc_shortcut = self.check_for_alloc_shortcut(spaceop) if alloc_shortcut: var_no_exc = self.gen_nonnull(spaceop.result, llops) else: v_exc_type = self.gen_getfield('exc_type', llops) var_no_exc = self.gen_isnull(v_exc_type, llops) # # We could add a "var_no_exc is likely true" hint, but it seems # not to help, so it was commented out again. #var_no_exc = llops.genop('likely', [var_no_exc], lltype.Bool) block.operations.extend(llops) block.exitswitch = var_no_exc #exception occurred case b = Block([]) b.operations = [ SpaceOperation('debug_record_traceback', [], varoftype(lltype.Void)) ] l = Link([error_constant(returnblock.inputargs[0].concretetype)], returnblock) b.closeblock(l) l = Link([], b) l.exitcase = l.llexitcase = False #non-exception case l0 = block.exits[0] l0.exitcase = l0.llexitcase = True block.recloseblock(l0, l) insert_zeroing_op = False if spaceop.opname in ['malloc', 'malloc_varsize']: flavor = spaceop.args[1].value['flavor'] if flavor == 'gc': insert_zeroing_op = True true_zero = spaceop.args[1].value.get('zero', False) # NB. when inserting more special-cases here, keep in mind that # you also need to list the opnames in transform_block() # (see "special cases") if insert_zeroing_op: if normalafterblock is None: normalafterblock = insert_empty_block(None, l0) v_result = spaceop.result if v_result in l0.args: result_i = l0.args.index(v_result) v_result_after = normalafterblock.inputargs[result_i] else: v_result_after = v_result.copy() l0.args.append(v_result) normalafterblock.inputargs.append(v_result_after) if true_zero: opname = "zero_everything_inside" else: opname = "zero_gc_pointers_inside" normalafterblock.operations.insert( 0, SpaceOperation(opname, [v_result_after], varoftype(lltype.Void)))
if pos == "removed": # the exception cannot actually occur at all. # This is set by calling exception_cannot_occur(). # We just remove all exception links. block.exitswitch = None block.exits = block.exits[:1] else: # We have to split the block in two, with the exception-catching # exitswitch after the llop at 'pos', and the extra operations # in the new part of the block, corresponding to the # no-exception case. See for example test_rlist.test_indexerror # or test_rpbc.test_multiple_ll_one_hl_op. assert 0 <= pos < len(newops) - 1 extraops = block.operations[pos + 1:] del block.operations[pos + 1:] extrablock = insert_empty_block(noexclink, newops=extraops) if extrablock is None: self.insert_link_conversions(block) else: # skip the extrablock as a link target, its link doesn't need conversions # by construction, OTOH some of involved vars have no annotation # so proceeding with it would kill information self.insert_link_conversions(block, skip=1) # consider it as a link source instead self.insert_link_conversions(extrablock) def _convert_link(self, block, link): if link.exitcase is not None and link.exitcase != 'default': if isinstance(block.exitswitch, Variable): r_case = self.bindingrepr(block.exitswitch)