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