def apply(self, sdfg: sd.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 = self._loop_range(itervar, guard_inedges, condition) # Find the state prior to the loop if rng[0] == symbolic.pystr_to_symbolic( guard_inedges[0].data.assignments[itervar]): init_edge: sd.InterstateEdge = guard_inedges[0] before_state: sd.SDFGState = guard_inedges[0].src last_state: sd.SDFGState = guard_inedges[1].src else: init_edge: sd.InterstateEdge = guard_inedges[1] before_state: sd.SDFGState = guard_inedges[1].src last_state: sd.SDFGState = guard_inedges[0].src # Get loop states loop_states = list( sdutil.dfs_conditional(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) #################################################################### # Transform # If begin, change initialization assignment and prepend states before # guard init_edge.data.assignments[itervar] = rng[0] + self.count * rng[2] append_state = before_state # Add `count` states, each with instantiated iteration variable unrolled_states = [] for i in range(self.count): # Instantiate loop states with iterate value new_states = self.instantiate_loop(sdfg, loop_states, loop_subgraph, itervar, rng[0] + i * rng[2]) # Connect states to before the loop with unconditional edges sdfg.add_edge(append_state, new_states[first_id], sd.InterstateEdge()) append_state = new_states[last_id] # Reconnect edge to guard state from last peeled iteration if append_state != before_state: sdfg.remove_edge(init_edge) sdfg.add_edge(append_state, guard, init_edge.data)
def _constants_from_unvisited_state( self, sdfg: SDFG, state: SDFGState, arrays: Set[str], existing_constants: Dict[SDFGState, Dict[str, Any]]) -> Dict[str, Any]: """ Collects constants from an unvisited state, traversing backwards until reaching states that do have collected constants. """ result: Dict[str, Any] = {} for parent, node in sdutil.dfs_conditional( sdfg, sources=[state], reverse=True, condition=lambda p, c: c not in existing_constants, yield_parent=True): # Skip first node if parent is None: continue # Get connecting edge (reversed) edge = sdfg.edges_between(node, parent)[0] # If node already has propagated constants, update dictionary and stop traversal self._propagate( result, self._data_independent_assignments(edge.data, arrays), True) if node in existing_constants: self._propagate(result, existing_constants[node], True) return result
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 = sdutil.dfs_conditional( 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 can_be_applied(graph, candidate, expr_index, sdfg, permissive=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 # All incoming edges to the guard must set the same variable itvar = None for iedge in guard_inedges: if itvar is None: itvar = set(iedge.data.assignments.keys()) else: itvar &= iedge.data.assignments.keys() if itvar is None: return False # Outgoing edges must be a negation of each other 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 = sdutil.dfs_conditional( sdfg, sources=[begin], condition=lambda _, child: child != guard) backedge = None for node in loop_nodes: for e in graph.out_edges(node): if e.dst == guard: backedge = e break # 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 backedge is None: return False # The backedge must assignment the iteration variable itvar &= backedge.data.assignments.keys() if len(itvar) != 1: # Either no consistent iteration variable found, or too many # consistent iteration variables found return False return True
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 rng[0] == symbolic.pystr_to_symbolic( 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( sdutil.dfs_conditional(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): # Instantiate loop states with iterate value new_states = self.instantiate_loop(sdfg, loop_states, loop_subgraph, itervar, i) # Connect iterations with unconditional edges if len(unrolled_states) > 0: sdfg.add_edge(unrolled_states[-1][1], new_states[first_id], sd.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], sd.InterstateEdge()) sdfg.add_edge(unrolled_states[-1][1], after_state, sd.InterstateEdge()) # Remove old states from SDFG sdfg.remove_nodes_from([guard] + loop_states)
def apply(self, sdfg: sd.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] not_condition_edge = sdfg.edges_between(guard, after_state)[0] itervar = list(guard_inedges[0].data.assignments.keys())[0] condition = condition_edge.data.condition_sympy() rng = self._loop_range(itervar, guard_inedges, condition) # Find the state prior to the loop if rng[0] == symbolic.pystr_to_symbolic( guard_inedges[0].data.assignments[itervar]): init_edge: sd.InterstateEdge = guard_inedges[0] before_state: sd.SDFGState = guard_inedges[0].src last_state: sd.SDFGState = guard_inedges[1].src else: init_edge: sd.InterstateEdge = guard_inedges[1] before_state: sd.SDFGState = guard_inedges[1].src last_state: sd.SDFGState = guard_inedges[0].src # Get loop states loop_states = list( sdutil.dfs_conditional(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) #################################################################### # Transform if self.begin: # If begin, change initialization assignment and prepend states before # guard init_edge.data.assignments[itervar] = str(rng[0] + self.count * rng[2]) append_state = before_state # Add `count` states, each with instantiated iteration variable for i in range(self.count): # Instantiate loop states with iterate value state_name: str = 'start_' + itervar + str(i * rng[2]) state_name = state_name.replace('-', 'm').replace( '+', 'p').replace('*', 'M').replace('/', 'D') new_states = self.instantiate_loop( sdfg, loop_states, loop_subgraph, itervar, rng[0] + i * rng[2], state_name, ) # Connect states to before the loop with unconditional edges sdfg.add_edge(append_state, new_states[first_id], sd.InterstateEdge()) append_state = new_states[last_id] # Reconnect edge to guard state from last peeled iteration if append_state != before_state: sdfg.remove_edge(init_edge) sdfg.add_edge(append_state, guard, init_edge.data) else: # If begin, change initialization assignment and prepend states before # guard itervar_sym = pystr_to_symbolic(itervar) condition_edge.data.condition = CodeBlock( self._modify_cond(condition_edge.data.condition, itervar, rng[2])) not_condition_edge.data.condition = CodeBlock( self._modify_cond(not_condition_edge.data.condition, itervar, rng[2])) prepend_state = after_state # Add `count` states, each with instantiated iteration variable for i in reversed(range(self.count)): # Instantiate loop states with iterate value state_name: str = 'end_' + itervar + str(-i * rng[2]) state_name = state_name.replace('-', 'm').replace( '+', 'p').replace('*', 'M').replace('/', 'D') new_states = self.instantiate_loop( sdfg, loop_states, loop_subgraph, itervar, itervar_sym + i * rng[2], state_name, ) # Connect states to before the loop with unconditional edges sdfg.add_edge(new_states[last_id], prepend_state, sd.InterstateEdge()) prepend_state = new_states[first_id] # Reconnect edge to guard state from last peeled iteration if prepend_state != after_state: sdfg.remove_edge(not_condition_edge) sdfg.add_edge(guard, prepend_state, not_condition_edge.data)
def apply(self, _, sdfg: sd.SDFG): #################################################################### # Obtain loop information guard: sd.SDFGState = self.loop_guard begin: sd.SDFGState = self.loop_begin after_state: sd.SDFGState = self.exit_state # Obtain iteration variable, range, and stride condition_edge = sdfg.edges_between(guard, begin)[0] not_condition_edge = sdfg.edges_between(guard, after_state)[0] itervar, rng, loop_struct = find_for_loop(sdfg, guard, begin) # Get loop states loop_states = list( sdutil.dfs_conditional(sdfg, sources=[begin], condition=lambda _, child: child != guard)) first_id = loop_states.index(begin) last_state = loop_struct[1] last_id = loop_states.index(last_state) loop_subgraph = gr.SubgraphView(sdfg, loop_states) #################################################################### # Transform if self.begin: # If begin, change initialization assignment and prepend states before # guard init_edges = [] before_states = loop_struct[0] for before_state in before_states: init_edge = sdfg.edges_between(before_state, guard)[0] init_edge.data.assignments[itervar] = str(rng[0] + self.count * rng[2]) init_edges.append(init_edge) append_states = before_states # Add `count` states, each with instantiated iteration variable for i in range(self.count): # Instantiate loop states with iterate value state_name: str = 'start_' + itervar + str(i * rng[2]) state_name = state_name.replace('-', 'm').replace( '+', 'p').replace('*', 'M').replace('/', 'D') new_states = self.instantiate_loop( sdfg, loop_states, loop_subgraph, itervar, rng[0] + i * rng[2], state_name, ) # Connect states to before the loop with unconditional edges for append_state in append_states: sdfg.add_edge(append_state, new_states[first_id], sd.InterstateEdge()) append_states = [new_states[last_id]] # Reconnect edge to guard state from last peeled iteration for append_state in append_states: if append_state not in before_states: for init_edge in init_edges: sdfg.remove_edge(init_edge) sdfg.add_edge(append_state, guard, init_edges[0].data) else: # If begin, change initialization assignment and prepend states before # guard itervar_sym = pystr_to_symbolic(itervar) condition_edge.data.condition = CodeBlock( self._modify_cond(condition_edge.data.condition, itervar, rng[2])) not_condition_edge.data.condition = CodeBlock( self._modify_cond(not_condition_edge.data.condition, itervar, rng[2])) prepend_state = after_state # Add `count` states, each with instantiated iteration variable for i in reversed(range(self.count)): # Instantiate loop states with iterate value state_name: str = 'end_' + itervar + str(-i * rng[2]) state_name = state_name.replace('-', 'm').replace( '+', 'p').replace('*', 'M').replace('/', 'D') new_states = self.instantiate_loop( sdfg, loop_states, loop_subgraph, itervar, itervar_sym + i * rng[2], state_name, ) # Connect states to before the loop with unconditional edges sdfg.add_edge(new_states[last_id], prepend_state, sd.InterstateEdge()) prepend_state = new_states[first_id] # Reconnect edge to guard state from last peeled iteration if prepend_state != after_state: sdfg.remove_edge(not_condition_edge) sdfg.add_edge(guard, prepend_state, not_condition_edge.data)
def state_parent_tree(sdfg: SDFG) -> Dict[SDFGState, SDFGState]: """ Computes an upward-pointing tree of each state, pointing to the "parent state" it belongs to (in terms of structured control flow). More formally, each state is either mapped to its immediate dominator with out degree > 2, one state upwards if state occurs after a loop, or the start state if no such states exist. :param sdfg: The SDFG to analyze. :return: A dictionary that maps each state to a parent state, or None if the root (start) state. """ idom = nx.immediate_dominators(sdfg.nx, sdfg.start_state) alldoms = all_dominators(sdfg, idom) loopexits: Dict[SDFGState, SDFGState] = defaultdict(lambda: None) # First, annotate loops for be in back_edges(sdfg, idom, alldoms): guard = be.dst laststate = be.src if loopexits[guard] is not None: continue # Natural loops = one edge leads back to loop, another leads out in_edges = sdfg.in_edges(guard) out_edges = sdfg.out_edges(guard) # A loop guard has two or more incoming edges (1 increment and # n init, all identical), and exactly two outgoing edges (loop and # exit loop). if len(in_edges) < 2 or len(out_edges) != 2: continue # The outgoing edges must be negations of one another. if out_edges[0].data.condition_sympy() != (sp.Not(out_edges[1].data.condition_sympy())): continue # Find all nodes that are between each branch and the guard. # Condition makes sure the entire cycle is dominated by this node. # If not, we're looking at a guard for a nested cycle, which we ignore for # this cycle. oa, ob = out_edges[0].dst, out_edges[1].dst reachable_a = False a_reached_guard = False def cond_a(parent, child): nonlocal reachable_a nonlocal a_reached_guard if reachable_a: # If last state has been reached, stop traversal return False if parent is laststate or child is laststate: # Reached back edge reachable_a = True a_reached_guard = True return False if oa not in alldoms[child]: # Traversed outside of the loop return False if child is guard: # Traversed back to guard a_reached_guard = True return False return True # Keep traversing reachable_b = False b_reached_guard = False def cond_b(parent, child): nonlocal reachable_b nonlocal b_reached_guard if reachable_b: # If last state has been reached, stop traversal return False if parent is laststate or child is laststate: # Reached back edge reachable_b = True b_reached_guard = True return False if ob not in alldoms[child]: # Traversed outside of the loop return False if child is guard: # Traversed back to guard b_reached_guard = True return False return True # Keep traversing list(sdutil.dfs_conditional(sdfg, (oa,), cond_a)) list(sdutil.dfs_conditional(sdfg, (ob,), cond_b)) # Check which candidate states led back to guard is_a_begin = a_reached_guard and reachable_a is_b_begin = b_reached_guard and reachable_b loop_state = None exit_state = None if is_a_begin and not is_b_begin: loop_state = oa exit_state = ob elif is_b_begin and not is_a_begin: loop_state = ob exit_state = oa if loop_state is None or exit_state is None: continue loopexits[guard] = exit_state # Get dominators parents: Dict[SDFGState, SDFGState] = {} step_up: Set[SDFGState] = set() for state in sdfg.nodes(): curdom = idom[state] if curdom == state: parents[state] = None continue while curdom != idom[curdom]: if sdfg.out_degree(curdom) > 1: break curdom = idom[curdom] if sdfg.out_degree(curdom) == 2 and loopexits[curdom] is not None: p = state while p != curdom and p != loopexits[curdom]: p = idom[p] if p == loopexits[curdom]: # Dominated by loop exit: do one more step up step_up.add(state) parents[state] = curdom # Step up for state in step_up: if parents[state] is not None: parents[state] = parents[parents[state]] return parents
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, together with the last # state(s) before the loop and the last loop state. itervar, rng, loop_struct = find_for_loop(sdfg, guard, begin) # Loop must be fully unrollable for now. if self.count != 0: raise NotImplementedError # TODO(later) # Get loop states loop_states = list( sdutil.dfs_conditional(sdfg, sources=[begin], condition=lambda _, child: child != guard)) first_id = loop_states.index(begin) last_state = loop_struct[1] last_id = loop_states.index(last_state) loop_subgraph = gr.SubgraphView(sdfg, loop_states) try: start, end, stride = (r for r in rng) stride = symbolic.evaluate(stride, sdfg.constants) loop_diff = int(symbolic.evaluate(end - start + 1, sdfg.constants)) is_symbolic = any([symbolic.issymbolic(r) for r in rng[:2]]) except TypeError: raise TypeError('Loop difference and strides cannot be symbolic.') # Create states for loop subgraph unrolled_states = [] for i in range(0, loop_diff, stride): current_index = start + i # Instantiate loop states with iterate value new_states = self.instantiate_loop(sdfg, loop_states, loop_subgraph, itervar, current_index, str(i) if is_symbolic else None) # Connect iterations with unconditional edges if len(unrolled_states) > 0: sdfg.add_edge(unrolled_states[-1][1], new_states[first_id], sd.InterstateEdge()) unrolled_states.append((new_states[first_id], new_states[last_id])) # Get any assignments that might be on the edge to the after state after_assignments = (sdfg.edges_between( guard, after_state)[0].data.assignments) # Connect new states to before and after states without conditions if unrolled_states: before_states = loop_struct[0] for before_state in before_states: sdfg.add_edge(before_state, unrolled_states[0][0], sd.InterstateEdge()) sdfg.add_edge(unrolled_states[-1][1], after_state, sd.InterstateEdge(assignments=after_assignments)) # Remove old states from SDFG sdfg.remove_nodes_from([guard] + loop_states)