Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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)