def __init__(self, exprs, **kwargs): # Check input legality mapper = OrderedDict([(i.lhs, i) for i in exprs]) if len(set(mapper)) != len(mapper): raise DSEException( "Found redundant node, cannot build TemporariesGraph.") # Construct Temporaries, tracking reads and readby tensor_map = DefaultOrderedDict(list) for i in mapper: tensor_map[as_symbol(i)].append(i) reads = DefaultOrderedDict(set) readby = DefaultOrderedDict(set) for k, v in mapper.items(): handle = retrieve_terminals(v.rhs) for i in list(handle): if i.is_Indexed: for idx in i.indices: handle |= retrieve_terminals(idx) reads[k].update( set(flatten([tensor_map.get(as_symbol(i), []) for i in handle]))) for i in reads[k]: readby[i].add(k) # Make sure read-after-writes are honored for scalar temporaries processed = [i for i in mapper if i.is_Indexed] queue = [i for i in mapper if i not in processed] while queue: k = queue.pop(0) if not readby[k]: processed.insert(0, k) elif all(i in processed for i in readby[k]): index = min(processed.index(i) for i in readby[k]) processed.insert(index, k) else: queue.append(k) # Build up the TemporariesGraph temporaries = [(i, Temporary(*mapper[i].args, inc=q_inc(mapper[i]), reads=reads[i], readby=readby[i])) for i in processed] super(TemporariesGraph, self).__init__(temporaries, **kwargs) # Determine indices along the space and time dimensions terms = [ v for k, v in self.items() if v.is_tensor and not q_indirect(k) ] indices = filter_ordered(flatten([i.function.indices for i in terms])) self.space_indices = tuple(i for i in indices if i.is_Space) self.time_indices = tuple(i for i in indices if i.is_Time)
def __init__(self, exprs, **kwargs): # Always convert to SSA exprs = convert_to_SSA(exprs) mapper = OrderedDict([(i.lhs, i) for i in exprs]) assert len(set(mapper)) == len(exprs), "not SSA Cluster?" # Construct the Nodes, tracking reads and readby tensor_map = DefaultOrderedDict(list) for i in mapper: tensor_map[as_symbol(i)].append(i) reads = DefaultOrderedDict(set) readby = DefaultOrderedDict(set) for k, v in mapper.items(): handle = retrieve_terminals(v.rhs) for i in list(handle): if i.is_Indexed: for idx in i.indices: handle |= retrieve_terminals(idx) reads[k].update( set(flatten([tensor_map.get(as_symbol(i), []) for i in handle]))) for i in reads[k]: readby[i].add(k) # Make sure read-after-writes are honored for scalar temporaries processed = [i for i in mapper if i.is_Indexed] queue = [i for i in mapper if i not in processed] while queue: k = queue.pop(0) if not readby[k]: processed.insert(0, k) elif all(i in processed for i in readby[k]): index = min(processed.index(i) for i in readby[k]) processed.insert(index, k) else: queue.append(k) # Build up the FlowGraph temporaries = [(i, Node(*mapper[i].args, inc=q_inc(mapper[i]), reads=reads[i], readby=readby[i])) for i in processed] super(FlowGraph, self).__init__(temporaries, **kwargs) # Determine indices along the space and time dimensions terms = [ v for k, v in self.items() if v.is_tensor and not q_indirect(k) ] indices = filter_ordered(flatten([i.function.indices for i in terms])) self.space_indices = tuple(i for i in indices if i.is_Space) self.time_indices = tuple(i for i in indices if i.is_Time)
def __init__(self, exprs, rules=None): """ A Scope enables data dependence analysis on a totally ordered sequence of expressions. """ exprs = as_tuple(exprs) self.reads = {} self.writes = {} self.initialized = set() for i, e in enumerate(exprs): # Reads for j in retrieve_terminals(e.rhs): v = self.reads.setdefault(j.function, []) mode = 'RI' if e.is_Increment and j.function is e.lhs.function else 'R' v.append(TimedAccess(j, mode, i, e.ispace)) # Write if q_terminal(e.lhs): v = self.writes.setdefault(e.lhs.function, []) mode = 'WI' if e.is_Increment else 'W' v.append(TimedAccess(e.lhs, mode, i, e.ispace)) # If an increment, we got one implicit read if e.is_Increment: v = self.reads.setdefault(e.lhs.function, []) v.append(TimedAccess(e.lhs, 'RI', i, e.ispace)) # If writing to a scalar, we have an initialization if not e.is_Increment and e.is_scalar: self.initialized.add(e.lhs.function) # Look up ConditionalDimensions for v in e.conditionals.values(): for j in retrieve_terminals(v): v = self.reads.setdefault(j.function, []) v.append(TimedAccess(j, 'R', -1, e.ispace)) # The iteration symbols too dimensions = set().union(*[e.dimensions for e in exprs]) for d in dimensions: for i in d._defines_symbols: for j in i.free_symbols: v = self.reads.setdefault(j.function, []) v.append(TimedAccess(j, 'R', -1)) # A set of rules to drive the collection of dependencies self.rules = as_tuple(rules) assert all(callable(i) for i in self.rules)
def _retrieve_symbols(self, expressions): """ Retrieve the symbolic functions read or written by the Operator, as well as all traversed dimensions. """ terms = flatten(retrieve_terminals(i) for i in expressions) input = [] for i in terms: try: input.append(i.base.function) except AttributeError: pass input = filter_sorted(input, key=attrgetter('name')) output = [i.lhs.base.function for i in expressions if i.lhs.is_Indexed] indexeds = [i for i in terms if i.is_Indexed] dimensions = [] for indexed in indexeds: for i in indexed.indices: dimensions.extend( [k for k in i.free_symbols if isinstance(k, Dimension)]) dimensions.extend(list(indexed.base.function.indices)) dimensions.extend([d.parent for d in dimensions if d.is_Stepping]) dimensions = filter_sorted(dimensions, key=attrgetter('name')) return input, output, dimensions
def detect_free_dimensions(expr): """ Return a degenerate :class:`Stencil` for the :class:`Dimension`s used as plain symbols, rather than as array indices, in ``expr``. """ return Stencil([(i, 0) for i in retrieve_terminals(expr) if isinstance(i, Dimension)])
def detect_accesses(expr): """ Return a mapper ``M : F -> S``, where F are Functions appearing in ``expr`` and S are Stencils. ``M[f]`` represents all data accesses to ``f`` within ``expr``. Also map ``M[None]`` to all Dimensions used in ``expr`` as plain symbols, rather than as array indices. """ # Compute M : F -> S mapper = defaultdict(Stencil) for e in retrieve_indexed(expr, mode='all', deep=True): f = e.function for a in e.indices: if isinstance(a, Dimension): mapper[f][a].update([0]) d = None off = [] for i in a.args: if isinstance(i, Dimension): d = i elif i.is_integer: off += [int(i)] if d is not None: mapper[f][d].update(off or [0]) # Compute M[None] other_dims = [i for i in retrieve_terminals(expr) if isinstance(i, Dimension)] other_dims.extend(list(expr.implicit_dims)) mapper[None] = Stencil([(i, 0) for i in other_dims]) return mapper
def detect_accesses(expr): """ Return a mapper ``M : F -> S``, where F are Functions appearing in ``expr`` and S are Stencils. ``M[f]`` represents all data accesses to ``f`` within ``expr``. Also map ``M[None]`` to all Dimensions used in ``expr`` as plain symbols, rather than as array indices. """ # Compute M : F -> S mapper = defaultdict(Stencil) for e in retrieve_indexed(expr, deep=True): f = e.function for a in e.indices: if isinstance(a, Dimension): mapper[f][a].update([0]) d = None off = [] for i in a.args: if isinstance(i, Dimension): d = i elif i.is_integer: off += [int(i)] if d is not None: mapper[f][d].update(off or [0]) # Compute M[None] other_dims = [i for i in retrieve_terminals(expr) if isinstance(i, Dimension)] other_dims.extend(list(expr.implicit_dims)) mapper[None] = Stencil([(i, 0) for i in other_dims]) return mapper
def __init__(self, exprs): """ A Scope enables data dependence analysis on a totally ordered sequence of expressions. """ exprs = as_tuple(exprs) self.reads = {} self.writes = {} for i, e in enumerate(exprs): # Reads for j in retrieve_terminals(e.rhs): v = self.reads.setdefault(j.function, []) mode = 'RI' if e.is_Increment and j.function is e.lhs.function else 'R' v.append(TimedAccess(j, mode, i, e.ispace)) # Write v = self.writes.setdefault(e.lhs.function, []) mode = 'WI' if e.is_Increment else 'W' v.append(TimedAccess(e.lhs, mode, i, e.ispace)) # If an increment, we got one implicit read if e.is_Increment: v = self.reads.setdefault(e.lhs.function, []) v.append(TimedAccess(e.lhs, 'RI', i, e.ispace)) # The iterators read symbols too dimensions = set().union(*[e.dimensions for e in exprs]) for d in dimensions: for j in d.symbolic_size.free_symbols: v = self.reads.setdefault(j.function, []) v.append(TimedAccess(j, 'R', -1))
def __init__(self, exprs): """ A Scope represents a group of TimedAcces objects extracted from some IREq ``exprs``. The expressions must be provided in program order. """ exprs = as_tuple(exprs) self.reads = {} self.writes = {} for i, e in enumerate(exprs): # Reads for j in retrieve_terminals(e.rhs): v = self.reads.setdefault(j.function, []) mode = 'RI' if e.is_Increment and j.function is e.lhs.function else 'R' v.append(TimedAccess(j, mode, i, e.ispace.directions)) # Write v = self.writes.setdefault(e.lhs.function, []) mode = 'WI' if e.is_Increment else 'W' v.append(TimedAccess(e.lhs, mode, i, e.ispace.directions)) # If an increment, we got one implicit read if e.is_Increment: v = self.reads.setdefault(e.lhs.function, []) v.append(TimedAccess(e.lhs, 'RI', i, e.ispace.directions)) # The iterators read symbols too dimensions = set().union(*[e.dimensions for e in exprs]) for d in dimensions: for j in d.symbolic_size.free_symbols: v = self.reads.setdefault(j.function, []) v.append(TimedAccess(j, 'R', -1, {}))
def retrieve_symbols(expressions): """ Return the :class:`Function` and :class:`Dimension` objects appearing in ``expressions``. """ terms = flatten(retrieve_terminals(i) for i in expressions) input = [] for i in terms: try: function = i.base.function except AttributeError: continue if function.is_Constant or function.is_TensorFunction: input.append(function) input = filter_sorted(input, key=attrgetter('name')) output = [i.lhs.base.function for i in expressions if i.lhs.is_Indexed] output = filter_sorted(output, key=attrgetter('name')) indexeds = [i for i in terms if i.is_Indexed] dimensions = [] for indexed in indexeds: for i in indexed.indices: dimensions.extend( [k for k in i.free_symbols if isinstance(k, Dimension)]) dimensions.extend(list(indexed.base.function.indices)) dimensions.extend([d.parent for d in dimensions if d.is_Stepping]) dimensions = filter_sorted(dimensions, key=attrgetter('name')) return input, output, dimensions
def is_time_invariant(mapper, expr): if any( isinstance(i, Dimension) and i.is_Time for i in expr.free_symbols): return False queue = [expr.rhs if expr.is_Equality else expr] seen = set() while queue: item = queue.pop() nodes = set() for i in retrieve_terminals(item): if i in seen: # Already inspected, nothing more can be inferred continue elif any( isinstance(j, Dimension) and j.is_Time for j in i.free_symbols): # Definitely not time-invariant return False elif i in mapper: # Go on with the search nodes.add(i) elif isinstance(i, Dimension): # Go on with the search, as `i` is not a time dimension pass elif not (i.function.is_DiscreteFunction or i.function.is_Symbol and i.function.is_const): # It didn't come from the outside and it's not in `mapper`, so # cannot determine if time-invariant; assume time-varying then return False seen.add(i) queue.extend([mapper[i].rhs for i in nodes]) return True
def topological_sort(exprs): """Topologically sort the temporaries in a list of equations.""" mapper = {e.lhs: e for e in exprs} assert len(mapper) == len(exprs) # Expect SSA # Build DAG and topologically-sort temporaries temporaries, tensors = split(exprs, lambda e: not e.lhs.is_Indexed) dag = DAG(nodes=temporaries) for e in temporaries: for r in retrieve_terminals(e.rhs): if r not in mapper: continue elif mapper[r] is e: # Avoid cyclic dependences, such as # Eq(f, f + 1) continue elif r.is_Indexed: # Only scalars enforce an ordering continue else: dag.add_edge(mapper[r], e, force_add=True) processed = dag.topological_sort() # Append tensor equations at the end in user-provided order processed.extend(tensors) return processed
def unknown(self): """ Return all symbols appearing in self for which a node is not available. """ known = {v.function for v in self.values()} reads = set([i.function for i in flatten(retrieve_terminals(v.rhs) for v in self.values())]) return reads - known
def unknown(self): """ Return all symbols appearing in self for which a temporary is not available. """ known = {v.function for v in self.values()} reads = set([i.base.function for i in flatten(retrieve_terminals(v.rhs) for v in self.values())]) return reads - known
def __init__(self, exprs, **kwargs): # Always convert to SSA exprs = makeit_ssa(exprs) mapper = OrderedDict([(i.lhs, i) for i in exprs]) assert len(set(mapper)) == len(exprs), "not SSA Cluster?" # Construct the Nodes, tracking reads and readby tensor_map = DefaultOrderedDict(list) for i in mapper: tensor_map[as_symbol(i)].append(i) reads = DefaultOrderedDict(set) readby = DefaultOrderedDict(set) for k, v in mapper.items(): handle = retrieve_terminals(v.rhs) for i in list(handle): if i.is_Indexed: for idx in i.indices: handle |= retrieve_terminals(idx) reads[k].update(set(flatten([tensor_map.get(as_symbol(i), []) for i in handle]))) for i in reads[k]: readby[i].add(k) # Make sure read-after-writes are honored for scalar nodes processed = [i for i in mapper if i.is_Indexed] queue = [i for i in mapper if i not in processed] while queue: k = queue.pop(0) if not readby[k] or k in readby[k]: processed.insert(0, k) elif all(i in processed for i in readby[k]): index = min(processed.index(i) for i in readby[k]) processed.insert(index, k) else: queue.append(k) # Build up the FlowGraph nodes = [(i, Node(mapper[i], reads=reads[i], readby=readby[i])) for i in processed] super(FlowGraph, self).__init__(nodes, **kwargs) # Determine indices along the space and time dimensions terms = [v for k, v in self.items() if v.is_Tensor and not q_indirect(k)] indices = filter_ordered(flatten([i.function.indices for i in terms])) self.space_indices = tuple(i for i in indices if i.is_Space) self.time_indices = tuple(i for i in indices if i.is_Time)
def detect_io(exprs, relax=False): """ ``{exprs} -> ({reads}, {writes})`` Parameters ---------- exprs : expr-like or list of expr-like The searched expressions. relax : bool, optional If False, as by default, collect only Constants and Functions. Otherwise, collect any Basic object. """ exprs = as_tuple(exprs) if relax is False: rule = lambda i: i.is_Input else: rule = lambda i: i.is_Scalar or i.is_Tensor # Don't forget the nasty case with indirections on the LHS: # >>> u[t, a[x]] = f[x] -> (reads={a, f}, writes={u}) roots = [] for i in exprs: try: roots.append(i.rhs) roots.extend(list(i.lhs.indices)) roots.extend(list(i.conditionals.values())) except AttributeError: # E.g., FunctionFromPointer roots.append(i) reads = [] terminals = flatten(retrieve_terminals(i, deep=True) for i in roots) for i in terminals: candidates = i.free_symbols try: candidates.update({i.function}) except AttributeError: pass for j in candidates: try: if rule(j): reads.append(j) except AttributeError: pass writes = [] for i in exprs: try: f = i.lhs.function except AttributeError: continue if rule(f): writes.append(f) return filter_sorted(reads), filter_sorted(writes)
def detect_io(exprs, relax=False): """ ``{exprs} -> ({reads}, {writes})`` Parameters ---------- exprs : expr-like or list of expr-like The searched expressions. relax : bool, optional If False, as by default, collect only Constants and Functions. Otherwise, collect any Basic object. """ exprs = as_tuple(exprs) if relax is False: rule = lambda i: i.is_Input else: rule = lambda i: i.is_Scalar or i.is_Tensor # Don't forget this nasty case, with indirections on the LHS: # >>> u[t, a[x]] = f[x] -> (reads={a, f}, writes={u}) roots = [] for i in exprs: try: roots.append(i.rhs) roots.extend(list(i.lhs.indices)) except AttributeError: # E.g., FunctionFromPointer roots.append(i) reads = [] terminals = flatten(retrieve_terminals(i, deep=True) for i in roots) for i in terminals: candidates = i.free_symbols try: candidates.update({i.function}) except AttributeError: pass for j in candidates: try: if rule(j): reads.append(j) except AttributeError: pass writes = [] for i in exprs: try: f = i.lhs.function except AttributeError: continue if rule(f): writes.append(f) return filter_sorted(reads), filter_sorted(writes)
def __init__(self, expr, dtype=None): assert isinstance(expr, Eq) assert isinstance(expr.lhs, (Symbol, Indexed)) self.expr = expr self.dtype = dtype # Traverse /expression/ to determine meta information # Note: at this point, expressions have already been indexified self.reads = [i for i in retrieve_terminals(self.expr.rhs) if isinstance(i, (types.Indexed, types.Symbol))] self.reads = filter_ordered(self.reads) # Filter collected dimensions and functions self.dimensions = flatten(i.indices for i in self.functions) self.dimensions = filter_ordered(self.dimensions)
def extract(cls, expr): """ Compute the stencil of ``expr``. """ assert expr.is_Equality # Collect all indexed objects appearing in /expr/ terminals = retrieve_terminals(expr, mode='all') indexeds = [i for i in terminals if i.is_Indexed] indexeds += flatten([retrieve_indexed(i) for i in e.indices] for e in indexeds) # Enforce deterministic dimension ordering... dims = OrderedDict() for e in terminals: if isinstance(e, Dimension): dims[(e, )] = e elif e.is_Indexed: d = [] for a in e.indices: found = [ i for i in a.free_symbols if isinstance(i, Dimension) ] d.extend([i for i in found if i not in d]) dims[tuple(d)] = e # ... giving higher priority to TimeFunction objects; time always go first dims = sorted( list(dims), key=lambda i: not (isinstance(dims[i], Dimension) or dims[i].base. function.is_TimeFunction)) stencil = Stencil([(i, set()) for i in partial_order(dims)]) # Determine the points accessed along each dimension for e in indexeds: for a in e.indices: if isinstance(a, Dimension): stencil[a].update([0]) d = None off = [0] for i in a.args: if isinstance(i, Dimension): d = i elif i.is_integer: off += [i] if d is not None: stencil[d].update(off) return stencil
def __init__(self, exprs): """ A Scope enables data dependence analysis on a totally ordered sequence of expressions. A Scope may be used only if IterationSpaces and Function's Dimensions follow the same ordering. For example, given ``f(x, y, z)`` and ``g(x, y)``, the IterationSpace must be ``[x, y, z]`` (and not ``[x, z, y]``). In Devito, this is guaranteed by construction -- IterationSpaces are built from a deterministic partial ordering of Dimensions. This condition ensures that the lexicographic ordering of the iterations for the iteration vector ``(x, y, z)`` is ``(0, 0, 0), (0, 0, 1), ..., (0, 1, 0), (0, 1, 1), ...``, which greatly simplifies the collection and classification of data dependences. """ exprs = as_tuple(exprs) self.reads = {} self.writes = {} for i, e in enumerate(exprs): # Reads for j in retrieve_terminals(e.rhs): v = self.reads.setdefault(j.function, []) mode = 'RI' if e.is_Increment and j.function is e.lhs.function else 'R' v.append(TimedAccess(j, mode, i, e.ispace)) # Write v = self.writes.setdefault(e.lhs.function, []) mode = 'WI' if e.is_Increment else 'W' v.append(TimedAccess(e.lhs, mode, i, e.ispace)) # If an increment, we got one implicit read if e.is_Increment: v = self.reads.setdefault(e.lhs.function, []) v.append(TimedAccess(e.lhs, 'RI', i, e.ispace)) # The iterators read symbols too dimensions = set().union(*[e.dimensions for e in exprs]) for d in dimensions: for j in d.symbolic_size.free_symbols: v = self.reads.setdefault(j.function, []) v.append(TimedAccess(j, 'R', -1))
def detect_io(exprs, relax=False): """ ``{exprs} -> ({reads}, {writes}) Parameters ---------- exprs : expr-like or list of expr-like The searched expressions. relax : bool, optional If False, as by default, collect only :class:`Constant`s and :class:`Function`s. Otherwise, collect any :class:`types.Basic`s. """ exprs = as_tuple(exprs) if relax is False: rule = lambda i: i.is_Input else: rule = lambda i: i.is_Scalar or i.is_Tensor reads = [] for i in flatten(retrieve_terminals(i, deep=True) for i in exprs): candidates = i.free_symbols try: candidates.update({i.base.function}) except AttributeError: pass for j in candidates: try: if rule(j): reads.append(j) except AttributeError: pass writes = [] for i in exprs: try: f = i.lhs.base.function except AttributeError: continue if rule(f): writes.append(f) return filter_sorted(reads), filter_sorted(writes)
def __init__(self, exprs): """ A Scope represents a group of :class:`TimedAccess` objects extracted from some :class:`IREq` ``exprs``. The expressions must be provided in program order. """ exprs = as_tuple(exprs) self.reads = {} self.writes = {} for i, e in enumerate(exprs): # reads for j in retrieve_terminals(e.rhs): v = self.reads.setdefault(j.base.function, []) mode = 'R' if not q_inc(e) else 'RI' v.append(TimedAccess(j, mode, i, e.ispace.directions)) # write v = self.writes.setdefault(e.lhs.base.function, []) mode = 'W' if not q_inc(e) else 'WI' v.append(TimedAccess(e.lhs, mode, i, e.ispace.directions))
def __init__(self, exprs): """ Initialize a Scope, which represents a group of :class:`Access` objects extracted from some expressions ``exprs``. The expressions are to be provided as they appear in program order. """ exprs = as_tuple(exprs) assert all(isinstance(i, Eq) for i in exprs) self.reads = {} self.writes = {} for i, e in enumerate(exprs): # reads for j in retrieve_terminals(e.rhs): v = self.reads.setdefault(j.base.function, []) mode = 'R' if not q_inc(e) else 'RI' v.append(TimedAccess(j, mode, i)) # write v = self.writes.setdefault(e.lhs.base.function, []) mode = 'W' if not q_inc(e) else 'WI' v.append(TimedAccess(e.lhs, mode, i))
def time_invariant(self, expr=None): """ Check if ``expr`` is time invariant. ``expr`` may be an expression ``e`` explicitly tracked by the FlowGraph or even a generic subexpression of ``e``. If no ``expr`` is provided, then time invariance of the entire FlowGraph is assessed. """ if expr is None: return all(self.time_invariant(v) for v in self.values()) if any(q_timedimension(i) for i in expr.free_symbols): return False queue = [expr.rhs if expr.is_Equality else expr] seen = set() while queue: item = queue.pop() temporaries = set() for i in retrieve_terminals(item): if i in seen: # Already inspected, nothing more can be inferred continue elif any( isinstance(j, Dimension) and j.is_Time for j in i.free_symbols): # Definitely not time-invariant return False elif i in self: # Go on with the search temporaries.add(i) elif isinstance(i, Dimension): # Go on with the search, as /i/ is not a time dimension pass elif not i.base.function.is_TensorFunction: # It didn't come from the outside and it's not in self, so # cannot determine if time-invariant; assume time-varying return False seen.add(i) queue.extend([self[i].rhs for i in temporaries]) return True
def time_invariant(self, expr=None): """ Check if ``expr`` is time invariant. ``expr`` may be an expression ``e`` explicitly tracked by the FlowGraph or even a generic subexpression of ``e``. If no ``expr`` is provided, then time invariance of the entire FlowGraph is assessed. """ if expr is None: return all(self.time_invariant(v) for v in self.values()) if any(q_timedimension(i) for i in expr.free_symbols): return False queue = [expr.rhs if expr.is_Equality else expr] seen = set() while queue: item = queue.pop() nodes = set() for i in retrieve_terminals(item): if i in seen: # Already inspected, nothing more can be inferred continue elif any(isinstance(j, Dimension) and j.is_Time for j in i.free_symbols): # Definitely not time-invariant return False elif i in self: # Go on with the search nodes.add(i) elif isinstance(i, Dimension): # Go on with the search, as /i/ is not a time dimension pass elif not i.function.is_DiscreteFunction: # It didn't come from the outside and it's not in self, so # cannot determine if time-invariant; assume time-varying return False seen.add(i) queue.extend([self[i].rhs for i in nodes]) return True
def topological_sort(exprs): """Topologically sort a list of equations.""" mapper = {e.lhs: e for e in exprs} assert len(mapper) == len(exprs) # Expect SSA dag = DAG(nodes=exprs) for e in exprs: for r in retrieve_terminals(e.rhs): if r not in mapper: continue elif mapper[r] is e: # Avoid cyclic dependences, such as # Eq(f, f + 1) continue elif r.is_Indexed: # Only scalars enforce an ordering continue else: dag.add_edge(mapper[r], e, force_add=True) processed = dag.topological_sort() return processed
def __init__(self, exprs, **kwargs): # Always convert to SSA exprs = makeit_ssa(exprs) mapper = OrderedDict([(i.lhs, i) for i in exprs]) assert len(set(mapper)) == len(exprs), "not SSA Cluster?" # Construct the Nodes, tracking reads and readby tensor_map = DefaultOrderedDict(list) for i in mapper: tensor_map[as_symbol(i)].append(i) reads = DefaultOrderedDict(set) readby = DefaultOrderedDict(set) for k, v in mapper.items(): handle = retrieve_terminals(v.rhs, deep=True) reads[k].update( set(flatten([tensor_map.get(as_symbol(i), []) for i in handle]))) for i in reads[k]: readby[i].add(k) # Make sure read-after-writes are honored for scalar nodes processed = [i for i in mapper if i.is_Indexed] queue = [i for i in mapper if i not in processed] while queue: k = queue.pop(0) if not readby[k] or k in readby[k]: processed.insert(0, k) elif all(i in processed for i in readby[k]): index = min(processed.index(i) for i in readby[k]) processed.insert(index, k) else: queue.append(k) # Build up the FlowGraph nodes = [(i, Node(mapper[i], reads=reads[i], readby=readby[i])) for i in processed] super(FlowGraph, self).__init__(nodes, **kwargs)
def __init__(self, exprs): """ A Scope represents a group of TimedAcces objects extracted from some IREq ``exprs``. The expressions must be provided in program order. """ exprs = as_tuple(exprs) self.reads = {} self.writes = {} for i, e in enumerate(exprs): # reads for j in retrieve_terminals(e.rhs): v = self.reads.setdefault(j.function, []) mode = 'RI' if e.is_Increment and j.function is e.lhs.function else 'R' v.append(TimedAccess(j, mode, i, e.ispace.directions)) # write v = self.writes.setdefault(e.lhs.function, []) mode = 'WI' if e.is_Increment else 'W' v.append(TimedAccess(e.lhs, mode, i, e.ispace.directions)) # if an increment, we got one implicit read if e.is_Increment: v = self.reads.setdefault(e.lhs.function, []) v.append(TimedAccess(e.lhs, 'RI', i, e.ispace.directions))