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, **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 collect(exprs): """ Determine groups of aliasing expressions in ``exprs``. An expression A aliases an expression B if both A and B apply the same operations to the same input operands, with the possibility for indexed objects to index into locations at a fixed constant offset in each dimension. For example: :: exprs = (a[i+1] + b[i+1], a[i+1] + b[j+1], a[i] + c[i], a[i+2] - b[i+2], a[i+2] + b[i], a[i-1] + b[i-1]) The following expressions in ``exprs`` alias to ``a[i] + b[i]``: :: a[i+1] + b[i+1] : same operands and operations, distance along i = 1 a[i-1] + b[i-1] : same operands and operations, distance along i = -1 Whereas the following do not: :: a[i+1] + b[j+1] : because at least one index differs a[i] + c[i] : because at least one of the operands differs a[i+2] - b[i+2] : because at least one operation differs a[i+2] + b[i] : because distance along ``i`` differ (+2 and +0) """ ExprData = namedtuple('ExprData', 'dimensions offsets') # Discard expressions: # - that surely won't alias to anything # - that are non-scalar candidates = OrderedDict() for expr in exprs: if expr.lhs.is_Indexed: continue indexeds = retrieve_indexed(expr.rhs, mode='all') if indexeds and not any(q_indirect(i) for i in indexeds): handle = calculate_offsets(indexeds) if handle: candidates[expr.rhs] = ExprData(*handle) aliases = OrderedDict() mapper = OrderedDict() unseen = list(candidates) while unseen: # Find aliasing expressions handle = unseen.pop(0) group = [handle] for e in list(unseen): if compare(handle, e) and\ is_translated(candidates[handle].offsets, candidates[e].offsets): group.append(e) unseen.remove(e) # Try creating a basis for the aliasing expressions' offsets offsets = [tuple(candidates[e].offsets) for e in group] try: COM, distances = calculate_COM(offsets) except DSEException: # Ignore these potential aliases and move on continue alias = create_alias(handle, COM) # An alias has been created, so I can now update the expression mapper mapper.update([(i, group) for i in group]) # In circumstances in which an expression has repeated coefficients, e.g. # ... + 0.025*a[...] + 0.025*b[...], # We may have found a common basis (i.e., same COM, same alias) at this point v = aliases.setdefault(alias, Alias(alias, candidates[handle].dimensions)) v.extend(group, distances) # Heuristically attempt to relax the aliases offsets # to maximize the likelyhood of loop fusion grouped = OrderedDict() for i in aliases.values(): grouped.setdefault(i.dimensions, []).append(i) for dimensions, group in grouped.items(): ideal_anti_stencil = Stencil.union(*[i.anti_stencil for i in group]) for i in group: if i.anti_stencil.subtract(ideal_anti_stencil).empty: aliases[i.alias] = i.relax(ideal_anti_stencil) return mapper, aliases
def collect(exprs): """ Determine groups of aliasing expressions in ``exprs``. An expression A aliases an expression B if both A and B apply the same operations to the same input operands, with the possibility for indexed objects to index into locations at a fixed constant offset in each dimension. For example: :: exprs = (a[i+1] + b[i+1], a[i+1] + b[j+1], a[i] + c[i], a[i+2] - b[i+2], a[i+2] + b[i], a[i-1] + b[i-1]) The following expressions in ``exprs`` alias to ``a[i] + b[i]``: :: a[i+1] + b[i+1] : same operands and operations, distance along i = 1 a[i-1] + b[i-1] : same operands and operations, distance along i = -1 Whereas the following do not: :: a[i+1] + b[j+1] : because at least one index differs a[i] + c[i] : because at least one of the operands differs a[i+2] - b[i+2] : because at least one operation differs a[i+2] + b[i] : because distance along ``i`` differ (+2 and +0) """ ExprData = namedtuple('ExprData', 'dimensions offsets') # Discard expressions: # - that surely won't alias to anything # - that are non-scalar candidates = OrderedDict() for expr in exprs: if expr.lhs.is_Indexed: continue indexeds = retrieve_indexed(expr.rhs, mode='all') if indexeds and not any(q_indirect(i) for i in indexeds): handle = calculate_offsets(indexeds) if handle: candidates[expr.rhs] = ExprData(*handle) aliases = OrderedDict() mapper = OrderedDict() unseen = list(candidates) while unseen: # Find aliasing expressions handle = unseen.pop(0) group = [handle] for e in list(unseen): if compare(handle, e) and\ is_translated(candidates[handle].offsets, candidates[e].offsets): group.append(e) unseen.remove(e) # Try creating a basis for the aliasing expressions' offsets offsets = [tuple(candidates[e].offsets) for e in group] try: COM, distances = calculate_COM(offsets) except DSEException: # Ignore these potential aliases and move on continue alias = create_alias(handle, COM) # An alias has been created, so I can now update the expression mapper mapper.update([(i, group) for i in group]) # In circumstances in which an expression has repeated coefficients, e.g. # ... + 0.025*a[...] + 0.025*b[...], # We may have found a common basis (i.e., same COM, same alias) at this point v = aliases.setdefault(alias, Alias(alias, candidates[handle].dimensions)) v.extend(group, distances) # Heuristically attempt to relax the aliases offsets # to maximize the likelyhood of loop fusion groups = OrderedDict() for i in aliases.values(): groups.setdefault(i.dimensions, []).append(i) for group in groups.values(): ideal_anti_stencil = Stencil.union(*[i.anti_stencil for i in group]) for i in group: if i.anti_stencil.subtract(ideal_anti_stencil).empty: aliases[i.alias] = i.relax(ideal_anti_stencil) return mapper, aliases