Esempio n. 1
0
    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
Esempio n. 2
0
    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
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
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))
Esempio n. 6
0
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))
Esempio n. 7
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)
Esempio n. 8
0
                # 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):
Esempio n. 9
0
    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)))
Esempio n. 10
0
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)
Esempio n. 11
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)
Esempio n. 12
0
    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)))
Esempio n. 13
0
            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)
Esempio n. 14
0
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)
Esempio n. 15
0
    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)
Esempio n. 16
0
                # 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):
Esempio n. 17
0
    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)
Esempio n. 18
0
    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)))
Esempio n. 19
0
            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)