Example #1
0
    def can_be_applied(graph, candidate, expr_index, sdfg, strict=False):
        guard = graph.node(candidate[DetectLoop._loop_guard])
        begin = graph.node(candidate[DetectLoop._loop_begin])

        # A for-loop guard only has two incoming edges (init and increment)
        guard_inedges = graph.in_edges(guard)
        if len(guard_inedges) != 2:
            return False
        # A for-loop guard only has two outgoing edges (loop and exit-loop)
        guard_outedges = graph.out_edges(guard)
        if len(guard_outedges) != 2:
            return False

        # Both incoming edges to guard must set exactly one variable and
        # the same one
        if (len(guard_inedges[0].data.assignments) != 1
                or len(guard_inedges[1].data.assignments) != 1):
            return False
        itervar = list(guard_inedges[0].data.assignments.keys())[0]
        if itervar not in guard_inedges[1].data.assignments:
            return False

        # Outgoing edges must not have assignments and be a negation of each
        # other
        if any(len(e.data.assignments) > 0 for e in guard_outedges):
            return False
        if guard_outedges[0].data.condition_sympy() != (sp.Not(
                guard_outedges[1].data.condition_sympy())):
            return False

        # All nodes inside loop must be dominated by loop guard
        dominators = nx.dominance.immediate_dominators(sdfg.nx,
                                                       sdfg.start_state)
        loop_nodes = nxutil.dfs_topological_sort(
            sdfg, sources=[begin], condition=lambda _, child: child != guard)
        backedge_found = False
        for node in loop_nodes:
            if any(e.dst == guard for e in graph.out_edges(node)):
                backedge_found = True

            # Traverse the dominator tree upwards, if we reached the guard,
            # the node is in the loop. If we reach the starting state
            # without passing through the guard, fail.
            dom = node
            while dom != dominators[dom]:
                if dom == guard:
                    break
                dom = dominators[dom]
            else:
                return False

        if not backedge_found:
            return False

        return True
Example #2
0
    def dispatch_subgraph(self,
                          sdfg,
                          dfg,
                          state_id,
                          function_stream,
                          callsite_stream,
                          skip_entry_node=False):
        """ Dispatches a code generator for a scope subgraph of an 
            `SDFGState`. """

        start_nodes = list(v for v in dfg.nodes()
                           if len(list(dfg.predecessors(v))) == 0)

        # Mark nodes to skip in order to be able to skip
        nodes_to_skip = set()

        if skip_entry_node:
            assert len(start_nodes) == 1
            nodes_to_skip.add(start_nodes[0])

        for v in nxutil.dfs_topological_sort(dfg, start_nodes):
            if v in nodes_to_skip:
                continue

            if isinstance(v, nodes.MapEntry):
                scope_subgraph = sdfg.find_state(state_id).scope_subgraph(v)

                # Propagate parallelism
                if dfg.is_parallel():
                    scope_subgraph.set_parallel_parent(dfg.get_parallel_parent)

                assert not dfg.is_parallel() or scope_subgraph.is_parallel()
                self.dispatch_scope(v.map.schedule, sdfg, scope_subgraph,
                                    state_id, function_stream, callsite_stream)

                # Skip scope subgraph nodes
                #print(scope_subgraph.nodes())
                nodes_to_skip.update(scope_subgraph.nodes())
            else:
                self.dispatch_node(sdfg, dfg, state_id, v, function_stream,
                                   callsite_stream)
Example #3
0
    def apply(self, sdfg):
        # Obtain loop information
        guard: sd.SDFGState = sdfg.node(self.subgraph[DetectLoop._loop_guard])
        begin: sd.SDFGState = sdfg.node(self.subgraph[DetectLoop._loop_begin])
        after_state: sd.SDFGState = sdfg.node(
            self.subgraph[DetectLoop._exit_state])

        # Obtain iteration variable, range, and stride
        guard_inedges = sdfg.in_edges(guard)
        condition_edge = sdfg.edges_between(guard, begin)[0]
        itervar = list(guard_inedges[0].data.assignments.keys())[0]
        condition = condition_edge.data.condition_sympy()
        rng = LoopUnroll._loop_range(itervar, guard_inedges, condition)

        # Loop must be unrollable
        if self.count == 0 and any(
                symbolic.issymbolic(r, sdfg.constants) for r in rng):
            raise ValueError('Loop cannot be fully unrolled, size is symbolic')
        if self.count != 0:
            raise NotImplementedError  # TODO(later)

        # Find the state prior to the loop
        if str(rng[0]) == guard_inedges[0].data.assignments[itervar]:
            before_state: sd.SDFGState = guard_inedges[0].src
            last_state: sd.SDFGState = guard_inedges[1].src
        else:
            before_state: sd.SDFGState = guard_inedges[1].src
            last_state: sd.SDFGState = guard_inedges[0].src

        # Get loop states
        loop_states = list(
            nxutil.dfs_topological_sort(
                sdfg,
                sources=[begin],
                condition=lambda _, child: child != guard))
        first_id = loop_states.index(begin)
        last_id = loop_states.index(last_state)
        loop_subgraph = gr.SubgraphView(sdfg, loop_states)

        # Evaluate the real values of the loop
        start, end, stride = (symbolic.evaluate(r, sdfg.constants)
                              for r in rng)

        # Create states for loop subgraph
        unrolled_states = []
        for i in range(start, end + 1, stride):
            # Using to/from JSON copies faster than deepcopy (which will also
            # copy the parent SDFG)
            new_states = [
                sd.SDFGState.from_json(s.to_json(), context={'sdfg': sdfg})
                for s in loop_states
            ]

            # Replace iterate with value in each state
            for state in new_states:
                state.set_label(state.label + '_%s_%d' % (itervar, i))
                state.replace(itervar, str(i))

            # Add subgraph to original SDFG
            for edge in loop_subgraph.edges():
                src = new_states[loop_states.index(edge.src)]
                dst = new_states[loop_states.index(edge.dst)]

                # Replace conditions in subgraph edges
                data: edges.InterstateEdge = copy.deepcopy(edge.data)
                if data.condition:
                    ASTFindReplace({itervar: str(i)}).visit(data.condition)

                sdfg.add_edge(src, dst, data)

            # Connect iterations with unconditional edges
            if len(unrolled_states) > 0:
                sdfg.add_edge(unrolled_states[-1][1], new_states[first_id],
                              edges.InterstateEdge())

            unrolled_states.append((new_states[first_id], new_states[last_id]))

        # Connect new states to before and after states without conditions
        if unrolled_states:
            sdfg.add_edge(before_state, unrolled_states[0][0],
                          edges.InterstateEdge())
            sdfg.add_edge(unrolled_states[-1][1], after_state,
                          edges.InterstateEdge())

        # Remove old states from SDFG
        sdfg.remove_nodes_from([guard] + loop_states)
Example #4
0
def _propagate_labels(g, sdfg):
    """ Propagates memlets throughout one SDFG state. 
        @param g: The state to propagate in.
        @param sdfg: The SDFG in which the state is situated.
        @note: This is an in-place operation on the SDFG state.
    """
    patterns = MemletPattern.patterns()

    # Algorithm:
    # 1. Start propagating information from tasklets outwards (their edges
    #    are hardcoded).
    #    NOTE: This process can be performed in parallel.
    # 2. Traverse the neighboring nodes (topological sort, first forward to
    #    outputs and then backward to inputs).
    #    There are four possibilities:
    #    a. If the neighboring node is a tasklet, skip (such edges are
    #       immutable)
    #    b. If the neighboring node is an array, make sure it is the correct
    #       array. Otherwise, throw a mismatch exception.
    #    c. If the neighboring node is a scope node, and its other edges are
    #       not set, set the results per-array, using the union of the
    #       obtained ranges in the previous depth.
    #    d. If the neighboring node is a scope node, and its other edges are
    #       already set, verify the results per-array, using the union of the
    #       obtained ranges in the previous depth.
    #    NOTE: The SDFG creation process ensures that all edges in the
    #          multigraph are tagged with the appropriate array. In any case
    #          of ambiguity, the function raises an exception.
    # 3. For each edge in the multigraph, collect results and group by array assigned to edge.
    #    Accumulate information about each array in the target node.
    scope_dict = g.scope_dict()

    def stop_at(parent, child):
        # Transients should only propagate in the direction of the
        # non-transient data
        if isinstance(parent,
                      nodes.AccessNode) and parent.desc(sdfg).transient:
            for _, _, _, _, memlet in g.edges_between(parent, child):
                if parent.data != memlet.data:
                    return True
            return False
        if isinstance(child, nodes.AccessNode):
            return False
        return True

    array_data = {}  # type: dict(node -> dict(data -> list(Subset)))
    tasklet_nodes = [
        node for node in g.nodes() if (isinstance(node, nodes.CodeNode) or (
            isinstance(node, nodes.AccessNode) and node.desc(sdfg).transient))
    ]
    # Step 1: Direction - To output
    for start_node in tasklet_nodes:
        for node in nxutil.dfs_topological_sort(g,
                                                start_node,
                                                condition=stop_at):
            _propagate_node(sdfg, g, node, array_data, patterns, scope_dict,
                            True)
    # Step 1: Direction - To input
    array_data = {}
    g.reverse()
    for node in nxutil.dfs_topological_sort(g,
                                            tasklet_nodes,
                                            condition=stop_at):
        _propagate_node(sdfg, g, node, array_data, patterns, scope_dict)

    # To support networkx 1.11
    g.reverse()