def apply(self, sdfg): def gnode(nname): return graph.nodes()[self.subgraph[nname]] graph = sdfg.nodes()[self.state_id] in_array = gnode(RedundantSecondArray._in_array) out_array = gnode(RedundantSecondArray._out_array) # We assume the following pattern: A -- e1 --> B -- e2 --> others # 1. Get edge e1 and extract subsets for arrays A and B e1 = graph.edges_between(in_array, out_array)[0] a_subset, b1_subset = _validate_subsets(e1, sdfg.arrays) # 2. Iterate over the e2 edges and traverse the memlet tree for e2 in graph.out_edges(out_array): path = graph.memlet_tree(e2) for e3 in path: # 2-a. Extract subsets for array B and others b3_subset, other_subset = _validate_subsets( e3, sdfg.arrays, src_name=out_array.data) # 2-b. Modify memlet to match array A. Example: # A -- (0, a:b)/(c:c+b) --> B -- (c+d)/None --> others # A -- (0, a+d)/None --> others e3.data.data = in_array.data # (c+d) - (c:c+b) = (d) b3_subset.offset(b1_subset, negative=True) # (0, a:b)(d) = (0, a+d) (or offset for indices) if isinstance(a_subset, subsets.Indices): tmp = copy.deepcopy(a_subset) tmp.offset(b3_subset, negative=False) e3.data.subset = tmp else: e3.data.subset = a_subset.compose(b3_subset) e3.data.other_subset = other_subset # 2-c. Remove edge and add new one graph.remove_edge(e2) graph.add_edge(in_array, e2.src_conn, e2.dst, e2.dst_conn, e2.data) # Finally, remove out_array node graph.remove_node(out_array) # TODO: Should the array be removed from the SDFG? # del sdfg.arrays[out_array] if Config.get_bool("debugprint"): RedundantSecondArray._arrays_removed += 1
def can_be_applied(graph, candidate, expr_index, sdfg, strict=False): in_array = graph.nodes()[candidate[RedundantSecondArray._in_array]] out_array = graph.nodes()[candidate[RedundantSecondArray._out_array]] in_desc = in_array.desc(sdfg) out_desc = out_array.desc(sdfg) # Ensure in degree is one (only one source, which is in_array) if graph.in_degree(out_array) != 1: return False # Make sure that the candidate is a transient variable if not out_desc.transient: return False # Dimensionality must be the same in strict mode if strict and len(in_desc.shape) != len(out_desc.shape): return False # Make sure that both arrays are using the same storage location # and are of the same type (e.g., Stream->Stream) if in_desc.storage != out_desc.storage: return False if type(in_desc) != type(out_desc): return False # Find occurrences in this and other states occurrences = [] for state in sdfg.nodes(): occurrences.extend([ n for n in state.nodes() if isinstance(n, nodes.AccessNode) and n.desc(sdfg) == out_desc ]) for isedge in sdfg.edges(): if out_array.data in isedge.data.free_symbols: occurrences.append(isedge) if len(occurrences) > 1: return False # Check whether the data copied from the first datanode cover # the subsets of all the output edges of the second datanode. # We assume the following pattern: A -- e1 --> B -- e2 --> others # 1. Get edge e1 and extract/validate subsets for arrays A and B e1 = graph.edges_between(in_array, out_array)[0] try: _, b1_subset = _validate_subsets(e1, sdfg.arrays) except NotImplementedError: return False # 2. Iterate over the e2 edges for e2 in graph.out_edges(out_array): # 2-a. Extract/validate subsets for array B and others try: b2_subset, _ = _validate_subsets(e2, sdfg.arrays) except NotImplementedError: return False # 2-b. Check where b1_subset covers b2_subset if not b1_subset.covers(b2_subset): return False # 2-c. Validate subsets in memlet tree # (should not be needed for valid SDGs) path = graph.memlet_tree(e2) for e3 in path: if e3 is not e2: try: _validate_subsets(e3, sdfg.arrays, src_name=out_array.data) except NotImplementedError: return False return True
def apply(self, sdfg): def gnode(nname): return graph.nodes()[self.subgraph[nname]] graph = sdfg.nodes()[self.state_id] in_array = gnode(RedundantSecondArray._in_array) out_array = gnode(RedundantSecondArray._out_array) in_desc = sdfg.arrays[in_array.data] out_desc = sdfg.arrays[out_array.data] # We assume the following pattern: A -- e1 --> B -- e2 --> others # 1. Get edge e1 and extract subsets for arrays A and B e1 = graph.edges_between(in_array, out_array)[0] a_subset, b1_subset = _validate_subsets(e1, sdfg.arrays) # Find extraneous A or B subset dimensions a_dims_to_pop = [] b_dims_to_pop = [] aset = a_subset popped = [] if a_subset and b1_subset and a_subset.dims() != b1_subset.dims(): a_size = a_subset.size_exact() b_size = b1_subset.size_exact() if a_subset.dims() > b1_subset.dims(): a_dims_to_pop = find_dims_to_pop(a_size, b_size) aset, popped = pop_dims(a_subset, a_dims_to_pop) else: b_dims_to_pop = find_dims_to_pop(b_size, a_size) # If the src subset does not cover the removed array, create a view. if a_subset and any(m != a for m, a in zip(a_subset.size(), out_desc.shape)): # NOTE: We do not want to create another view, if the immediate # successors of out_array are views as well. We just remove it. out_successors_desc = [ e.dst.desc(sdfg) if isinstance(e.dst, nodes.AccessNode) else None for e in graph.out_edges(out_array) ] if all([ desc and isinstance(desc, data.View) for desc in out_successors_desc ]): for e in graph.out_edges(out_array): _, b_subset = _validate_subsets(e, sdfg.arrays) graph.add_edge( in_array, None, e.dst, e.dst_conn, mm.Memlet(in_array.data, subset=a_subset, other_subset=b_subset, wcr=e1.data.wcr, wcr_nonatomic=e1.data.wcr_nonatomic)) graph.remove_edge(e) graph.remove_edge(e1) graph.remove_node(out_array) if out_array.data in sdfg.arrays: del sdfg.arrays[out_array.data] return view_strides = out_desc.strides if (a_dims_to_pop and len(a_dims_to_pop) == len(in_desc.shape) - len(out_desc.shape)): view_strides = [ s for i, s in enumerate(in_desc.strides) if i not in a_dims_to_pop ] sdfg.arrays[out_array.data] = data.View( out_desc.dtype, out_desc.shape, True, out_desc.allow_conflicts, in_desc.storage, in_desc.location, view_strides, out_desc.offset, in_desc.may_alias, dtypes.AllocationLifetime.Scope, out_desc.alignment, out_desc.debuginfo, out_desc.total_size) return # 2. Iterate over the e2 edges and traverse the memlet tree for e2 in graph.out_edges(out_array): path = graph.memlet_tree(e2) wcr = e1.data.wcr wcr_nonatomic = e1.data.wcr_nonatomic for e3 in path: # 2-a. Extract subsets for array B and others b3_subset, other_subset = _validate_subsets( e3, sdfg.arrays, src_name=out_array.data) # 2-b. Modify memlet to match array A. Example: # A -- (0, a:b)/(c:c+b) --> B -- (c+d)/None --> others # A -- (0, a+d)/None --> others e3.data.data = in_array.data # (c+d) - (c:c+b) = (d) b3_subset.offset(b1_subset, negative=True) # (0, a:b)(d) = (0, a+d) (or offset for indices) if b3_subset and b_dims_to_pop: bset, _ = pop_dims(b3_subset, b_dims_to_pop) else: bset = b3_subset e3.data.subset = compose_and_push_back(aset, bset, a_dims_to_pop, popped) # NOTE: This fixes the following case: # A ----> A[subset] ----> ... -----> Tasklet # Tasklet is not data, so it doesn't have an other subset. if isinstance(e3.dst, nodes.AccessNode): e3.data.other_subset = other_subset else: e3.data.other_subset = None wcr = wcr or e3.data.wcr wcr_nonatomic = wcr_nonatomic or e3.data.wcr_nonatomic e3.data.wcr = wcr e3.data.wcr_nonatomic = wcr_nonatomic # 2-c. Remove edge and add new one graph.remove_edge(e2) e2.data.wcr = wcr e2.data.wcr_nonatomic = wcr_nonatomic graph.add_edge(in_array, e2.src_conn, e2.dst, e2.dst_conn, e2.data) # Finally, remove out_array node graph.remove_node(out_array) if out_array.data in sdfg.arrays: try: sdfg.remove_data(out_array.data) except ValueError: # Already in use (e.g., with Views) pass
def can_be_applied(graph, candidate, expr_index, sdfg, strict=False): in_array = graph.nodes()[candidate[RedundantSecondArray._in_array]] out_array = graph.nodes()[candidate[RedundantSecondArray._out_array]] in_desc = in_array.desc(sdfg) out_desc = out_array.desc(sdfg) # Ensure in degree is one (only one source, which is in_array) if graph.in_degree(out_array) != 1: return False # Make sure that the candidate is a transient variable if not out_desc.transient: return False # 1. Get edge e1 and extract/validate subsets for arrays A and B e1 = graph.edges_between(in_array, out_array)[0] a_subset, b1_subset = _validate_subsets(e1, sdfg.arrays) if strict: # In strict mode, make sure the memlet covers the removed array if not b1_subset: return False subset = copy.deepcopy(b1_subset) subset.squeeze() shape = [sz for sz in out_desc.shape if sz != 1] if any(m != a for m, a in zip(subset.size(), shape)): return False # NOTE: Library node check # The transformation must not apply in strict mode if out_array is # not a view, is input to a library node, and an access or a view # of in_desc is also output to the same library node. # The reason is that the application of the transformation will lead # to in_desc being both input and output of the library node. # We do not know if this is safe. # First find the true in_desc (in case in_array is a view). true_in_desc = in_desc if isinstance(in_desc, data.View): e = sdutil.get_view_edge(graph, in_array) if not e: return False true_in_desc = sdfg.arrays[e.dst.data] if not isinstance(out_desc, data.View): edges_to_check = [] for a in graph.out_edges(out_array): if isinstance(a.dst, nodes.LibraryNode): edges_to_check.append(a) elif (isinstance(a.dst, nodes.AccessNode) and isinstance(sdfg.arrays[a.dst.data], data.View)): for b in graph.out_edges(a.dst): edges_to_check.append(graph.memlet_path(b)[-1]) for a in edges_to_check: if isinstance(a.dst, nodes.LibraryNode): for b in graph.out_edges(a.dst): if isinstance(b.dst, nodes.AccessNode): desc = sdfg.arrays[b.dst.data] if isinstance(desc, data.View): e = sdutil.get_view_edge(graph, b.dst) if not e: return False desc = sdfg.arrays[e.dst.data] if desc is true_in_desc: return False # In strict mode, check if the state has two or more access nodes # for in_array and at least one of them is a write access. There # might be a RW, WR, or WW dependency. accesses = [ n for n in graph.nodes() if isinstance(n, nodes.AccessNode) and n.desc(sdfg) == in_desc and n is not in_array ] if len(accesses) > 0: if (graph.in_degree(in_array) > 0 or any(graph.in_degree(a) > 0 for a in accesses)): # We need to ensure that a data race will not happen if we # remove in_array. # First, we simplify the graph G = helpers.simplify_state(graph) # Loop over the accesses for a in accesses: subsets_intersect = False for e in graph.in_edges(a): _, subset = _validate_subsets(e, sdfg.arrays, dst_name=a.data) res = subsets.intersects(a_subset, subset) if res == True or res is None: subsets_intersect = True break if not subsets_intersect: continue try: has_bward_path = nx.has_path(G, a, in_array) except NodeNotFound: has_bward_path = nx.has_path(graph.nx, a, in_array) try: has_fward_path = nx.has_path(G, in_array, a) except NodeNotFound: has_fward_path = nx.has_path(graph.nx, in_array, a) # If there is no path between the access nodes # (disconnected components), then it is definitely # possible to have data races. Abort. if not (has_bward_path or has_fward_path): return False # If there is a forward path then a must not be a direct # successor of in_array. if has_fward_path and a in G.successors(in_array): for src, _ in G.in_edges(a): if src is in_array: continue if (nx.has_path(G, in_array, src) and src != out_array): continue return False # Make sure that both arrays are using the same storage location # and are of the same type (e.g., Stream->Stream) if in_desc.storage != out_desc.storage: return False if in_desc.location != out_desc.location: return False if type(in_desc) != type(out_desc): if isinstance(in_desc, data.View): # Case View -> Access # If the View points to the Access (and has a different shape?) # then we should (probably) not remove the Access. e = sdutil.get_view_edge(graph, in_array) if e and e.dst is out_array and in_desc.shape != out_desc.shape: return False # Check that the View's immediate ancestors are Accesses. # Otherwise, the application of the transformation will result # in an ambiguous View. view_ancestors_desc = [ e.src.desc(sdfg) if isinstance(e.src, nodes.AccessNode) else None for e in graph.in_edges(in_array) ] if any([ not desc or isinstance(desc, data.View) for desc in view_ancestors_desc ]): return False elif isinstance(out_desc, data.View): # Case Access -> View # If the View points to the Access and has the same shape, # it can be removed e = sdutil.get_view_edge(graph, out_array) if e and e.src is in_array and in_desc.shape == out_desc.shape: return True return False else: # Something else, for example, Stream return False else: # Two views connected to each other if isinstance(in_desc, data.View): return False # Find occurrences in this and other states occurrences = [] for state in sdfg.nodes(): occurrences.extend([ n for n in state.nodes() if isinstance(n, nodes.AccessNode) and n.desc(sdfg) == out_desc ]) for isedge in sdfg.edges(): if out_array.data in isedge.data.free_symbols: occurrences.append(isedge) if len(occurrences) > 1: return False # Check whether the data copied from the first datanode cover # the subsets of all the output edges of the second datanode. # We assume the following pattern: A -- e1 --> B -- e2 --> others # 2. Iterate over the e2 edges for e2 in graph.out_edges(out_array): # 2-a. Extract/validate subsets for array B and others try: b2_subset, _ = _validate_subsets(e2, sdfg.arrays) except NotImplementedError: return False # 2-b. Check where b1_subset covers b2_subset if not b1_subset.covers(b2_subset): return False # 2-c. Validate subsets in memlet tree # (should not be needed for valid SDGs) path = graph.memlet_tree(e2) for e3 in path: if e3 is not e2: try: _validate_subsets(e3, sdfg.arrays, src_name=out_array.data) except NotImplementedError: return False return True
def apply(self, sdfg): graph = sdfg.nodes()[self.state_id] in_array = self.in_array(sdfg) out_array = self.out_array(sdfg) in_desc = sdfg.arrays[in_array.data] out_desc = sdfg.arrays[out_array.data] # 1. Get edge e1 and extract subsets for arrays A and B e1 = graph.edges_between(in_array, out_array)[0] a1_subset, b_subset = _validate_subsets(e1, sdfg.arrays) # View connected to a view: simple case if (isinstance(in_desc, data.View) and isinstance(out_desc, data.View)): for e in graph.in_edges(in_array): new_memlet = copy.deepcopy(e.data) e.dst_subset = b_subset graph.add_edge(e.src, e.src_conn, out_array, e.dst_conn, new_memlet) graph.remove_node(in_array) if in_array.data in sdfg.arrays: del sdfg.arrays[in_array.data] return # Find extraneous A or B subset dimensions a_dims_to_pop = [] b_dims_to_pop = [] bset = b_subset popped = [] if a1_subset and b_subset and a1_subset.dims() != b_subset.dims(): a_size = a1_subset.size_exact() b_size = b_subset.size_exact() if a1_subset.dims() > b_subset.dims(): a_dims_to_pop = find_dims_to_pop(a_size, b_size) else: b_dims_to_pop = find_dims_to_pop(b_size, a_size) bset, popped = pop_dims(b_subset, b_dims_to_pop) from dace.libraries.standard import Reduce reduction = False for e in graph.in_edges(in_array): if isinstance(e.src, Reduce): reduction = True # If: # 1. A reduce node is involved; # 2. The memlet does not cover the removed array; or # 3. Dimensions are mismatching (all dimensions are popped); # create a view. if reduction or len(a_dims_to_pop) == len(in_desc.shape) or any( m != a for m, a in zip(a1_subset.size(), in_desc.shape)): self._make_view(sdfg, graph, in_array, out_array, e1, b_subset, b_dims_to_pop) return # Validate that subsets are composable. If not, make a view try: for e2 in graph.in_edges(in_array): path = graph.memlet_tree(e2) wcr = e1.data.wcr wcr_nonatomic = e1.data.wcr_nonatomic for e3 in path: # 2-a. Extract subsets for array B and others other_subset, a3_subset = _validate_subsets( e3, sdfg.arrays, dst_name=in_array.data) # 2-b. Modify memlet to match array B. dname = out_array.data src_is_data = False a3_subset.offset(a1_subset, negative=True) if a3_subset and a_dims_to_pop: aset, _ = pop_dims(a3_subset, a_dims_to_pop) else: aset = a3_subset compose_and_push_back(bset, aset, b_dims_to_pop, popped) except (ValueError, NotImplementedError): self._make_view(sdfg, graph, in_array, out_array, e1, b_subset, b_dims_to_pop) return # 2. Iterate over the e2 edges and traverse the memlet tree for e2 in graph.in_edges(in_array): path = graph.memlet_tree(e2) wcr = e1.data.wcr wcr_nonatomic = e1.data.wcr_nonatomic for e3 in path: # 2-a. Extract subsets for array B and others other_subset, a3_subset = _validate_subsets( e3, sdfg.arrays, dst_name=in_array.data) # 2-b. Modify memlet to match array B. dname = out_array.data src_is_data = False a3_subset.offset(a1_subset, negative=True) if a3_subset and a_dims_to_pop: aset, _ = pop_dims(a3_subset, a_dims_to_pop) else: aset = a3_subset dst_subset = compose_and_push_back(bset, aset, b_dims_to_pop, popped) # NOTE: This fixes the following case: # Tasklet ----> A[subset] ----> ... -----> A # Tasklet is not data, so it doesn't have an other subset. if isinstance(e3.src, nodes.AccessNode): if e3.src.data == out_array.data: dname = e3.src.data src_is_data = True src_subset = other_subset else: src_subset = None subset = src_subset if src_is_data else dst_subset other_subset = dst_subset if src_is_data else src_subset e3.data.data = dname e3.data.subset = subset e3.data.other_subset = other_subset wcr = wcr or e3.data.wcr wcr_nonatomic = wcr_nonatomic or e3.data.wcr_nonatomic e3.data.wcr = wcr e3.data.wcr_nonatomic = wcr_nonatomic # 2-c. Remove edge and add new one graph.remove_edge(e2) e2.data.wcr = wcr e2.data.wcr_nonatomic = wcr_nonatomic graph.add_edge(e2.src, e2.src_conn, out_array, e2.dst_conn, e2.data) # Finally, remove in_array node graph.remove_node(in_array) try: if in_array.data in sdfg.arrays: sdfg.remove_data(in_array.data) except ValueError: # Already in use (e.g., with Views) pass
def apply(self, sdfg): graph = sdfg.nodes()[self.state_id] in_array = self.in_array(sdfg) out_array = self.out_array(sdfg) in_desc = sdfg.arrays[in_array.data] out_desc = sdfg.arrays[out_array.data] # 1. Get edge e1 and extract subsets for arrays A and B e1 = graph.edges_between(in_array, out_array)[0] a1_subset, b_subset = _validate_subsets(e1, sdfg.arrays) # Find extraneous A or B subset dimensions a_dims_to_pop = [] b_dims_to_pop = [] bset = b_subset popped = [] if a1_subset and b_subset and a1_subset.dims() != b_subset.dims(): a_size = a1_subset.size_exact() b_size = b_subset.size_exact() if a1_subset.dims() > b_subset.dims(): a_dims_to_pop = find_dims_to_pop(a_size, b_size) else: b_dims_to_pop = find_dims_to_pop(b_size, a_size) bset, popped = pop_dims(b_subset, b_dims_to_pop) from dace.libraries.standard import Reduce reduction = False for e in graph.in_edges(in_array): if isinstance(e.src, Reduce): reduction = True # If the memlet does not cover the removed array, create a view. if reduction or any(m != a for m, a in zip(a1_subset.size(), in_desc.shape)): # NOTE: We do not want to create another view, if the immediate # ancestors of in_array are views as well. We just remove it. in_ancestors_desc = [ e.src.desc(sdfg) if isinstance(e.src, nodes.AccessNode) else None for e in graph.in_edges(in_array) ] if all([ desc and isinstance(desc, data.View) for desc in in_ancestors_desc ]): for e in graph.in_edges(in_array): a_subset, _ = _validate_subsets(e, sdfg.arrays) graph.add_edge( e.src, e.src_conn, out_array, None, mm.Memlet(out_array.data, subset=b_subset, other_subset=a_subset, wcr=e1.data.wcr, wcr_nonatomic=e1.data.wcr.nonatomic)) graph.remove_edge(e) graph.remove_edge(e1) graph.remove_node(in_array) if in_array.data in sdfg.arrays: del sdfg.arrays[in_array.data] return view_strides = in_desc.strides if (b_dims_to_pop and len(b_dims_to_pop) == len(out_desc.shape) - len(in_desc.shape)): view_strides = [ s for i, s in enumerate(out_desc.strides) if i not in b_dims_to_pop ] sdfg.arrays[in_array.data] = data.View( in_desc.dtype, in_desc.shape, True, in_desc.allow_conflicts, out_desc.storage, out_desc.location, view_strides, in_desc.offset, out_desc.may_alias, dtypes.AllocationLifetime.Scope, in_desc.alignment, in_desc.debuginfo, in_desc.total_size) return # 2. Iterate over the e2 edges and traverse the memlet tree for e2 in graph.in_edges(in_array): path = graph.memlet_tree(e2) wcr = e1.data.wcr wcr_nonatomic = e1.data.wcr_nonatomic for e3 in path: # 2-a. Extract subsets for array B and others other_subset, a3_subset = _validate_subsets( e3, sdfg.arrays, dst_name=in_array.data) # 2-b. Modify memlet to match array B. dname = out_array.data src_is_data = False a3_subset.offset(a1_subset, negative=True) if a3_subset and a_dims_to_pop: aset, _ = pop_dims(a3_subset, a_dims_to_pop) else: aset = a3_subset dst_subset = compose_and_push_back(bset, aset, b_dims_to_pop, popped) # NOTE: This fixes the following case: # Tasklet ----> A[subset] ----> ... -----> A # Tasklet is not data, so it doesn't have an other subset. if isinstance(e3.src, nodes.AccessNode): if e3.src.data == out_array.data: dname = e3.src.data src_is_data = True src_subset = other_subset else: src_subset = None subset = src_subset if src_is_data else dst_subset other_subset = dst_subset if src_is_data else src_subset e3.data.data = dname e3.data.subset = subset e3.data.other_subset = other_subset wcr = wcr or e3.data.wcr wcr_nonatomic = wcr_nonatomic or e3.data.wcr_nonatomic e3.data.wcr = wcr e3.data.wcr_nonatomic = wcr_nonatomic # 2-c. Remove edge and add new one graph.remove_edge(e2) e2.data.wcr = wcr e2.data.wcr_nonatomic = wcr_nonatomic graph.add_edge(e2.src, e2.src_conn, out_array, e2.dst_conn, e2.data) # Finally, remove in_array node graph.remove_node(in_array) if in_array.data in sdfg.arrays: del sdfg.arrays[in_array.data]