From ad088a51d0287c6c411e6521c5fea5d98f5b5a65 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 22:47:43 -0500 Subject: [PATCH 01/19] import constrain_loop_nesting --- loopy/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/loopy/__init__.py b/loopy/__init__.py index a73f83bb9..01bee01b0 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -69,7 +69,8 @@ from loopy.version import VERSION, MOST_RECENT_LANGUAGE_VERSION from loopy.transform.iname import ( - set_loop_priority, prioritize_loops, untag_inames, + set_loop_priority, prioritize_loops, constrain_loop_nesting, + untag_inames, split_iname, chunk_iname, join_inames, tag_inames, duplicate_inames, rename_iname, remove_unused_inames, split_reduction_inward, split_reduction_outward, @@ -194,7 +195,8 @@ # {{{ transforms - "set_loop_priority", "prioritize_loops", "untag_inames", + "set_loop_priority", "prioritize_loops", "constrain_loop_nesting", + "untag_inames", "split_iname", "chunk_iname", "join_inames", "tag_inames", "duplicate_inames", "rename_iname", "remove_unused_inames", From 3a656c09dd09894cdf8fccabe29b04b362148867 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 22:49:43 -0500 Subject: [PATCH 02/19] add loop_nest_constraints attribute to kernel --- loopy/kernel/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/loopy/kernel/__init__.py b/loopy/kernel/__init__.py index 2f39614b8..021712443 100644 --- a/loopy/kernel/__init__.py +++ b/loopy/kernel/__init__.py @@ -268,6 +268,7 @@ def __init__(self, domains, instructions, args=None, iname_slab_increments=None, loop_priority=frozenset(), + loop_nest_constraints=None, silenced_warnings=None, applied_iname_rewrites=None, @@ -380,6 +381,7 @@ def __init__(self, domains, instructions, args=None, assumptions=assumptions, iname_slab_increments=iname_slab_increments, loop_priority=loop_priority, + loop_nest_constraints=loop_nest_constraints, silenced_warnings=silenced_warnings, temporary_variables=temporary_variables, local_sizes=local_sizes, @@ -1543,6 +1545,7 @@ def __setstate__(self, state): "substitutions", "iname_slab_increments", "loop_priority", + "loop_nest_constraints", "silenced_warnings", "options", "state", From c6653270f0ad8dafec100de8eef1cb08d1c030ba Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 22:53:35 -0500 Subject: [PATCH 03/19] copy in classes to contain loop nest constraints from branch loop-nest-constraints-v2 --- loopy/transform/iname.py | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index c3b4a42ee..93f420014 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -28,6 +28,7 @@ RuleAwareIdentityMapper, RuleAwareSubstitutionMapper, SubstitutionRuleMappingContext) from loopy.diagnostic import LoopyError +from pytools import Record from loopy.translation_unit import (TranslationUnit, for_each_kernel) @@ -121,6 +122,100 @@ def prioritize_loops(kernel, loop_priority): # }}} +# {{{ Handle loop nest constraints + +# {{{ Classes to house loop nest constraints + +# {{{ UnexpandedInameSet + +class UnexpandedInameSet(Record): + def __init__(self, inames, complement=False): + Record.__init__( + self, + inames=inames, + complement=complement, + ) + + def contains(self, inames): + if isinstance(inames, set): + return (not (inames & self.inames) if self.complement + else inames.issubset(self.inames)) + else: + return (inames not in self.inames if self.complement + else inames in self.inames) + + def get_inames_represented(self, iname_universe=None): + """Return the set of inames represented by the UnexpandedInameSet + """ + if self.complement: + if not iname_universe: + raise ValueError( + "Cannot expand UnexpandedInameSet %s without " + "iname_universe." % (self)) + return iname_universe-self.inames + else: + return self.inames.copy() + + def __lt__(self, other): + # FIXME is this function really necessary? If so, what should it return? + return self.__hash__() < other.__hash__() + + def __hash__(self): + return hash(repr(self)) + + def update_persistent_hash(self, key_hash, key_builder): + """Custom hash computation function for use with + :class:`pytools.persistent_dict.PersistentDict`. + """ + + key_builder.rec(key_hash, self.inames) + key_builder.rec(key_hash, self.complement) + + def __str__(self): + return "%s{%s}" % ("~" if self.complement else "", + ",".join(i for i in sorted(self.inames))) + +# }}} + + +# {{{ LoopNestConstraints + +class LoopNestConstraints(Record): + def __init__(self, must_nest=None, must_not_nest=None, + must_nest_graph=None): + Record.__init__( + self, + must_nest=must_nest, + must_not_nest=must_not_nest, + must_nest_graph=must_nest_graph, + ) + + def __hash__(self): + return hash(repr(self)) + + def update_persistent_hash(self, key_hash, key_builder): + """Custom hash computation function for use with + :class:`pytools.persistent_dict.PersistentDict`. + """ + + key_builder.rec(key_hash, self.must_nest) + key_builder.rec(key_hash, self.must_not_nest) + key_builder.rec(key_hash, self.must_nest_graph) + + def __str__(self): + return "LoopNestConstraints(\n" \ + " must_nest = " + str(self.must_nest) + "\n" \ + " must_not_nest = " + str(self.must_not_nest) + "\n" \ + " must_nest_graph = " + str(self.must_nest_graph) + "\n" \ + ")" + +# }}} + +# }}} + +# }}} + + # {{{ split/chunk inames # {{{ backend From 6f080d29df9d7abd3c5cbdda8dc1cf2a68b9b0b2 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 22:56:11 -0500 Subject: [PATCH 04/19] copy in functions for initial loop nest constraint creation from branch loop-nest-constraints-v2 --- loopy/transform/iname.py | 396 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 93f420014..6b98af3a7 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -213,6 +213,402 @@ def __str__(self): # }}} + +# {{{ Initial loop nest constraint creation + +# {{{ process_loop_nest_specification + +def process_loop_nest_specification( + nesting, + max_tuple_size=None, + complement_sets_allowed=True, + ): + + # Ensure that user-supplied nesting conforms to syntax rules, and + # convert string representations of nestings to tuple of UnexpandedInameSets + + import re + + def _raise_loop_nest_input_error(msg): + valid_prio_rules = ( + "Valid `must_nest` description formats: " # noqa + "\"iname, iname, ...\" or (str, str, str, ...), " # noqa + "where str can be of form " # noqa + "\"iname\" or \"{iname, iname, ...}\". " # noqa + "No set complements allowed.\n" # noqa + "Valid `must_not_nest` description tuples must have length 2: " # noqa + "\"iname, iname\", \"iname, ~iname\", or " # noqa + "(str, str), where str can be of form " # noqa + "\"iname\", \"~iname\", \"{iname, iname, ...}\", or " # noqa + "\"~{iname, iname, ...}\"." # noqa + ) + raise ValueError( + "Invalid loop nest prioritization: %s\n" + "Loop nest prioritization formatting rules:\n%s" + % (msg, valid_prio_rules)) + + def _error_on_regex_match(match_str, target_str): + if re.findall(match_str, target_str): + _raise_loop_nest_input_error( + "Unrecognized character(s) %s in nest string %s" + % (re.findall(match_str, target_str), target_str)) + + def _process_iname_set_str(iname_set_str): + # Convert something like ~{i,j} or ~i or "i,j" to an UnexpandedInameSet + + # Remove leading/trailing whitespace + iname_set_str_stripped = iname_set_str.strip() + + if not iname_set_str_stripped: + _raise_loop_nest_input_error( + "Found 0 inames in string %s." + % (iname_set_str)) + + # Process complement sets + if iname_set_str_stripped[0] == "~": + # Make sure compelement is allowed + if not complement_sets_allowed: + _raise_loop_nest_input_error( + "Complement (~) not allowed in this loop nest string %s. " + "If you have a use-case where allowing a currently " + "disallowed set complement would be helpful, and the " + "desired nesting constraint cannot easily be expressed " + "another way, " + "please contact the Loo.py maintainers." + % (iname_set_str)) + + # Remove tilde + iname_set_str_stripped = iname_set_str_stripped[1:] + if "~" in iname_set_str_stripped: + _raise_loop_nest_input_error( + "Multiple complement symbols found in iname set string %s" + % (iname_set_str)) + + # Make sure that braces are included if multiple inames present + if "," in iname_set_str_stripped and not ( + iname_set_str_stripped.startswith("{") and + iname_set_str_stripped.endswith("}")): + _raise_loop_nest_input_error( + "Complements of sets containing multiple inames must " + "enclose inames in braces: %s is not valid." + % (iname_set_str)) + + complement = True + else: + complement = False + + # Remove leading/trailing spaces + iname_set_str_stripped = iname_set_str_stripped.strip(" ") + + # Make sure braces are valid and strip them + if iname_set_str_stripped[0] == "{": + if not iname_set_str_stripped[-1] == "}": + _raise_loop_nest_input_error( + "Invalid braces: %s" % (iname_set_str)) + else: + # Remove enclosing braces + iname_set_str_stripped = iname_set_str_stripped[1:-1] + # (If there are dangling braces around, they will be caught next) + + # Remove any more spaces + iname_set_str_stripped = iname_set_str_stripped.strip() + + # Should be no remaining special characters besides comma and space + _error_on_regex_match(r"([^,\w ])", iname_set_str_stripped) + + # Split by commas or spaces to get inames + inames = re.findall(r"([\w]+)(?:[ |,]*|$)", iname_set_str_stripped) + + # Make sure iname count matches what we expect from comma count + if len(inames) != iname_set_str_stripped.count(",") + 1: + _raise_loop_nest_input_error( + "Found %d inames but expected %d in string %s." + % (len(inames), iname_set_str_stripped.count(",") + 1, + iname_set_str)) + + if len(inames) == 0: + _raise_loop_nest_input_error( + "Found empty set in string %s." + % (iname_set_str)) + + # NOTE this won't catch certain cases of bad syntax, e.g., ("{h i j,,}", "k") + + return UnexpandedInameSet( + set([s.strip() for s in iname_set_str_stripped.split(",")]), + complement=complement) + + if isinstance(nesting, str): + # Enforce that constraints involving iname sets be passed as tuple. + # Iname sets defined negatively with a *single* iname are allowed here. + + # Check for any special characters besides comma, space, and tilde. + # E.g., curly braces would indicate that an iname set was NOT + # passed as a tuple, which is not allowed. + _error_on_regex_match(r"([^,\w~ ])", nesting) + + # Split by comma and process each tier + nesting_as_tuple = tuple( + _process_iname_set_str(set_str) for set_str in nesting.split(",")) + else: + assert isinstance(nesting, (tuple, list)) + # Process each tier + nesting_as_tuple = tuple( + _process_iname_set_str(set_str) for set_str in nesting) + + # Check max_inames_per_set + if max_tuple_size and len(nesting_as_tuple) > max_tuple_size: + _raise_loop_nest_input_error( + "Loop nest prioritization tuple %s exceeds max tuple size %d." + % (nesting_as_tuple)) + + # Make sure nesting has len > 1 + if len(nesting_as_tuple) <= 1: + _raise_loop_nest_input_error( + "Loop nest prioritization tuple %s must have length > 1." + % (nesting_as_tuple)) + + # Return tuple of UnexpandedInameSets + return nesting_as_tuple + +# }}} + + +# {{{ constrain_loop_nesting + +def constrain_loop_nesting( + kernel, must_nest=None, must_not_nest=None): + r"""Add the provided constraints to the kernel. + :arg must_nest: A tuple or comma-separated string representing + an ordering of loop nesting tiers that must appear in the + linearized kernel. Each item in the tuple represents a + :class:`UnexpandedInameSet`\ s. + :arg must_not_nest: A two-tuple or comma-separated string representing + an ordering of loop nesting tiers that must not appear in the + linearized kernel. Each item in the tuple represents a + :class:`UnexpandedInameSet`\ s. + """ + + # {{{ Get any current constraints, if they exist + if kernel.loop_nest_constraints: + if kernel.loop_nest_constraints.must_nest: + must_nest_constraints_old = kernel.loop_nest_constraints.must_nest + else: + must_nest_constraints_old = set() + + if kernel.loop_nest_constraints.must_not_nest: + must_not_nest_constraints_old = \ + kernel.loop_nest_constraints.must_not_nest + else: + must_not_nest_constraints_old = set() + + if kernel.loop_nest_constraints.must_nest_graph: + must_nest_graph_old = kernel.loop_nest_constraints.must_nest_graph + else: + must_nest_graph_old = {} + else: + must_nest_constraints_old = set() + must_not_nest_constraints_old = set() + must_nest_graph_old = {} + + # }}} + + # {{{ Process must_nest + + if must_nest: + # {{{ Parse must_nest, check for conflicts, combine with old constraints + + # {{{ Parse must_nest (no complements allowed) + must_nest_tuple = process_loop_nest_specification( + must_nest, complement_sets_allowed=False) + # }}} + + # {{{ Error if someone prioritizes concurrent iname + + from loopy.kernel.data import ConcurrentTag + for iname_set in must_nest_tuple: + for iname in iname_set.inames: + if kernel.iname_tags_of_type(iname, ConcurrentTag): + raise ValueError( + "iname %s tagged with ConcurrentTag, " + "cannot use iname in must-nest constraint %s." + % (iname, must_nest_tuple)) + + # }}} + + # {{{ Update must_nest graph (and check for cycles) + + must_nest_graph_new = update_must_nest_graph( + must_nest_graph_old, must_nest_tuple, kernel.all_inames()) + + # }}} + + # {{{ Make sure must_nest constraints don't violate must_not_nest + # (this may not catch all problems) + check_must_not_nest_against_must_nest_graph( + must_not_nest_constraints_old, must_nest_graph_new) + # }}} + + # {{{ Check for conflicts with inames tagged 'vec' (must be innermost) + + from loopy.kernel.data import VectorizeTag + for iname in kernel.all_inames(): + if kernel.iname_tags_of_type(iname, VectorizeTag) and ( + must_nest_graph_new.get(iname, set())): + # Must-nest graph doesn't allow iname to be a leaf, error + raise ValueError( + "Iname %s tagged as 'vec', but loop nest constraints " + "%s require that iname %s nest outside of inames %s. " + "Vectorized inames must nest innermost; cannot " + "impose loop nest specification." + % (iname, must_nest, iname, + must_nest_graph_new.get(iname, set()))) + + # }}} + + # {{{ Add new must_nest constraints to existing must_nest constraints + must_nest_constraints_new = must_nest_constraints_old | set( + [must_nest_tuple, ]) + # }}} + + # }}} + else: + # {{{ No new must_nest constraints, just keep the old ones + + must_nest_constraints_new = must_nest_constraints_old + must_nest_graph_new = must_nest_graph_old + + # }}} + + # }}} + + # {{{ Process must_not_nest + + if must_not_nest: + # {{{ Parse must_not_nest, check for conflicts, combine with old constraints + + # {{{ Parse must_not_nest; complements allowed; max_tuple_size=2 + + must_not_nest_tuple = process_loop_nest_specification( + must_not_nest, max_tuple_size=2) + + # }}} + + # {{{ Make sure must_not_nest constraints don't violate must_nest + + # (cycles are allowed in must_not_nest constraints) + import itertools + must_pairs = [] + for iname_before, inames_after in must_nest_graph_new.items(): + must_pairs.extend(list(itertools.product([iname_before], inames_after))) + + if not check_must_not_nest(must_pairs, must_not_nest_tuple): + raise ValueError( + "constrain_loop_nesting: nest constraint conflict detected. " + "must_not_nest constraints %s inconsistent with " + "must_nest constraints %s." + % (must_not_nest_tuple, must_nest_constraints_new)) + + # }}} + + # {{{ Add new must_not_nest constraints to exisitng must_not_nest constraints + must_not_nest_constraints_new = must_not_nest_constraints_old | set([ + must_not_nest_tuple, ]) + # }}} + + # }}} + else: + # {{{ No new must_not_nest constraints, just keep the old ones + + must_not_nest_constraints_new = must_not_nest_constraints_old + + # }}} + + # }}} + + nest_constraints = LoopNestConstraints( + must_nest=must_nest_constraints_new, + must_not_nest=must_not_nest_constraints_new, + must_nest_graph=must_nest_graph_new, + ) + + return kernel.copy(loop_nest_constraints=nest_constraints) + +# }}} + + +# {{{ update_must_nest_graph + +def update_must_nest_graph(must_nest_graph, must_nest, all_inames): + # Note: there should *not* be any complements in the must_nest tuples + + from copy import deepcopy + new_graph = deepcopy(must_nest_graph) + + # First, each iname must be a node in the graph + for missing_iname in all_inames - new_graph.keys(): + new_graph[missing_iname] = set() + + # Expand must_nest into (before, after) pairs + must_nest_expanded = _expand_iname_sets_in_tuple(must_nest, all_inames) + + # Update must_nest_graph with new pairs + for before, after in must_nest_expanded: + new_graph[before].add(after) + + # Compute transitive closure + from pytools.graph import compute_transitive_closure, contains_cycle + new_graph_closure = compute_transitive_closure(new_graph) + # Note: compute_transitive_closure now allows cycles, will not error + + # Check for inconsistent must_nest constraints by checking for cycle: + if contains_cycle(new_graph_closure): + raise ValueError( + "update_must_nest_graph: Nest constraint cycle detected. " + "must_nest constraints %s inconsistent with existing " + "must_nest constraints %s." + % (must_nest, must_nest_graph)) + + return new_graph_closure + +# }}} + + +# {{{ _expand_iname_sets_in_tuple + +def _expand_iname_sets_in_tuple( + iname_sets_tuple, + iname_universe=None, + ): + + # First convert UnexpandedInameSets to sets. + # Note that must_nest constraints cannot be negatively defined. + positively_defined_iname_sets = [ + iname_set.get_inames_represented(iname_universe) + for iname_set in iname_sets_tuple] + + # Now expand all priority tuples into (before, after) pairs using + # Cartesian product of all pairs of sets + # (Assumes prio_sets length > 1) + import itertools + loop_priority_pairs = set() + for i, before_set in enumerate(positively_defined_iname_sets[:-1]): + for after_set in positively_defined_iname_sets[i+1:]: + loop_priority_pairs.update( + list(itertools.product(before_set, after_set))) + + # Make sure no priority tuple contains an iname twice + for prio_tuple in loop_priority_pairs: + if len(set(prio_tuple)) != len(prio_tuple): + raise ValueError( + "Loop nesting %s contains cycle: %s. " + % (iname_sets_tuple, prio_tuple)) + + return loop_priority_pairs + +# }}} + +# }}} + # }}} From 33dd1790e78b8c6e83eb07f4d7a2d392764435ac Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 23:01:38 -0500 Subject: [PATCH 05/19] copy in functions for checking loop nest constraints from branch loop-nest-constraints-v2 --- loopy/transform/iname.py | 179 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 6b98af3a7..e6eb1bd3f 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -378,10 +378,12 @@ def _process_iname_set_str(iname_set_str): def constrain_loop_nesting( kernel, must_nest=None, must_not_nest=None): r"""Add the provided constraints to the kernel. + :arg must_nest: A tuple or comma-separated string representing an ordering of loop nesting tiers that must appear in the linearized kernel. Each item in the tuple represents a :class:`UnexpandedInameSet`\ s. + :arg must_not_nest: A two-tuple or comma-separated string representing an ordering of loop nesting tiers that must not appear in the linearized kernel. Each item in the tuple represents a @@ -609,6 +611,183 @@ def _expand_iname_sets_in_tuple( # }}} + +# {{{ Checking constraints + +# {{{ check_must_nest + +def check_must_nest(all_loop_nests, must_nest, all_inames): + r"""Determine whether must_nest constraint is satisfied by + all_loop_nests + + :arg all_loop_nests: A list of lists of inames, each representing + the nesting order of nested loops. + + :arg must_nest: A tuple of :class:`UnexpandedInameSet`\ s describing + nestings that must appear in all_loop_nests. + + :returns: A :class:`bool` indicating whether the must nest constraints + are satisfied by the provided loop nesting. + """ + + # In order to make sure must_nest is satisfied, we + # need to expand all must_nest tiers + + # FIXME instead of expanding tiers into all pairs up front, + # create these pairs one at a time so that we can stop as soon as we fail + + must_nest_expanded = _expand_iname_sets_in_tuple(must_nest) + + # must_nest_expanded contains pairs + for before, after in must_nest_expanded: + found = False + for nesting in all_loop_nests: + if before in nesting and after in nesting and ( + nesting.index(before) < nesting.index(after)): + found = True + break + if not found: + return False + return True + +# }}} + + +# {{{ check_must_not_nest + +def check_must_not_nest(all_loop_nests, must_not_nest): + r"""Determine whether must_not_nest constraint is satisfied by + all_loop_nests + + :arg all_loop_nests: A list of lists of inames, each representing + the nesting order of nested loops. + + :arg must_not_nest: A two-tuple of :class:`UnexpandedInameSet`\ s + describing nestings that must not appear in all_loop_nests. + + :returns: A :class:`bool` indicating whether the must_not_nest constraints + are satisfied by the provided loop nesting. + """ + + # Note that must_not_nest may only contain two tiers + + for nesting in all_loop_nests: + + # Go through each pair in all_loop_nests + for i, iname_before in enumerate(nesting): + for iname_after in nesting[i+1:]: + + # Check whether it violates must not nest + if (must_not_nest[0].contains(iname_before) + and must_not_nest[1].contains(iname_after)): + # Stop as soon as we fail + return False + return True + +# }}} + + +# {{{ loop_nest_constraints_satisfied + +def loop_nest_constraints_satisfied( + all_loop_nests, + must_nest_constraints=None, + must_not_nest_constraints=None, + all_inames=None): + r"""Determine whether must_not_nest constraint is satisfied by + all_loop_nests + + :arg all_loop_nests: A set of lists of inames, each representing + the nesting order of loops. + + :arg must_nest_constraints: An iterable of tuples of + :class:`UnexpandedInameSet`\ s, each describing nestings that must + appear in all_loop_nests. + + :arg must_not_nest_constraints: An iterable of two-tuples of + :class:`UnexpandedInameSet`\ s, each describing nestings that must not + appear in all_loop_nests. + + :returns: A :class:`bool` indicating whether the constraints + are satisfied by the provided loop nesting. + """ + + # Check must-nest constraints + if must_nest_constraints: + for must_nest in must_nest_constraints: + if not check_must_nest( + all_loop_nests, must_nest, all_inames): + return False + + # Check must-not-nest constraints + if must_not_nest_constraints: + for must_not_nest in must_not_nest_constraints: + if not check_must_not_nest( + all_loop_nests, must_not_nest): + return False + + return True + +# }}} + + +# {{{ check_must_not_nest_against_must_nest_graph + +def check_must_not_nest_against_must_nest_graph( + must_not_nest_constraints, must_nest_graph): + r"""Ensure none of the must_not_nest constraints are violated by + nestings represented in the must_nest_graph + + :arg must_not_nest_constraints: A set of two-tuples of + :class:`UnexpandedInameSet`\ s describing nestings that must not appear + in loop nestings. + + :arg must_nest_graph: A :class:`dict` mapping each iname to other inames + that must be nested inside it. + """ + + if must_not_nest_constraints and must_nest_graph: + import itertools + must_pairs = [] + for iname_before, inames_after in must_nest_graph.items(): + must_pairs.extend( + list(itertools.product([iname_before], inames_after))) + if any(not check_must_not_nest(must_pairs, must_not_nest_tuple) + for must_not_nest_tuple in must_not_nest_constraints): + raise ValueError( + "Nest constraint conflict detected. " + "must_not_nest constraints %s inconsistent with " + "must_nest relationships (must_nest graph: %s)." + % (must_not_nest_constraints, must_nest_graph)) + +# }}} + + +# {{{ get_iname_nestings + +def get_iname_nestings(linearization): + """Return a list of iname tuples representing the deepest loop nestings + in a kernel linearization. + """ + from loopy.schedule import EnterLoop, LeaveLoop + nestings = [] + current_tiers = [] + already_exiting_loops = False + for lin_item in linearization: + if isinstance(lin_item, EnterLoop): + already_exiting_loops = False + current_tiers.append(lin_item.iname) + elif isinstance(lin_item, LeaveLoop): + if not already_exiting_loops: + nestings.append(tuple(current_tiers)) + already_exiting_loops = True + del current_tiers[-1] + return nestings + +# }}} + +# }}} + # }}} From 63015f67249ba773320b96341f87ca1a92a621de Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 23:16:15 -0500 Subject: [PATCH 06/19] add tests for loop nest constraint creation from branch loop-nest-constraints-v2 --- test/test_nest_constraints.py | 376 ++++++++++++++++++++++++++++++++++ 1 file changed, 376 insertions(+) create mode 100644 test/test_nest_constraints.py diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py new file mode 100644 index 000000000..6bc1e8ef4 --- /dev/null +++ b/test/test_nest_constraints.py @@ -0,0 +1,376 @@ +__copyright__ = "Copyright (C) 2021 James Stevens" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import sys +import loopy as lp +import numpy as np +import pyopencl as cl + +import logging +logger = logging.getLogger(__name__) + +try: + import faulthandler +except ImportError: + pass +else: + faulthandler.enable() + +from pyopencl.tools import pytest_generate_tests_for_pyopencl \ + as pytest_generate_tests + +__all__ = [ + "pytest_generate_tests", + "cl" # "cl.create_some_context" + ] + + +from loopy.version import LOOPY_USE_LANGUAGE_VERSION_2018_2 # noqa + + +# {{{ test_loop_constraint_string_parsing + +def test_loop_constraint_string_parsing(): + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k,xx]: 0<=g,h,i,j,k,xx 1: + exec(sys.argv[1]) + else: + from pytest import main + main([__file__]) + +# vim: foldmethod=marker From 90b84c3b06945e50ff219eb80fed355ffb76a31d Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Sat, 8 May 2021 23:38:45 -0500 Subject: [PATCH 07/19] fix typo in comment --- loopy/transform/iname.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index e6eb1bd3f..cfdcc4fc4 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -355,7 +355,7 @@ def _process_iname_set_str(iname_set_str): nesting_as_tuple = tuple( _process_iname_set_str(set_str) for set_str in nesting) - # Check max_inames_per_set + # Check max_tuple_size if max_tuple_size and len(nesting_as_tuple) > max_tuple_size: _raise_loop_nest_input_error( "Loop nest prioritization tuple %s exceeds max tuple size %d." From 29c26171c34d17fbaef19f250b4ce6a3cbd1a211 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 20 Jul 2021 15:26:05 -0500 Subject: [PATCH 08/19] add for_each_kernel decorator to constrain_loop_nesting --- loopy/transform/iname.py | 1 + 1 file changed, 1 insertion(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index cfdcc4fc4..efadf9232 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -375,6 +375,7 @@ def _process_iname_set_str(iname_set_str): # {{{ constrain_loop_nesting +@for_each_kernel def constrain_loop_nesting( kernel, must_nest=None, must_not_nest=None): r"""Add the provided constraints to the kernel. From 9dbba3993dfbbe8cc1c4be488eaf56f6d8e89dac Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 20 Jul 2021 15:28:42 -0500 Subject: [PATCH 09/19] fix bugs due to callables update --- test/test_nest_constraints.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 6bc1e8ef4..09243f83d 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -163,7 +163,8 @@ def test_loop_constraint_string_parsing(): lp.constrain_loop_nesting(ref_knl, must_nest="k,h,j") # Handling spaces - knl = lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h }", " { j , i } ")) + prog = lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h }", " { j , i } ")) + knl = prog["loopy_kernel"] assert list(knl.loop_nest_constraints.must_nest)[0][0].inames == set("k") assert list(knl.loop_nest_constraints.must_nest)[0][1].inames == set("h") assert list(knl.loop_nest_constraints.must_nest)[0][2].inames == set(["j", "i"]) @@ -302,7 +303,7 @@ def test_adding_multiple_nest_constraints_to_knl(): knl = lp.constrain_loop_nesting( knl, must_nest=("x", "y")) - must_nest_knl = knl.loop_nest_constraints.must_nest + must_nest_knl = knl["loopy_kernel"].loop_nest_constraints.must_nest from loopy.transform.iname import UnexpandedInameSet must_nest_expected = set([ (UnexpandedInameSet(set(["g"], )), UnexpandedInameSet(set(["h", "i"], ))), @@ -315,7 +316,7 @@ def test_adding_multiple_nest_constraints_to_knl(): ]) assert must_nest_knl == must_nest_expected - must_not_nest_knl = knl.loop_nest_constraints.must_not_nest + must_not_nest_knl = knl["loopy_kernel"].loop_nest_constraints.must_not_nest must_not_nest_expected = set([ (UnexpandedInameSet(set(["k", "i"], )), UnexpandedInameSet(set(["k", "i"], ), complement=True)), From 51ede85d901cff58f745ae05b4a7d8c83fd75f0b Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Tue, 20 Jul 2021 15:50:47 -0500 Subject: [PATCH 10/19] change 'assert False' to 'raise AssertionError()' --- test/test_nest_constraints.py | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 09243f83d..9e5f16e37 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -58,80 +58,80 @@ def test_loop_constraint_string_parsing(): try: lp.constrain_loop_nesting(ref_knl, "{g,h,k},{j,i}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,h,i,k},{j}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,{h,i,k}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,~h,i,k}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, "{g,#h,i,k}") - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("{g,{h}", "i,k")) - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("{g,~h}", "i,k")) - assert False + raise AssertionError() except ValueError as e: assert "Unrecognized character(s)" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("k", "~{g,h}", "{g,h}")) - assert False + raise AssertionError() except ValueError as e: assert "Complement (~) not allowed" in str(e) try: lp.constrain_loop_nesting(ref_knl, ("k", "{i,j,k}", "{g,h}")) - assert False + raise AssertionError() except ValueError as e: assert "contains cycle" in str(e) try: lp.constrain_loop_nesting(ref_knl, must_not_nest=("~j,i", "{j,i}")) - assert False + raise AssertionError() except ValueError as e: assert ("Complements of sets containing multiple inames " "must enclose inames in braces") in str(e) try: lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h}", "{j,i,}")) - assert False + raise AssertionError() except ValueError as e: assert ("Found 2 inames but expected 3") in str(e) try: lp.constrain_loop_nesting(ref_knl, must_nest=("k", "{h}", "{j, x x, i}")) - assert False + raise AssertionError() except ValueError as e: assert ("Found 4 inames but expected 3") in str(e) try: lp.constrain_loop_nesting(ref_knl, must_nest="{h}}") - assert False + raise AssertionError() except ValueError as e: assert ( "Unrecognized character(s) ['{', '}', '}'] in nest string {h}}" @@ -139,7 +139,7 @@ def test_loop_constraint_string_parsing(): try: lp.constrain_loop_nesting(ref_knl, must_nest="{h i j,,}") - assert False + raise AssertionError() except ValueError as e: assert( "Unrecognized character(s) [\'{\', \'}\'] in nest string {h i j,,}" @@ -147,7 +147,7 @@ def test_loop_constraint_string_parsing(): try: lp.constrain_loop_nesting(ref_knl, must_nest=("{h}}", "i")) - assert False + raise AssertionError() except ValueError as e: assert ( "Unrecognized character(s) [\'}\'] in nest string h}" @@ -171,13 +171,13 @@ def test_loop_constraint_string_parsing(): try: knl = lp.constrain_loop_nesting(ref_knl, ("j", "{}")) - assert False + raise AssertionError() except ValueError as e: assert "Found 0 inames" in str(e) try: knl = lp.constrain_loop_nesting(ref_knl, ("j", "")) - assert False + raise AssertionError() except ValueError as e: assert "Found 0 inames" in str(e) @@ -349,7 +349,7 @@ def test_incompatible_nest_constraints(): try: knl = lp.constrain_loop_nesting( knl, must_nest=("k", "h")) # (should fail) - assert False + raise AssertionError() except ValueError as e: assert "Nest constraint conflict detected" in str(e) @@ -360,7 +360,7 @@ def test_incompatible_nest_constraints(): try: knl = lp.constrain_loop_nesting( knl, must_nest=("j", "g")) # (should fail) - assert False + raise AssertionError() except ValueError as e: assert "Nest constraint cycle detected" in str(e) From f04ca4bc7f84c351531f1637306062730fae8acf Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Wed, 21 Jul 2021 15:41:34 -0500 Subject: [PATCH 11/19] copy in funcs from loop-nest-constraints-v2 branch: check_all_must_not_nests(), get_graph_sources() --- loopy/transform/iname.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index efadf9232..4662704c8 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -688,6 +688,27 @@ def check_must_not_nest(all_loop_nests, must_not_nest): # }}} +# {{{ check_all_must_not_nests + +def check_all_must_not_nests(all_loop_nests, must_not_nests): + r"""Determine whether all must_not_nest constraints are satisfied by + all_loop_nests + :arg all_loop_nests: A list of lists of inames, each representing + the nesting order of nested loops. + :arg must_not_nests: A set of two-tuples of :class:`UnexpandedInameSet`\ s + describing nestings that must not appear in all_loop_nests. + :returns: A :class:`bool` indicating whether the must_not_nest constraints + are satisfied by the provided loop nesting. + """ + + for must_not_nest in must_not_nests: + if not check_must_not_nest(all_loop_nests, must_not_nest): + return False + return True + +# }}} + + # {{{ loop_nest_constraints_satisfied def loop_nest_constraints_satisfied( @@ -787,6 +808,17 @@ def get_iname_nestings(linearization): # }}} + +# {{{ get_graph_sources + +def get_graph_sources(graph): + sources = set(graph.keys()) + for non_sources in graph.values(): + sources -= non_sources + return sources + +# }}} + # }}} # }}} From 057f69b485cb418fa4f460ca82b795a837000dfa Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Wed, 21 Jul 2021 15:42:25 -0500 Subject: [PATCH 12/19] enforce new loop nest constraints during linearization (copied in from old loop-nest-constraints-v2 branch) --- loopy/schedule/__init__.py | 305 ++++++++++++++++++++++++++++--------- 1 file changed, 236 insertions(+), 69 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 5822f44ed..674ab47a3 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -632,7 +632,7 @@ class SchedulerState(ImmutableRecord): """ @property - def last_entered_loop(self): + def deepest_active_iname(self): if self.active_inames: return self.active_inames[-1] else: @@ -1088,40 +1088,40 @@ def insn_sort_key(insn_id): # {{{ see if we're ready to leave the innermost loop - last_entered_loop = sched_state.last_entered_loop + deepest_active_iname = sched_state.deepest_active_iname - if last_entered_loop is not None: + if deepest_active_iname is not None: can_leave = True if ( - last_entered_loop in sched_state.prescheduled_inames + deepest_active_iname in sched_state.prescheduled_inames and not ( isinstance(next_preschedule_item, LeaveLoop) - and next_preschedule_item.iname == last_entered_loop)): + and next_preschedule_item.iname == deepest_active_iname)): # A prescheduled loop can only be left if the preschedule agrees. if debug_mode: print("cannot leave '%s' because of preschedule constraints" - % last_entered_loop) + % deepest_active_iname) can_leave = False - elif last_entered_loop not in sched_state.breakable_inames: + elif deepest_active_iname not in sched_state.breakable_inames: # If the iname is not breakable, then check that we've # scheduled all the instructions that require it. for insn_id in sched_state.unscheduled_insn_ids: insn = kernel.id_to_insn[insn_id] - if last_entered_loop in insn.within_inames: + if deepest_active_iname in insn.within_inames: if debug_mode: print("cannot leave '%s' because '%s' still depends on it" - % (last_entered_loop, format_insn(kernel, insn.id))) + % (deepest_active_iname, format_insn(kernel, insn.id))) # check if there's a dependency of insn that needs to be - # outside of last_entered_loop. + # outside of deepest_active_iname. for subdep_id in gen_dependencies_except(kernel, insn_id, sched_state.scheduled_insn_ids): want = (kernel.insn_inames(subdep_id) - sched_state.parallel_inames) if ( - last_entered_loop not in want): + deepest_active_iname not in want): print( "%(warn)swarning:%(reset_all)s '%(iname)s', " "which the schedule is " @@ -1135,7 +1135,7 @@ def insn_sort_key(insn_id): % { "warn": Fore.RED + Style.BRIGHT, "reset_all": Style.RESET_ALL, - "iname": last_entered_loop, + "iname": deepest_active_iname, "subdep": format_insn_id(kernel, subdep_id), "dep": format_insn_id(kernel, insn_id), "subdep_i": format_insn(kernel, subdep_id), @@ -1162,23 +1162,57 @@ def insn_sort_key(insn_id): if ignore_count: ignore_count -= 1 else: - assert sched_item.iname == last_entered_loop + assert sched_item.iname == deepest_active_iname if seen_an_insn: can_leave = True break + # {{{ Don't leave if doing so would violate must_nest constraints + + # Don't leave if must_nest constraints require that + # additional inames be nested inside the current iname + if can_leave: + must_nest_graph = ( + sched_state.kernel.loop_nest_constraints.must_nest_graph + if sched_state.kernel.loop_nest_constraints else None) + + if must_nest_graph: + # get inames that must nest inside the current iname + must_nest_inside = must_nest_graph[deepest_active_iname] + + if must_nest_inside: + # get scheduled inames that are nested inside current iname + within_deepest_active_iname = False + actually_nested_inside = set() + for sched_item in sched_state.schedule: + if isinstance(sched_item, EnterLoop): + if within_deepest_active_iname: + actually_nested_inside.add(sched_item.iname) + elif sched_item.iname == deepest_active_iname: + within_deepest_active_iname = True + elif (isinstance(sched_item, LeaveLoop) and + sched_item.iname == deepest_active_iname): + break + + # don't leave if must_nest constraints require that + # additional inames be nested inside the current iname + if not must_nest_inside.issubset(actually_nested_inside): + can_leave = False + + # }}} + if can_leave and not debug_mode: for sub_sched in generate_loop_schedules_internal( sched_state.copy( schedule=( sched_state.schedule - + (LeaveLoop(iname=last_entered_loop),)), + + (LeaveLoop(iname=deepest_active_iname),)), active_inames=sched_state.active_inames[:-1], insn_ids_to_try=insn_ids_to_try, preschedule=( sched_state.preschedule - if last_entered_loop + if deepest_active_iname not in sched_state.prescheduled_inames else sched_state.preschedule[1:]), ), @@ -1316,65 +1350,130 @@ def insn_sort_key(insn_id): # {{{ tier building - # Build priority tiers. If a schedule is found in the first tier, then - # loops in the second are not even tried (and so on). - loop_priority_set = set().union(*[set(prio) - for prio in - sched_state.kernel.loop_priority]) - useful_loops_set = set(iname_to_usefulness.keys()) - useful_and_desired = useful_loops_set & loop_priority_set - - if useful_and_desired: - wanted = ( - useful_and_desired - - sched_state.ilp_inames - - sched_state.vec_inames - ) - priority_tiers = [t for t in - get_priority_tiers(wanted, - sched_state.kernel.loop_priority - ) - ] - - # Update the loop priority set, because some constraints may have - # have been contradictary. - loop_priority_set = set().union(*[set(t) for t in priority_tiers]) - - priority_tiers.append( + # Keys of iname_to_usefulness are now inames that get us closer to + # scheduling an insn + + if sched_state.kernel.loop_nest_constraints: + # {{{ Use loop_nest_constraints in determining next_iname_candidates + + # Inames not yet entered that would get us closer to scheduling an insn: + useful_loops_set = set(iname_to_usefulness.keys()) + + from loopy.transform.iname import ( + check_all_must_not_nests, + get_graph_sources, + ) + from pytools.graph import compute_induced_subgraph + + # Since vec_inames must be innermost, + # they are not valid canidates unless only vec_inames remain + if useful_loops_set - sched_state.vec_inames: + useful_loops_set -= sched_state.vec_inames + + # To enter an iname without violating must_nest constraints, + # iname must be a source in the induced subgraph of must_nest_graph + # containing inames in useful_loops_set + must_nest_graph_full = ( + sched_state.kernel.loop_nest_constraints.must_nest_graph + if sched_state.kernel.loop_nest_constraints else None) + if must_nest_graph_full: + must_nest_graph_useful = compute_induced_subgraph( + must_nest_graph_full, useful_loops_set - - loop_priority_set - - sched_state.ilp_inames - - sched_state.vec_inames ) + source_inames = get_graph_sources(must_nest_graph_useful) + else: + source_inames = useful_loops_set + + # Since graph has a key for every iname, + # sources should be the only valid iname candidates + + # Check whether entering any source_inames violates + # must-not-nest constraints, given the currently active inames + must_not_nest_constraints = ( + sched_state.kernel.loop_nest_constraints.must_not_nest + if sched_state.kernel.loop_nest_constraints else None) + if must_not_nest_constraints: + next_iname_candidates = set() + for next_iname in source_inames: + iname_orders_to_check = [ + (active_iname, next_iname) + for active_iname in active_inames_set] + + if check_all_must_not_nests( + iname_orders_to_check, must_not_nest_constraints): + next_iname_candidates.add(next_iname) + else: + next_iname_candidates = source_inames + + # }}} else: - priority_tiers = [ - useful_loops_set + # {{{ old tier building with loop_priority + + # Build priority tiers. If a schedule is found in the first tier, then + # loops in the second are not even tried (and so on). + loop_priority_set = set().union(*[set(prio) + for prio in + sched_state.kernel.loop_priority]) + useful_loops_set = set(iname_to_usefulness.keys()) + useful_and_desired = useful_loops_set & loop_priority_set + + if useful_and_desired: + wanted = ( + useful_and_desired - sched_state.ilp_inames - sched_state.vec_inames - ] - - # vectorization must be the absolute innermost loop - priority_tiers.extend([ - [iname] - for iname in sched_state.ilp_inames - if iname in useful_loops_set - ]) + ) + priority_tiers = [t for t in + get_priority_tiers(wanted, + sched_state.kernel.loop_priority + ) + ] + + # Update the loop priority set, because some constraints may have + # have been contradictary. + loop_priority_set = set().union(*[set(t) for t in priority_tiers]) + + priority_tiers.append( + useful_loops_set + - loop_priority_set + - sched_state.ilp_inames + - sched_state.vec_inames + ) + else: + priority_tiers = [ + useful_loops_set + - sched_state.ilp_inames + - sched_state.vec_inames + ] + + # vectorization must be the absolute innermost loop + priority_tiers.extend([ + [iname] + for iname in sched_state.ilp_inames + if iname in useful_loops_set + ]) + + priority_tiers.extend([ + [iname] + for iname in sched_state.vec_inames + if iname in useful_loops_set + ]) - priority_tiers.extend([ - [iname] - for iname in sched_state.vec_inames - if iname in useful_loops_set - ]) + # }}} # }}} - if debug_mode: - print("useful inames: %s" % ",".join(useful_loops_set)) - else: - for tier in priority_tiers: + if sched_state.kernel.loop_nest_constraints: + # {{{ loop over next_iname_candidates generated w/ loop_nest_constraints + + if debug_mode: + print("useful inames: %s" % ",".join(useful_loops_set)) + else: found_viable_schedule = False - for iname in sorted(tier, + # loop over iname candidates; enter inames and recurse: + for iname in sorted(next_iname_candidates, key=lambda iname: ( iname_to_usefulness.get(iname, 0), # Sort by iname to achieve deterministic @@ -1382,6 +1481,7 @@ def insn_sort_key(insn_id): iname), reverse=True): + # enter the loop and recurse for sub_sched in generate_loop_schedules_internal( sched_state.copy( schedule=( @@ -1395,16 +1495,61 @@ def insn_sort_key(insn_id): insn_ids_to_try=insn_ids_to_try, preschedule=( sched_state.preschedule - if iname not in sched_state.prescheduled_inames + if iname not in + sched_state.prescheduled_inames else sched_state.preschedule[1:]), ), debug=debug): + found_viable_schedule = True yield sub_sched if found_viable_schedule: return + # }}} + else: + # {{{ old looping over tiers + + if debug_mode: + print("useful inames: %s" % ",".join(useful_loops_set)) + else: + for tier in priority_tiers: + found_viable_schedule = False + + for iname in sorted(tier, + key=lambda iname: ( + iname_to_usefulness.get(iname, 0), + # Sort by iname to achieve deterministic + # ordering of generated schedules. + iname), + reverse=True): + + for sub_sched in generate_loop_schedules_internal( + sched_state.copy( + schedule=( + sched_state.schedule + + (EnterLoop(iname=iname),)), + active_inames=( + sched_state.active_inames + (iname,)), + entered_inames=( + sched_state.entered_inames + | frozenset((iname,))), + insn_ids_to_try=insn_ids_to_try, + preschedule=( + sched_state.preschedule + if iname not in + sched_state.prescheduled_inames + else sched_state.preschedule[1:]), + ), + debug=debug): + found_viable_schedule = True + yield sub_sched + + if found_viable_schedule: + return + # }}} + # }}} if debug_mode: @@ -1415,10 +1560,31 @@ def insn_sort_key(insn_id): if inp: raise ScheduleDebugInputError(inp) + # {{{ Make sure ALL must_nest_constraints are satisfied + + # (The check above avoids contradicting some must_nest constraints, + # but we don't know if all required nestings are present) + must_constraints_satisfied = True + if sched_state.kernel.loop_nest_constraints: + from loopy.transform.iname import ( + get_iname_nestings, + loop_nest_constraints_satisfied, + ) + must_nest_constraints = sched_state.kernel.loop_nest_constraints.must_nest + if must_nest_constraints: + sched_tiers = get_iname_nestings(sched_state.schedule) + must_constraints_satisfied = loop_nest_constraints_satisfied( + sched_tiers, must_nest_constraints, + must_not_nest_constraints=None, # (checked upon loop creation) + all_inames=kernel.all_inames()) + + # }}} + if ( not sched_state.active_inames and not sched_state.unscheduled_insn_ids - and not sched_state.preschedule): + and not sched_state.preschedule + and must_constraints_satisfied): # if done, yield result debug.log_success(sched_state.schedule) @@ -2133,7 +2299,7 @@ def print_longest_dead_end(): key_builder=LoopyKeyBuilder()) -def _get_one_scheduled_kernel_inner(kernel, callables_table): +def _get_one_scheduled_kernel_inner(kernel, callables_table, debug_args=None): # This helper function exists to ensure that the generator chain is fully # out of scope after the function returns. This allows it to be # garbage-collected in the exit handler of the @@ -2143,7 +2309,8 @@ def _get_one_scheduled_kernel_inner(kernel, callables_table): # # See https://gitlab.tiker.net/inducer/sumpy/issues/31 for context. - return next(iter(generate_loop_schedules(kernel, callables_table))) + return next(iter(generate_loop_schedules( + kernel, callables_table, debug_args=debug_args))) def get_one_scheduled_kernel(kernel, callables_table): @@ -2155,7 +2322,7 @@ def get_one_scheduled_kernel(kernel, callables_table): return get_one_linearized_kernel(kernel, callables_table) -def get_one_linearized_kernel(kernel, callables_table): +def get_one_linearized_kernel(kernel, callables_table, debug_args=None): from loopy import CACHING_ENABLED # must include *callables_table* within the cache key as the preschedule @@ -2176,7 +2343,7 @@ def get_one_linearized_kernel(kernel, callables_table): with ProcessLogger(logger, "%s: schedule" % kernel.name): with MinRecursionLimitForScheduling(kernel): result = _get_one_scheduled_kernel_inner(kernel, - callables_table) + callables_table, debug_args=debug_args) if CACHING_ENABLED and not from_cache: schedule_cache.store_if_not_present(sched_cache_key, result) From 9725335d9b8ced521d9cf681647b4f9c98fe1d45 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Wed, 21 Jul 2021 15:43:23 -0500 Subject: [PATCH 13/19] test that nest constraints are enforced during linearization --- test/test_nest_constraints.py | 272 ++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 9e5f16e37..f2a808df5 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -24,6 +24,7 @@ import loopy as lp import numpy as np import pyopencl as cl +from loopy import preprocess_kernel, get_one_linearized_kernel import logging logger = logging.getLogger(__name__) @@ -47,6 +48,19 @@ from loopy.version import LOOPY_USE_LANGUAGE_VERSION_2018_2 # noqa +# {{{ Helper functions + +def _process_and_linearize(prog, knl_name="loopy_kernel"): + # Return linearization items along with the preprocessed kernel and + # linearized kernel + proc_prog = preprocess_kernel(prog) + lin_prog = get_one_linearized_kernel( + proc_prog[knl_name], proc_prog.callables_table) + return lin_prog + +# }}} + + # {{{ test_loop_constraint_string_parsing def test_loop_constraint_string_parsing(): @@ -367,6 +381,264 @@ def test_incompatible_nest_constraints(): # }}} +# {{{ test_vec_innermost: + +def test_vec_innermost(): + + def is_innermost(iname, lin_items): + from loopy.schedule import (EnterLoop, LeaveLoop) + + # find EnterLoop(iname) in linearization + enter_iname_idx = None + for i, lin_item in enumerate(lin_items): + if isinstance(lin_item, EnterLoop) and ( + lin_item.iname == iname): + enter_iname_idx = i + break + else: + # iname not found + return False + + # now go through remaining linearization items after EnterLoop(iname) + for lin_item in lin_items[enter_iname_idx+1:]: + if isinstance(lin_item, LeaveLoop): + # Break as soon as we find a LeaveLoop + # If this happens before we find an EnterLoop, iname is innermost + break + elif isinstance(lin_item, EnterLoop): + # we found an EnterLoop inside iname + return False + + return True + + ref_knl = lp.make_kernel( + "{ [g,h,i,j,k]: 0<=g,h,i,j,k 1: exec(sys.argv[1]) From 9a07784c0fdf3b121876f0ff110e864a8521d9e0 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Wed, 21 Jul 2021 15:45:47 -0500 Subject: [PATCH 14/19] raise AssertionError instead of asserting False --- test/test_nest_constraints.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index f2a808df5..ba6ecf196 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -448,7 +448,7 @@ def is_innermost(iname, lin_items): knl = lp.tag_inames(knl, {"h": "vec"}) try: lp.constrain_loop_nesting(knl, must_nest=("{g,h,i,j}", "{k}")) - assert False + raise AssertionError() except ValueError as e: assert ( "iname h tagged with ConcurrentTag, " @@ -461,7 +461,7 @@ def is_innermost(iname, lin_items): knl = lp.constrain_loop_nesting(knl, must_nest=("{g,h,i,j}", "{k}")) try: lp.tag_inames(knl, {"h": "vec"}) - assert False + raise AssertionError() except ValueError as e: assert ( "cannot tag 'h' as concurrent--iname involved " @@ -632,7 +632,7 @@ def loop_order(lin_items): proc_prog.callables_table, debug_args={"interactive": False}, ) - assert False + raise AssertionError() except RuntimeError as e: assert "no valid schedules found" in str(e) From ad8940b668e8cfea72c135a09e8fcb495afc0d39 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Fri, 23 Jul 2021 14:22:46 -0500 Subject: [PATCH 15/19] clearer comments/code for handling nest constraints during linearization --- loopy/schedule/__init__.py | 84 +++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 674ab47a3..5fe806a10 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1170,33 +1170,48 @@ def insn_sort_key(insn_id): # {{{ Don't leave if doing so would violate must_nest constraints # Don't leave if must_nest constraints require that - # additional inames be nested inside the current iname + # additional inames be nested inside the current iname. + # (Check for these inames in the must_nest graph.) if can_leave: must_nest_graph = ( sched_state.kernel.loop_nest_constraints.must_nest_graph if sched_state.kernel.loop_nest_constraints else None) if must_nest_graph: - # get inames that must nest inside the current iname + + # Get inames that must nest inside the current iname must_nest_inside = must_nest_graph[deepest_active_iname] if must_nest_inside: - # get scheduled inames that are nested inside current iname + # {{{ Get inames that are scheduled inside current iname + + # Iterate through already scheduled loop inames until + # we find deepest_active_iname, then create the set of + # inames that are already scheduled inside it. + already_nested_inside = set() + + # Switch to flip when we encounter deepest_active_iname within_deepest_active_iname = False - actually_nested_inside = set() + for sched_item in sched_state.schedule: if isinstance(sched_item, EnterLoop): if within_deepest_active_iname: - actually_nested_inside.add(sched_item.iname) + # Found iname nested inside deepest_active_iname + already_nested_inside.add(sched_item.iname) elif sched_item.iname == deepest_active_iname: + # Found deepest_active_iname within_deepest_active_iname = True elif (isinstance(sched_item, LeaveLoop) and sched_item.iname == deepest_active_iname): + # We're leaving deepest_active_iname, and have + # found all deeper inames break - # don't leave if must_nest constraints require that + # }}} + + # Don't leave if must_nest constraints require that # additional inames be nested inside the current iname - if not must_nest_inside.issubset(actually_nested_inside): + if not must_nest_inside.issubset(already_nested_inside): can_leave = False # }}} @@ -1354,7 +1369,9 @@ def insn_sort_key(insn_id): # scheduling an insn if sched_state.kernel.loop_nest_constraints: + # {{{ Use loop_nest_constraints in determining next_iname_candidates + # (ensure that candidates don't violate nest constraints) # Inames not yet entered that would get us closer to scheduling an insn: useful_loops_set = set(iname_to_usefulness.keys()) @@ -1370,26 +1387,30 @@ def insn_sort_key(insn_id): if useful_loops_set - sched_state.vec_inames: useful_loops_set -= sched_state.vec_inames + # {{{ Remove iname candidates that would violate must_nest + # To enter an iname without violating must_nest constraints, # iname must be a source in the induced subgraph of must_nest_graph - # containing inames in useful_loops_set - must_nest_graph_full = ( + # containing inames in useful_loops_set (graph has a key for every + # iname; inames without children are still sources) + complete_must_nest_graph = ( sched_state.kernel.loop_nest_constraints.must_nest_graph if sched_state.kernel.loop_nest_constraints else None) - if must_nest_graph_full: + if complete_must_nest_graph: must_nest_graph_useful = compute_induced_subgraph( - must_nest_graph_full, + complete_must_nest_graph, useful_loops_set ) source_inames = get_graph_sources(must_nest_graph_useful) else: + # No must_nest constraints were provided, all inames are + # childless sources in the non-existant must_nest graph source_inames = useful_loops_set - # Since graph has a key for every iname, - # sources should be the only valid iname candidates + # }}} + + # {{{ Remove iname candidates that would violate must_not_nest - # Check whether entering any source_inames violates - # must-not-nest constraints, given the currently active inames must_not_nest_constraints = ( sched_state.kernel.loop_nest_constraints.must_not_nest if sched_state.kernel.loop_nest_constraints else None) @@ -1404,11 +1425,16 @@ def insn_sort_key(insn_id): iname_orders_to_check, must_not_nest_constraints): next_iname_candidates.add(next_iname) else: + # No must_not_nest constraints were provided next_iname_candidates = source_inames # }}} + + # }}} + else: - # {{{ old tier building with loop_priority + + # {{{ Old tier building with loop_priority # Build priority tiers. If a schedule is found in the first tier, then # loops in the second are not even tried (and so on). @@ -1465,23 +1491,25 @@ def insn_sort_key(insn_id): # }}} if sched_state.kernel.loop_nest_constraints: - # {{{ loop over next_iname_candidates generated w/ loop_nest_constraints + # {{{ Enter inames in next_iname_candidates + # (which were curtailed by nest constraints) if debug_mode: print("useful inames: %s" % ",".join(useful_loops_set)) else: found_viable_schedule = False - # loop over iname candidates; enter inames and recurse: + # Loop over iname candidates; enter inames and recurse + + # Sort by iname to achieve deterministic ordering of generated + # schedules for iname in sorted(next_iname_candidates, key=lambda iname: ( iname_to_usefulness.get(iname, 0), - # Sort by iname to achieve deterministic - # ordering of generated schedules. iname), reverse=True): - # enter the loop and recurse + # Enter the loop and recurse for sub_sched in generate_loop_schedules_internal( sched_state.copy( schedule=( @@ -1509,7 +1537,7 @@ def insn_sort_key(insn_id): # }}} else: - # {{{ old looping over tiers + # {{{ Old looping over tiers (ignores nest constraints) if debug_mode: print("useful inames: %s" % ",".join(useful_loops_set)) @@ -1562,16 +1590,16 @@ def insn_sort_key(insn_id): # {{{ Make sure ALL must_nest_constraints are satisfied - # (The check above avoids contradicting some must_nest constraints, - # but we don't know if all required nestings are present) + # (The check above avoids entering loops that would contradict must_nest + # constraints, but we don't know if all required nestings are present) must_constraints_satisfied = True if sched_state.kernel.loop_nest_constraints: - from loopy.transform.iname import ( - get_iname_nestings, - loop_nest_constraints_satisfied, - ) must_nest_constraints = sched_state.kernel.loop_nest_constraints.must_nest if must_nest_constraints: + from loopy.transform.iname import ( + get_iname_nestings, + loop_nest_constraints_satisfied, + ) sched_tiers = get_iname_nestings(sched_state.schedule) must_constraints_satisfied = loop_nest_constraints_satisfied( sched_tiers, must_nest_constraints, From 42a109a2e2c3578e674ad49e294b6a9de484c076 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Fri, 23 Jul 2021 14:55:28 -0500 Subject: [PATCH 16/19] remove unnecessary all_inames arg from nest constraint checking functions --- loopy/schedule/__init__.py | 2 +- loopy/transform/iname.py | 45 +++++++++++++++++++---------------- test/test_nest_constraints.py | 26 ++++++++++---------- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index 5fe806a10..dfbd5b7df 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1604,7 +1604,7 @@ def insn_sort_key(insn_id): must_constraints_satisfied = loop_nest_constraints_satisfied( sched_tiers, must_nest_constraints, must_not_nest_constraints=None, # (checked upon loop creation) - all_inames=kernel.all_inames()) + ) # }}} diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 4662704c8..e229e5170 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -617,12 +617,12 @@ def _expand_iname_sets_in_tuple( # {{{ check_must_nest -def check_must_nest(all_loop_nests, must_nest, all_inames): +def check_must_nest(all_loop_nests, must_nest): r"""Determine whether must_nest constraint is satisfied by all_loop_nests - :arg all_loop_nests: A list of lists of inames, each representing - the nesting order of nested loops. + :arg all_loop_nests: A sequence of sequences of inames, each representing + the order of nested loops. :arg must_nest: A tuple of :class:`UnexpandedInameSet`\ s describing nestings that must appear in all_loop_nests. @@ -637,17 +637,19 @@ def check_must_nest(all_loop_nests, must_nest, all_inames): # FIXME instead of expanding tiers into all pairs up front, # create these pairs one at a time so that we can stop as soon as we fail + # Get all must-nest pairs of inames must_nest_expanded = _expand_iname_sets_in_tuple(must_nest) - # must_nest_expanded contains pairs for before, after in must_nest_expanded: - found = False + correct_pair_found = False for nesting in all_loop_nests: + # If both before and after are found in the nesting, + # and they're ordered correctly, this must-nest pair is satisfied if before in nesting and after in nesting and ( nesting.index(before) < nesting.index(after)): - found = True + correct_pair_found = True break - if not found: + if not correct_pair_found: return False return True @@ -660,8 +662,8 @@ def check_must_not_nest(all_loop_nests, must_not_nest): r"""Determine whether must_not_nest constraint is satisfied by all_loop_nests - :arg all_loop_nests: A list of lists of inames, each representing - the nesting order of nested loops. + :arg all_loop_nests: A sequence of sequences of inames, each representing + the order of nested loops. :arg must_not_nest: A two-tuple of :class:`UnexpandedInameSet`\ s describing nestings that must not appear in all_loop_nests. @@ -673,12 +675,10 @@ def check_must_not_nest(all_loop_nests, must_not_nest): # Note that must_not_nest may only contain two tiers for nesting in all_loop_nests: - - # Go through each pair in all_loop_nests + # Go through each pair in all_loop_nests and check + # whether it violates must not nest for i, iname_before in enumerate(nesting): for iname_after in nesting[i+1:]: - - # Check whether it violates must not nest if (must_not_nest[0].contains(iname_before) and must_not_nest[1].contains(iname_after)): # Stop as soon as we fail @@ -693,10 +693,13 @@ def check_must_not_nest(all_loop_nests, must_not_nest): def check_all_must_not_nests(all_loop_nests, must_not_nests): r"""Determine whether all must_not_nest constraints are satisfied by all_loop_nests - :arg all_loop_nests: A list of lists of inames, each representing - the nesting order of nested loops. + + :arg all_loop_nests: A sequence of sequences of inames, each representing + the order of nested loops. + :arg must_not_nests: A set of two-tuples of :class:`UnexpandedInameSet`\ s describing nestings that must not appear in all_loop_nests. + :returns: A :class:`bool` indicating whether the must_not_nest constraints are satisfied by the provided loop nesting. """ @@ -715,12 +718,12 @@ def loop_nest_constraints_satisfied( all_loop_nests, must_nest_constraints=None, must_not_nest_constraints=None, - all_inames=None): - r"""Determine whether must_not_nest constraint is satisfied by - all_loop_nests + ): + r"""Determine whether must_nest and must_not_nest constraints are satisfied + by all_loop_nests - :arg all_loop_nests: A set of lists of inames, each representing - the nesting order of loops. + :arg all_loop_nests: A sequence of sequences of inames, each representing + the order of nested loops. :arg must_nest_constraints: An iterable of tuples of :class:`UnexpandedInameSet`\ s, each describing nestings that must @@ -738,7 +741,7 @@ def loop_nest_constraints_satisfied( if must_nest_constraints: for must_nest in must_nest_constraints: if not check_must_nest( - all_loop_nests, must_nest, all_inames): + all_loop_nests, must_nest): return False # Check must-not-nest constraints diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index ba6ecf196..83468ad19 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -206,8 +206,6 @@ def test_loop_nest_constraints_satisfied(): loop_nest_constraints_satisfied, ) - all_inames = frozenset(["g", "h", "i", "j", "k"]) - must_nest_constraints = [ process_loop_nest_specification( nesting=("{g,h}", "{i,j,k}"), @@ -221,17 +219,17 @@ def test_loop_nest_constraints_satisfied(): loop_nests = set([("g", "h", "i", "j", "k"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, must_nest_constraints, must_not_nest_constraints, all_inames) + loop_nests, must_nest_constraints, must_not_nest_constraints) assert valid loop_nests = set([("g", "i", "h", "j", "k"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, must_nest_constraints, must_not_nest_constraints, all_inames) + loop_nests, must_nest_constraints, must_not_nest_constraints) assert not valid loop_nests = set([("g", "h", "i", "k", "j"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, must_nest_constraints, must_not_nest_constraints, all_inames) + loop_nests, must_nest_constraints, must_not_nest_constraints) assert not valid # now j, k must be innermost @@ -240,47 +238,47 @@ def test_loop_nest_constraints_satisfied(): ] loop_nests = set([("g", "i", "h", "j", "k"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert valid loop_nests = set([("g", "h", "i", "k", "j"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert valid loop_nests = set([("g", "i", "j", "h", "k"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert not valid loop_nests = set([("g", "h", "j", "k", "i"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert not valid loop_nests = set([("j", "k"), ]) valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert valid loop_nests = set([("g", "k"), ]) # j not present valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert valid loop_nests = set([("g", "i"), ]) # j, k not present valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert valid loop_nests = set([("k",), ]) # only k present valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert valid loop_nests = set([("i",), ]) valid = loop_nest_constraints_satisfied( - loop_nests, None, must_not_nest_constraints, all_inames) + loop_nests, None, must_not_nest_constraints) assert valid # }}} From d6bc3c076ae01641a3907d2c6575a6a354c5de8f Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Fri, 23 Jul 2021 15:00:28 -0500 Subject: [PATCH 17/19] eliminate mostly redundant function check_all_must_not_nests --- loopy/schedule/__init__.py | 7 ++++--- loopy/transform/iname.py | 24 ------------------------ 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/loopy/schedule/__init__.py b/loopy/schedule/__init__.py index dfbd5b7df..229bd4d68 100644 --- a/loopy/schedule/__init__.py +++ b/loopy/schedule/__init__.py @@ -1377,7 +1377,7 @@ def insn_sort_key(insn_id): useful_loops_set = set(iname_to_usefulness.keys()) from loopy.transform.iname import ( - check_all_must_not_nests, + loop_nest_constraints_satisfied, get_graph_sources, ) from pytools.graph import compute_induced_subgraph @@ -1421,8 +1421,9 @@ def insn_sort_key(insn_id): (active_iname, next_iname) for active_iname in active_inames_set] - if check_all_must_not_nests( - iname_orders_to_check, must_not_nest_constraints): + if loop_nest_constraints_satisfied( + iname_orders_to_check, + must_not_nest_constraints=must_not_nest_constraints): next_iname_candidates.add(next_iname) else: # No must_not_nest constraints were provided diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index e229e5170..13fe2a695 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -688,30 +688,6 @@ def check_must_not_nest(all_loop_nests, must_not_nest): # }}} -# {{{ check_all_must_not_nests - -def check_all_must_not_nests(all_loop_nests, must_not_nests): - r"""Determine whether all must_not_nest constraints are satisfied by - all_loop_nests - - :arg all_loop_nests: A sequence of sequences of inames, each representing - the order of nested loops. - - :arg must_not_nests: A set of two-tuples of :class:`UnexpandedInameSet`\ s - describing nestings that must not appear in all_loop_nests. - - :returns: A :class:`bool` indicating whether the must_not_nest constraints - are satisfied by the provided loop nesting. - """ - - for must_not_nest in must_not_nests: - if not check_must_not_nest(all_loop_nests, must_not_nest): - return False - return True - -# }}} - - # {{{ loop_nest_constraints_satisfied def loop_nest_constraints_satisfied( From f7f8f2aa3a383ca4eb8bd8e9d4d24de67615df8c Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Fri, 23 Jul 2021 15:32:26 -0500 Subject: [PATCH 18/19] more code cleanup --- loopy/transform/iname.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/loopy/transform/iname.py b/loopy/transform/iname.py index 13fe2a695..bff63102d 100644 --- a/loopy/transform/iname.py +++ b/loopy/transform/iname.py @@ -157,7 +157,8 @@ def get_inames_represented(self, iname_universe=None): return self.inames.copy() def __lt__(self, other): - # FIXME is this function really necessary? If so, what should it return? + # FIXME is this function really necessary? (for caching?) + # If so, what should it return? return self.__hash__() < other.__hash__() def __hash__(self): @@ -440,7 +441,7 @@ def constrain_loop_nesting( # {{{ Update must_nest graph (and check for cycles) - must_nest_graph_new = update_must_nest_graph( + must_nest_graph_new = add_to_must_nest_graph( must_nest_graph_old, must_nest_tuple, kernel.all_inames()) # }}} @@ -539,10 +540,10 @@ def constrain_loop_nesting( # }}} -# {{{ update_must_nest_graph +# {{{ add_to_must_nest_graph -def update_must_nest_graph(must_nest_graph, must_nest, all_inames): - # Note: there should *not* be any complements in the must_nest tuples +def add_to_must_nest_graph(must_nest_graph, new_must_nest, all_inames): + # Note: there should not be any complements in the new_must_nest tuples from copy import deepcopy new_graph = deepcopy(must_nest_graph) @@ -551,8 +552,8 @@ def update_must_nest_graph(must_nest_graph, must_nest, all_inames): for missing_iname in all_inames - new_graph.keys(): new_graph[missing_iname] = set() - # Expand must_nest into (before, after) pairs - must_nest_expanded = _expand_iname_sets_in_tuple(must_nest, all_inames) + # Expand new_must_nest into (before, after) pairs + must_nest_expanded = _expand_iname_sets_in_tuple(new_must_nest, all_inames) # Update must_nest_graph with new pairs for before, after in must_nest_expanded: @@ -566,10 +567,10 @@ def update_must_nest_graph(must_nest_graph, must_nest, all_inames): # Check for inconsistent must_nest constraints by checking for cycle: if contains_cycle(new_graph_closure): raise ValueError( - "update_must_nest_graph: Nest constraint cycle detected. " + "add_to_must_nest_graph: Nest constraint cycle detected. " "must_nest constraints %s inconsistent with existing " "must_nest constraints %s." - % (must_nest, must_nest_graph)) + % (new_must_nest, must_nest_graph)) return new_graph_closure @@ -591,7 +592,7 @@ def _expand_iname_sets_in_tuple( # Now expand all priority tuples into (before, after) pairs using # Cartesian product of all pairs of sets - # (Assumes prio_sets length > 1) + # (Assumes prio_sets length > 1, which is enforced elsewhere) import itertools loop_priority_pairs = set() for i, before_set in enumerate(positively_defined_iname_sets[:-1]): @@ -599,7 +600,7 @@ def _expand_iname_sets_in_tuple( loop_priority_pairs.update( list(itertools.product(before_set, after_set))) - # Make sure no priority tuple contains an iname twice + # Make sure no priority tuple contains an iname twice (cycle) for prio_tuple in loop_priority_pairs: if len(set(prio_tuple)) != len(prio_tuple): raise ValueError( From cd76f66e29dfa043c26ac96ae9ee953b917bd6c1 Mon Sep 17 00:00:00 2001 From: jdsteve2 Date: Fri, 23 Jul 2021 15:49:37 -0500 Subject: [PATCH 19/19] add a test that tries adding a must_not_nest constraint that conflicts with a vec tag --- test/test_nest_constraints.py | 152 ++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 73 deletions(-) diff --git a/test/test_nest_constraints.py b/test/test_nest_constraints.py index 83468ad19..70f821715 100644 --- a/test/test_nest_constraints.py +++ b/test/test_nest_constraints.py @@ -218,68 +218,68 @@ def test_loop_nest_constraints_satisfied(): ] loop_nests = set([("g", "h", "i", "j", "k"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, must_nest_constraints, must_not_nest_constraints) - assert valid + assert satisfied loop_nests = set([("g", "i", "h", "j", "k"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, must_nest_constraints, must_not_nest_constraints) - assert not valid + assert not satisfied loop_nests = set([("g", "h", "i", "k", "j"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, must_nest_constraints, must_not_nest_constraints) - assert not valid + assert not satisfied # now j, k must be innermost must_not_nest_constraints = [ process_loop_nest_specification(("{k,j}", "~{k,j}")), ] loop_nests = set([("g", "i", "h", "j", "k"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert valid + assert satisfied loop_nests = set([("g", "h", "i", "k", "j"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert valid + assert satisfied loop_nests = set([("g", "i", "j", "h", "k"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert not valid + assert not satisfied loop_nests = set([("g", "h", "j", "k", "i"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert not valid + assert not satisfied loop_nests = set([("j", "k"), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert valid + assert satisfied loop_nests = set([("g", "k"), ]) # j not present - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert valid + assert satisfied loop_nests = set([("g", "i"), ]) # j, k not present - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert valid + assert satisfied loop_nests = set([("k",), ]) # only k present - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert valid + assert satisfied loop_nests = set([("i",), ]) - valid = loop_nest_constraints_satisfied( + satisfied = loop_nest_constraints_satisfied( loop_nests, None, must_not_nest_constraints) - assert valid + assert satisfied # }}} @@ -384,9 +384,10 @@ def test_incompatible_nest_constraints(): def test_vec_innermost(): def is_innermost(iname, lin_items): + """Return True if iname is nested innermost""" from loopy.schedule import (EnterLoop, LeaveLoop) - # find EnterLoop(iname) in linearization + # Find EnterLoop(iname) in linearization enter_iname_idx = None for i, lin_item in enumerate(lin_items): if isinstance(lin_item, EnterLoop) and ( @@ -394,21 +395,20 @@ def is_innermost(iname, lin_items): enter_iname_idx = i break else: - # iname not found + # Iname not found return False - # now go through remaining linearization items after EnterLoop(iname) + # Now check linearization items after EnterLoop(iname) + # and before LeaveLoop(iname) to see if another loop is entered for lin_item in lin_items[enter_iname_idx+1:]: if isinstance(lin_item, LeaveLoop): - # Break as soon as we find a LeaveLoop - # If this happens before we find an EnterLoop, iname is innermost - break + # We found a LeaveLoop *before* finding an EnterLoop, so + # iname is innermost + return True elif isinstance(lin_item, EnterLoop): - # we found an EnterLoop inside iname + # We found an EnterLoop inside iname return False - return True - ref_knl = lp.make_kernel( "{ [g,h,i,j,k]: 0<=g,h,i,j,k