diff --git a/loopy/check.py b/loopy/check.py index 2fa9874b135900f1c7e257803313e80f46b41dcb..2b6b88dbcd6f3f0ac04510c53bbf850c7c3bbcb7 100644 --- a/loopy/check.py +++ b/loopy/check.py @@ -450,26 +450,30 @@ def check_variable_access_ordered(kernel): * an (at least indirect) depdendency edge, or * an explicit statement that no ordering is necessary (expressed - through :attr:`loopy.Instruuction.no_sync_with`) + through :attr:`loopy.Instruction.no_sync_with`) """ if kernel.options.enforce_variable_access_ordered == "no_check": return - checked_variables = ( - kernel.get_written_variables() - | set(kernel.temporary_variables) - | set(arg for arg in kernel.arg_dict)) + checked_variables = kernel.get_written_variables() & ( + set(kernel.temporary_variables) | set(arg for arg in kernel.arg_dict)) wmap = kernel.writer_map() rmap = kernel.reader_map() from loopy.kernel.data import GlobalArg, ValueArg, temp_var_scope + from loopy.kernel.tools import find_aliasing_equivalence_classes depfind = IndirectDependencyEdgeFinder(kernel) + aliasing_equiv_classes = find_aliasing_equivalence_classes(kernel) for name in checked_variables: - readers = rmap.get(name, set()) - writers = wmap.get(name, set()) + eq_class = aliasing_equiv_classes[name] + + readers = set.union( + *[rmap.get(eq_name, set()) for eq_name in eq_class]) + writers = set.union( + *[wmap.get(eq_name, set()) for eq_name in eq_class]) if not writers: continue @@ -503,15 +507,21 @@ def check_variable_access_ordered(kernel): if not has_dependency_relationship: msg = ("No dependency relationship found between " - "'{writer_id}' which writes '{var}' and " - "'{other_id}' which also accesses '{var}'. " - "Please either add a (possibly indirect) dependency " - "between the two, or add one to the other's no_sync set " + "'{writer_id}' which writes {var} and " + "'{other_id}' which also accesses {var}. " + "Either add a (possibly indirect) dependency " + "between the two, or add one to the other's nosync set " "to indicate that no ordering is intended. " .format( writer_id=writer_id, other_id=other_id, - var=name)) + var=( + "the variable '%s'" % name + if len(eq_class) == 1 + else ( + "the aliasing equivalence class '%s'" + % ", ".join(eq_class)) + ))) if kernel.options.enforce_variable_access_ordered: from loopy.diagnostic import VariableAccessNotOrdered raise VariableAccessNotOrdered(msg) diff --git a/loopy/kernel/tools.py b/loopy/kernel/tools.py index fbc4238c21e966cb61d1c074ce6924fd9af26084..4f0b805e17d42ad7cf58eafac73675ecca7ffae6 100644 --- a/loopy/kernel/tools.py +++ b/loopy/kernel/tools.py @@ -1731,4 +1731,84 @@ def get_subkernel_to_insn_id_map(kernel): # }}} +# {{{ find aliasing equivalence classes + +class DisjointSets(object): + """ + .. automethod:: __getitem__ + .. automethod:: find_leader_or_create_group + .. automethod:: union + .. automethod:: union_many + """ + + # https://en.wikipedia.org/wiki/Disjoint-set_data_structure + + def __init__(self): + self.leader_to_group = {} + self.element_to_leader = {} + + def __getitem__(self, item): + """ + :arg item: A representative of an equivalence class. + :returns: the equivalence class, given as a set of elements + """ + try: + leader = self.element_to_leader[item] + except KeyError: + return set([item]) + else: + return self.leader_to_group[leader] + + def find_leader_or_create_group(self, el): + try: + return self.element_to_leader[el] + except KeyError: + pass + + self.element_to_leader[el] = el + self.leader_to_group[el] = set([el]) + return el + + def union(self, a, b): + leader_a = self.find_leader_or_create_group(a) + leader_b = self.find_leader_or_create_group(b) + + if leader_a == leader_b: + return + + new_leader = leader_a + + for b_el in self.leader_to_group[leader_b]: + self.element_to_leader[b_el] = new_leader + + self.leader_to_group[leader_a].update(self.leader_to_group[leader_b]) + del self.leader_to_group[leader_b] + + def union_many(self, relation): + """ + :arg relation: an iterable of 2-tuples enumerating the elements of the + relation. The relation is assumed to be an equivalence relation + (transitive, reflexive, symmetric) but need not explicitly contain + all elements to make it that. + + The first elements of the tuples become group leaders. + + :returns: *self* + """ + + for a, b in relation: + self.union(a, b) + + return self + + +def find_aliasing_equivalence_classes(kernel): + return DisjointSets().union_many( + (tv.base_storage, tv.name) + for tv in six.itervalues(kernel.temporary_variables) + if tv.base_storage is not None) + +# }}} + + # vim: foldmethod=marker diff --git a/test/test_loopy.py b/test/test_loopy.py index 931436959a0cdb06510fc0106ecd1feb22d7e652..e1de0af8023af116260a2b6667e15562dbc82753 100644 --- a/test/test_loopy.py +++ b/test/test_loopy.py @@ -2806,6 +2806,25 @@ def test_check_for_variable_access_ordering(): lp.get_one_scheduled_kernel(knl) +def test_check_for_variable_access_ordering_with_aliasing(): + knl = lp.make_kernel( + "{[i]: 0<=i 1: exec(sys.argv[1])