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)
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)