From 9f097a381daa63b159f55054667b7a8fe02264c7 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 7 Nov 2022 17:09:40 -0600 Subject: [PATCH 01/52] Sketch out where precise dependencies might go in the IR --- loopy/kernel/creation.py | 28 +----- loopy/kernel/instruction.py | 178 +++++++++++++++++++++++++++------- loopy/transform/dependency.py | 29 ++++++ 3 files changed, 171 insertions(+), 64 deletions(-) create mode 100644 loopy/transform/dependency.py diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index 3fc3dfe9f..b343c7daf 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -1441,32 +1441,6 @@ def add_assignment(base_name, expr, dtype, additional_inames): # }}} -# {{{ add_sequential_dependencies - -def add_sequential_dependencies(knl): - new_insns = [] - prev_insn = None - for insn in knl.instructions: - depon = insn.depends_on - if depon is None: - depon = frozenset() - - if prev_insn is not None: - depon = depon | frozenset((prev_insn.id,)) - - insn = insn.copy( - depends_on=depon, - depends_on_is_final=True) - - new_insns.append(insn) - - prev_insn = insn - - return knl.copy(instructions=new_insns) - -# }}} - - # {{{ temporary variable creation def create_temporaries(knl, default_order): @@ -2490,7 +2464,7 @@ def make_function(domains, instructions, kernel_data=None, **kwargs): check_for_duplicate_insn_ids(knl) if seq_dependencies: - knl = add_sequential_dependencies(knl) + knl = add_lexicographic_happens_after(knl) assert len(knl.instructions) == len(inames_to_dup) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 1f04b549f..cc841ff38 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -20,16 +20,21 @@ THE SOFTWARE. """ +from dataclasses import dataclass +from typing import FrozenSet, Optional, Mapping, Tuple, Type, Union from sys import intern from functools import cached_property +from collections.abc import Mapping as MappingABC + from warnings import warn import islpy as isl from pytools import ImmutableRecord, memoize_method from pytools.tag import Tag, tag_dataclass, Taggable from loopy.diagnostic import LoopyError -from loopy.tools import Optional +from loopy.tools import Optional as LoopyOptional +from loopy.typing import ExpressionT # {{{ instruction tags @@ -75,6 +80,37 @@ class UseStreamingStoreTag(Tag): # }}} +# {{{ HappensAfter + +@dataclass(frozen=True) +class HappensAfter: + """A class representing a "happens-before" relationship between two + statements found in a :class:`loopy.LoopKernel`. Used to validate that a + given kernel transformation respects the data dependencies in a given + program. + + .. attribute:: variable_name + + If this is a precise dependency, the name of the variable name in + :attr:`after_id` that mediates the dependency. + + .. attribute:: instances_rel + + An :class:`isl.Map` representing the happens-after relationship. The + input of the map is an iname tuple and the output of the map is a set + of iname tuples that must execute after the input. + + As a (deprecated) matter of backward compatibility, this may be *None*, + in which case the semantics revert to the (underspecified) statement-level + dependencies of prior versions of :mod:`loopy`. + """ + + variable_name: Optional[str] + instances_rel: Optional[isl.Map] + +# }}} + + # {{{ instructions: base class class InstructionBase(ImmutableRecord, Taggable): @@ -198,10 +234,20 @@ class InstructionBase(ImmutableRecord, Taggable): Inherits from :class:`pytools.tag.Taggable`. """ + id: Optional[str] + happens_after: Mapping[str, HappensAfter] + depends_on_is_final: bool + groups: FrozenSet[str] + conflicts_with_groups: FrozenSet[str] + no_sync_with: FrozenSet[Tuple[str, str]] + predicates: FrozenSet[ExpressionT] + within_inames: FrozenSet[str] + within_inames_is_final: bool + priority: int # within_inames_is_final is deprecated and will be removed in version 2017.x. - fields = set("id depends_on depends_on_is_final " + fields = set("id depends_on_is_final " "groups conflicts_with_groups " "no_sync_with " "predicates " @@ -214,12 +260,22 @@ class InstructionBase(ImmutableRecord, Taggable): # Names of fields that are sets of pymbolic expressions. Needed for key building pymbolic_set_fields = {"predicates"} - def __init__(self, id, depends_on, depends_on_is_final, - groups, conflicts_with_groups, - no_sync_with, - within_inames_is_final, within_inames, - priority, - predicates, tags): + def __init__(self, + id: Optional[str], + happens_after: Union[ + Mapping[str, HappensAfter], FrozenSet[str], str, None], + depends_on_is_final: Optional[bool], + groups: Optional[FrozenSet[str]], + conflicts_with_groups: Optional[FrozenSet[str]], + no_sync_with: Optional[FrozenSet[Tuple[str, str]]], + within_inames_is_final: Optional[bool], + within_inames: Optional[FrozenSet[str]], + priority: Optional[int], + predicates: Optional[FrozenSet[str]], + tags: Optional[FrozenSet[Tag]], + *, + depends_on: Union[FrozenSet[str], str, None]=None, + ) -> None: if predicates is None: predicates = frozenset() @@ -241,8 +297,45 @@ def __init__(self, id, depends_on, depends_on_is_final, predicates = frozenset(new_predicates) del new_predicates - if depends_on is None: - depends_on = frozenset() + # {{{ process happens_after/depends_on + + if happens_after is not None and depends_on is not None: + raise TypeError("may not pass both happens_after and depends_on") + elif depends_on is not None: + happens_after = depends_on + + del depends_on + + if depends_on_is_final and happens_after is None: + raise LoopyError("Setting depends_on_is_final to True requires " + "actually specifying happens_after/depends_on") + + if happens_after is None: + happens_after = {} + elif isinstance(happens_after, str): + warn("Passing a string for happens_after/depends_on is deprecated and " + "will stop working in 2024. Instead, pass a full-fledged " + "happens_after data structure.", DeprecationWarning, stacklevel=2) + + happens_after = { + after_id.strip(): HappensAfter( + variable_name=None, + instances_rel=None) + for after_id in happens_after.split(",") + if after_id.strip()} + elif isinstance(happens_after, frozenset): + happens_after = { + after_id: HappensAfter( + variable_name=None, + instances_rel=None) + for after_id in happens_after} + elif isinstance(happens_after, MappingABC): + pass + else: + raise TypeError("'happens_after' has unexpected type: " + f"{type(happens_after)}") + + # }}} if groups is None: groups = frozenset() @@ -259,17 +352,9 @@ def __init__(self, id, depends_on, depends_on_is_final, if within_inames_is_final is None: within_inames_is_final = False - if isinstance(depends_on, str): - depends_on = frozenset( - s.strip() for s in depends_on.split(",") if s.strip()) - if depends_on_is_final is None: depends_on_is_final = False - if depends_on_is_final and not isinstance(depends_on, frozenset): - raise LoopyError("Setting depends_on_is_final to True requires " - "actually specifying depends_on") - if tags is None: tags = frozenset() @@ -292,13 +377,12 @@ def __init__(self, id, depends_on, depends_on_is_final, # assert all(is_interned(pred) for pred in predicates) assert isinstance(within_inames, frozenset) - assert isinstance(depends_on, frozenset) or depends_on is None assert isinstance(groups, frozenset) assert isinstance(conflicts_with_groups, frozenset) ImmutableRecord.__init__(self, id=id, - depends_on=depends_on, + happens_after=happens_after, depends_on_is_final=depends_on_is_final, no_sync_with=no_sync_with, groups=groups, conflicts_with_groups=conflicts_with_groups, @@ -354,6 +438,10 @@ def with_transformed_expressions(self, f, assignee_f=None): # }}} + @property + def depends_on(self): + return frozenset(self.happens_after) + @property def assignee_name(self): """A convenience wrapper around :meth:`assignee_var_names` @@ -462,7 +550,9 @@ def __setstate__(self, val): if self.id is not None: # pylint:disable=access-member-before-definition self.id = intern(self.id) - self.depends_on = intern_frozenset_of_ids(self.depends_on) + self.happens_after = { + intern(after_id): ha + for after_id, ha in self.happens_after.items()} self.groups = intern_frozenset_of_ids(self.groups) self.conflicts_with_groups = ( intern_frozenset_of_ids(self.conflicts_with_groups)) @@ -794,30 +884,43 @@ class Assignment(MultiAssignmentBase): .. automethod:: __init__ """ + assignee: ExpressionT + expression: ExpressionT + temp_var_type: LoopyOptional + atomicity: Tuple[VarAtomicity, ...] + fields = MultiAssignmentBase.fields | \ set("assignee temp_var_type atomicity".split()) pymbolic_fields = MultiAssignmentBase.pymbolic_fields | {"assignee"} def __init__(self, - assignee, expression, - id=None, - depends_on=None, - depends_on_is_final=None, - groups=None, - conflicts_with_groups=None, - no_sync_with=None, - within_inames_is_final=None, - within_inames=None, - tags=None, - temp_var_type=_not_provided, atomicity=(), - priority=0, predicates=frozenset()): + assignee: Union[str, ExpressionT], + expression: Union[str, ExpressionT], + id: Optional[str] = None, + happens_after: Union[ + Mapping[str, HappensAfter], FrozenSet[str], str, None] = None, + depends_on_is_final: Optional[bool] = None, + groups: Optional[FrozenSet[str]] = None, + conflicts_with_groups: Optional[FrozenSet[str]] = None, + no_sync_with: Optional[FrozenSet[Tuple[str, str]]] = None, + within_inames_is_final: Optional[bool] = None, + within_inames: Optional[FrozenSet[str]] = None, + priority: Optional[int] = None, + predicates: Optional[FrozenSet[str]] = None, + tags: Optional[FrozenSet[Tag]] = None, + temp_var_type: Union[ + Type[_not_provided], None, LoopyOptional] = _not_provided, + atomicity: Tuple[VarAtomicity, ...] = (), + *, + depends_on: Union[FrozenSet[str], str, None] = None, + ) -> None: if temp_var_type is _not_provided: - temp_var_type = Optional() + temp_var_type = LoopyOptional() super().__init__( id=id, - depends_on=depends_on, + happens_after=happens_after, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, @@ -826,7 +929,8 @@ def __init__(self, within_inames=within_inames, priority=priority, predicates=predicates, - tags=tags) + tags=tags, + depends_on=depends_on) from loopy.symbolic import parse if isinstance(assignee, str): @@ -1002,7 +1106,7 @@ def __init__(self, self.expression = expression if temp_var_types is None: - self.temp_var_types = (Optional(),) * len(self.assignees) + self.temp_var_types = (LoopyOptional(),) * len(self.assignees) else: self.temp_var_types = tuple( _check_and_fix_temp_var_type(tvt, stacklevel=3) diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py new file mode 100644 index 000000000..b895ce453 --- /dev/null +++ b/loopy/transform/dependency.py @@ -0,0 +1,29 @@ +__copyright__ = "Copyright (C) 2022 Addison Alvey-Blanco" + +__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. +""" + +from loopy.kernel import LoopKernel +from loopy.translation_unit import for_each_kernel + + +@for_each_kernel +def narrow_dependencies(kernel: LoopKernel) -> LoopKernel: + pass From 779c2f0dd356ea794d95c1e55781fc22fa685f96 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Mon, 7 Nov 2022 21:20:40 -0600 Subject: [PATCH 02/52] New branch --- .DS_Store | Bin 0 -> 8196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ab9371dc6d6a5b2437104e04bd52461adc69298a GIT binary patch literal 8196 zcmeHMJ#Q015S_J+Y{imsg;D@IAsP@Sq@l$`hK2^@f&xVX+lg&q=d8qz5-Jmk2Acc< z8d@4!s3MU%q5z3hAbtQ6B}gc|{qWta@6L7;g2b-1JExu5eed4x-Fj>y5=(=|BGEh% z6bN9Ql{q`tiX6$tk(T*FB0cEoB~b(r+`zyDc}_NcND;xElV!p+}B>$ zIt82p|D^)*{otWWTQ+v6t$KB!Q7Hgy2HmRQx+pJTJ!8wp4z+~`Md+chdZ;idhS0;& z@0qx=u|sY3a1ti@5C&P83`Hn(%lAPbL=}*^dz!9No!Xry&)?Grp{MIt zDz%PZLr<=AbX&Yw-M`7bjS1dfi_fZ_&J6IH)TX|LcyMM8a|S++`SWZ3!K-Hqlleo` zIKPGaX%ACVc7#hU8c@wbr?c;)5uC%X-Qd2*=IQz&ljJCi4@uc+j%u_*YtX($U3x_8 z^q4kmR)x)^IL__ILMdg|Bz8{Da9XU*Q>|ATJlnKpGoxiNGak<6PUrH7OW+OliF#x3 zvQWf_E`2D{9$%*`Z-F}&!SSn&jT>Kf)Lw~1g*=j@Y(8ax;Vv8CQ@8FvRd^YCIR4W? zfN#ce^`l-3FZD*^<*+NZupheAqYkde_QdaMTny*1a~SN^$XuMwq&SP0_#kr3#lS<2j4?2n+xEuEZ~d_;`|6@D6EGN$yNqCEsF1=kHI8 z=Ny-x-#hyJL~)#m!CB8b#<~x>EAJx@1=pctAGL%QD~xt*l@DJhM{@4p-t|ZOJz-wX zdU~|QSAW^cj5t~vXBW*?oZ{(md^_y6N1 z;bwOVI0a5r0g Date: Fri, 13 Jan 2023 12:42:02 -0600 Subject: [PATCH 03/52] Added draft of new version of dependency finding --- .DS_Store | Bin 8196 -> 8196 bytes loopy/kernel/creation.py | 3 +- loopy/kernel/dependency.py | 100 +++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 loopy/kernel/dependency.py diff --git a/.DS_Store b/.DS_Store index ab9371dc6d6a5b2437104e04bd52461adc69298a..a55d04f2cbdc3358d7dc4b9a5275c6bf19cd3730 100644 GIT binary patch delta 578 zcmZp1XmQvuRUr1Ns5}D$0}F#5LpnnyLrHGFi%U{YeiBfOV~6f!SEk-0j;Qh}c;yQ+ z41<&Na|?ia7#QwP{v@ExxNx(W;BrRB{gd|z$?5EhKR25JVkSF7GDAK?9zzL35zywO z+7h}crNAt)*k20>Izo+47i_VJ4PqWbR1UZP5qEkt<`+$g4d-ji!Yr6wN$it79V zd2f=K%;a}SJS|3n$=YJl4yONrZeam>IF+FoHK@I;Zq0!P3z9s-9T54=ZDL()2%87g HA$5!ZnkJ}s delta 578 zcmZp1XmQvuRUr197#9Nr0}F#5LpnnyLrHGFi%U{YeiBfOwQ8n31RB5t>C=Y@g#dLG^DCT4VOo_10uhd2)2Tigc diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index b343c7daf..7a0e6918b 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -1894,7 +1894,8 @@ def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True, if error_if_used: raise LoopyError(msg) - insn = insn.copy(depends_on=new_deps) +# insn = insn.copy(depends_on=new_deps) + insn = insn.copy(happens_after=new_deps) changed = True new_insns.append(insn) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py new file mode 100644 index 000000000..954dd8ffe --- /dev/null +++ b/loopy/kernel/dependency.py @@ -0,0 +1,100 @@ +import pymbolic.primitives as p + +from dataclasses import dataclass +from islpy import Map +from typing import FrozenSet, Optional, List + +from loopy import LoopKernel +from loopy import InstructionBase +from loopy.symbolic import WalkMapper + +@dataclass(frozen=True) +class HappensAfter: + variable_name: Optional[str] + instances_rel: Optional[Map] + +class AccessMapMapper(WalkMapper): + + def __init__(self, kernel, var_names): + self.kernel = kernel + self._var_names = var_names + + from collections import defaultdict + self.access_maps = defaultdict(lambda: + defaultdict(lambda: + defaultdict(lambda: None))) + + super.__init__() + + def map_subscript(self, expr, inames, insn_id): + + domain = self.kernel.get_inames_domain(inames) + + # why do we need this? + WalkMapper.map_subscript(self, expr, inames) + + assert isinstance(expr.aggregate, p.Variable) + + if expr.aggregate.name not in self._var_names: + return + + arg_name = expr.aggregate.name + subscript = expr.index_tuple + + descriptor = self.kernel.get_var_descriptor(arg_name) + + from loopy.diagnostic import UnableToDetermineAccessRangeError + from loopy.symbolic import get_access_map + + try: + access_map = get_access_map(domain, subscript) + except UnableToDetermineAccessRangeError: + return + + if self.access_maps[insn_id][arg_name][inames] is None: + self.access_maps[insn_id][arg_name][inames] = access_map + +def compute_happens_after(knl: LoopKernel) -> LoopKernel: + writer_map = knl.writer_map() + variables = knl.all_variable_names - knl.inames.keys() + + amap = AccessMapMapper(knl, variables) + + dep_map = { + insn.id: insn.read_dependency_names() - insn.within_inames + for insn in knl.instructions + } + + new_insns = [] + for insn in knl.instructions: + current_insn = insn.id + inames = insn.within_inames + + new_happens_after = [] + for var in dep_map[insn.id]: + for writer in (writer_map.get(var, set()) - { current_insn }): + + # get relation for current instruction and a write instruction + cur_relation = amap.access_maps[current_insn][var][inames] + write_relation = amap.access_maps[writer][var][inames] + + # compute the dependency relation + dep_relation = cur_relation.apply_range(write_relation.reverse()) + + # create the mapping from writer -> (variable, dependency rel'n) + happens_after = HappensAfter(var, dep_relation) + happens_after_mapping = { writer: happens_after } + + # add to the new list of dependencies + new_happens_after |= happens_after_mapping + + # update happens_after of our current instruction with the mapping + insn = insn.copy(happens_after=new_happens_after) + new_insns.append(insn) + + # return the kernel with the new instructions + return knl.copy(instructions=new_insns) + +def add_lexicographic_happens_after(knl: LoopKernel) -> None: + pass + From ba3fbf577e9cf7b3cf9c4d104bea0452a28959c4 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Fri, 13 Jan 2023 12:52:01 -0600 Subject: [PATCH 04/52] Added some documentation --- loopy/kernel/dependency.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 954dd8ffe..06386809a 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -14,6 +14,12 @@ class HappensAfter: instances_rel: Optional[Map] class AccessMapMapper(WalkMapper): + """ + TODO Update this documentation so it reflects proper formatting + + Used instead of BatchedAccessMapMapper to get single access maps for each + instruction. + """ def __init__(self, kernel, var_names): self.kernel = kernel @@ -55,6 +61,12 @@ def map_subscript(self, expr, inames, insn_id): self.access_maps[insn_id][arg_name][inames] = access_map def compute_happens_after(knl: LoopKernel) -> LoopKernel: + """ + TODO Update documentation to reflect the proper format. + + Determine dependency relations that exist between instructions. Similar to + apply_single_writer_dependency_heuristic. Extremely rough draft. + """ writer_map = knl.writer_map() variables = knl.all_variable_names - knl.inames.keys() From 990624b9800db43bb6a59a43d9827404a7109087 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Fri, 20 Jan 2023 09:51:47 -0600 Subject: [PATCH 05/52] Started implementation of add_lexicographic_... --- loopy/kernel/dependency.py | 68 +++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 06386809a..61af2fd35 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -1,11 +1,11 @@ +import islpy as isl import pymbolic.primitives as p from dataclasses import dataclass from islpy import Map -from typing import FrozenSet, Optional, List +from typing import Optional from loopy import LoopKernel -from loopy import InstructionBase from loopy.symbolic import WalkMapper @dataclass(frozen=True) @@ -18,10 +18,10 @@ class AccessMapMapper(WalkMapper): TODO Update this documentation so it reflects proper formatting Used instead of BatchedAccessMapMapper to get single access maps for each - instruction. + instruction. """ - def __init__(self, kernel, var_names): + def __init__(self, kernel: LoopKernel, var_names: set): self.kernel = kernel self._var_names = var_names @@ -29,14 +29,13 @@ def __init__(self, kernel, var_names): self.access_maps = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: None))) - + super.__init__() - def map_subscript(self, expr, inames, insn_id): + def map_subscript(self, expr: p.expression, inames: frozenset, insn_id: str): domain = self.kernel.get_inames_domain(inames) - # why do we need this? WalkMapper.map_subscript(self, expr, inames) assert isinstance(expr.aggregate, p.Variable) @@ -47,8 +46,6 @@ def map_subscript(self, expr, inames, insn_id): arg_name = expr.aggregate.name subscript = expr.index_tuple - descriptor = self.kernel.get_var_descriptor(arg_name) - from loopy.diagnostic import UnableToDetermineAccessRangeError from loopy.symbolic import get_access_map @@ -56,11 +53,11 @@ def map_subscript(self, expr, inames, insn_id): access_map = get_access_map(domain, subscript) except UnableToDetermineAccessRangeError: return - + if self.access_maps[insn_id][arg_name][inames] is None: self.access_maps[insn_id][arg_name][inames] = access_map -def compute_happens_after(knl: LoopKernel) -> LoopKernel: +def compute_happens_after(knl: LoopKernel) -> LoopKernel: """ TODO Update documentation to reflect the proper format. @@ -70,8 +67,14 @@ def compute_happens_after(knl: LoopKernel) -> LoopKernel: writer_map = knl.writer_map() variables = knl.all_variable_names - knl.inames.keys() + # initialize the mapper amap = AccessMapMapper(knl, variables) + for insn in knl.instructions: + amap(insn.assignee, insn.within_inames) + amap(insn.expression, insn.within_inames) + + # compute data dependencies dep_map = { insn.id: insn.read_dependency_names() - insn.within_inames for insn in knl.instructions @@ -87,7 +90,7 @@ def compute_happens_after(knl: LoopKernel) -> LoopKernel: for writer in (writer_map.get(var, set()) - { current_insn }): # get relation for current instruction and a write instruction - cur_relation = amap.access_maps[current_insn][var][inames] + cur_relation = amap.access_maps[current_insn][var][inames] write_relation = amap.access_maps[writer][var][inames] # compute the dependency relation @@ -99,8 +102,8 @@ def compute_happens_after(knl: LoopKernel) -> LoopKernel: # add to the new list of dependencies new_happens_after |= happens_after_mapping - - # update happens_after of our current instruction with the mapping + + # update happens_after of our current instruction with the mapping insn = insn.copy(happens_after=new_happens_after) new_insns.append(insn) @@ -108,5 +111,40 @@ def compute_happens_after(knl: LoopKernel) -> LoopKernel: return knl.copy(instructions=new_insns) def add_lexicographic_happens_after(knl: LoopKernel) -> None: - pass + """ + TODO properly format this documentation. + + Creates a dependency relation between two instructions based on a + lexicographic ordering of the statements in a program. + + For example, the C-like execution order (i.e. sequential ordering) of a + program. + """ + + # we want to modify the output dimension and OUT = 3 + dim_type = isl.dim_type(3) + + # generate an unordered mapping from statement instances to points in the + # loop domain + insn_number = 0 + schedules = {} + for insn in knl.instructions: + domain = knl.get_inames_domain(insn.within_inames) + + # if we do not set the dim name, the name is set as None + domain = domain.insert_dims(dim_type, 0, 1).set_dim_name(dim_type, 0, + insn.id) + + space = domain.get_space() + domain = domain.add_constraint( + isl.Constraint.eq_from_names(space, {1: -1*insn_number, insn.id: 1}) + ) + + # this may not be the final way we keep track of the schedules + schedule = isl.Map.from_domain_and_range(domain, domain) + schedules[insn.id] = schedule + + insn_number += 1 + + # determine a lexicographic order on the space the schedules belong to From 93b4126a87a5e5390e85973db3d788c85d94089b Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Fri, 20 Jan 2023 10:04:04 -0600 Subject: [PATCH 06/52] Update gitignore to ignore DS_STORE --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7cf3c4751..d7c5d9d03 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ htmlcov lextab.py yacctab.py .pytest_cache/* +.DS_STORE loopy/_git_rev.py From 01d3688121c0f7bbf0d8f2ceb4a3283ded8682b8 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Jan 2023 13:43:47 -0600 Subject: [PATCH 07/52] Sketch out add_lexicographic_happens_after --- loopy/kernel/dependency.py | 83 ++++++++++++++++++++++++++++++++++++-- test/test_dependencies.py | 32 +++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 test/test_dependencies.py diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 61af2fd35..bc87b2f5b 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -1,4 +1,8 @@ +# FIXME Add copyright header + + import islpy as isl +from islpy import dim_type import pymbolic.primitives as p from dataclasses import dataclass @@ -7,6 +11,8 @@ from loopy import LoopKernel from loopy.symbolic import WalkMapper +from loopy.translation_unit import for_each_kernel +from loopy.typing import ExpressionT @dataclass(frozen=True) class HappensAfter: @@ -32,7 +38,7 @@ def __init__(self, kernel: LoopKernel, var_names: set): super.__init__() - def map_subscript(self, expr: p.expression, inames: frozenset, insn_id: str): + def map_subscript(self, expr: ExpressionT, inames: frozenset, insn_id: str): domain = self.kernel.get_inames_domain(inames) @@ -110,7 +116,7 @@ def compute_happens_after(knl: LoopKernel) -> LoopKernel: # return the kernel with the new instructions return knl.copy(instructions=new_insns) -def add_lexicographic_happens_after(knl: LoopKernel) -> None: +def add_lexicographic_happens_after_orig(knl: LoopKernel) -> None: """ TODO properly format this documentation. @@ -122,7 +128,7 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> None: """ # we want to modify the output dimension and OUT = 3 - dim_type = isl.dim_type(3) + dim_type = isl.dim_type.out # generate an unordered mapping from statement instances to points in the # loop domain @@ -148,3 +154,74 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> None: # determine a lexicographic order on the space the schedules belong to + +@for_each_kernel +def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: + + new_insns = [] + + for iafter, insn_after in enumerate(knl.instructions): + if iafter == 0: + new_insns.append(insn_after) + else: + insn_before = knl.instructions[iafter - 1] + shared_inames = insn_after.within_inames & insn_before.within_inames + unshared_before = insn_before.within_inames + + domain_before = knl.get_inames_domain(insn_before.within_inames) + domain_after = knl.get_inames_domain(insn_after.within_inames) + + happens_before = isl.Map.from_domain_and_range( + domain_before, domain_after) + for idim in range(happens_before.dim(dim_type.out)): + happens_before = happens_before.set_dim_name( + dim_type.out, idim, + happens_before.get_dim_name(dim_type.out, idim) + "'") + n_inames_before = happens_before.dim(dim_type.in_) + happens_before_set = happens_before.move_dims( + dim_type.out, 0, + dim_type.in_, 0, + n_inames_before).range() + + shared_inames_order_before = [ + domain_before.get_dim_name(dim_type.out, idim) + for idim in range(domain_before.dim(dim_type.out)) + if domain_before.get_dim_name(dim_type.out, idim) + in shared_inames] + shared_inames_order_after = [ + domain_after.get_dim_name(dim_type.out, idim) + for idim in range(domain_after.dim(dim_type.out)) + if domain_after.get_dim_name(dim_type.out, idim) + in shared_inames] + + assert shared_inames_order_after == shared_inames_order_before + shared_inames_order = shared_inames_order_after + + affs = isl.affs_from_space(happens_before_set.space) + + lex_set = isl.Set.empty(happens_before_set.space) + for iinnermost, innermost_iname in enumerate(shared_inames_order): + innermost_set = affs[innermost_iname].lt_set( + affs[innermost_iname+"'"]) + + for outer_iname in shared_inames_order[:iinnermost]: + innermost_set = innermost_set & ( + affs[outer_iname].eq_set(affs[outer_iname + "'"])) + + lex_set = lex_set | innermost_set + + lex_map = isl.Map.from_range(lex_set).move_dims( + dim_type.in_, 0, + dim_type.out, 0, + n_inames_before) + + happens_before = happens_before & lex_map + + pu.db + + new_insns.append(insn_after) + + return knl.copy(instructions=new_insns) + + + diff --git a/test/test_dependencies.py b/test/test_dependencies.py new file mode 100644 index 000000000..9bb28c598 --- /dev/null +++ b/test/test_dependencies.py @@ -0,0 +1,32 @@ +# FIXME Add copyright header + + +import sys +import loopy as lp + + +def test_lex_dependencies(): + knl = lp.make_kernel( + [ + "{[a,b]:0<=a,b<7}", + "{[i,j]: 0<=i,j 1: + exec(sys.argv[1]) + else: + from pytest import main + main([__file__]) + +# vim: foldmethod=marker From 51c460d70faae119f20888d1de84a784d5b7d4fd Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Sat, 21 Jan 2023 16:06:37 -0600 Subject: [PATCH 08/52] Add relation to HappensAfter data structure --- .DS_Store | Bin 8196 -> 0 bytes loopy/kernel/dependency.py | 122 +++++++++++++++++++++---------------- 2 files changed, 69 insertions(+), 53 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a55d04f2cbdc3358d7dc4b9a5275c6bf19cd3730..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMzi$&U6n;*Ea-vd{1=In-ZAhpAGQhwTs+55)pbShEkfco_l3rBO3ML}f3aJt! zEFg7ZVni^&z+5rF00aCDT`Te9^N-v;=kAg$rAXM7{Z4+*{yu;9+2@pq#NuYNOf*kK z9ITQ&=`elx#jH$T@E6|?GwMNkE$KqUtQ@|)Tos8$KjMfm{h8(T4Us4YB5LN|rgO@+B)2;Cg@ zzKN?CJJeP;Ct)rh!XOKCLlFuc{rf7ML`7RyIt82paRubvJxk}QL7i@k-`}%)k*60_ ztMzVBM~&D0eNAkyeZ0!O%?aLqoA;{T&K&Ss)S-ce_~pbI^cng%=9|*frOXqB$@~#& zlHbDp@c~^^euPVH+N8RLPN!bPBRGeHop9V^{q%x}NqUsUhotOuoa(en>(IVUJ-SO9 zbdT=atO{Fcah%)!g;K`MNn)Rz;j~zrr<$)Ucy?&rW=1PuW;~qBa`)VbOX!XCiF*_9 z@=zpzE(0ji8K0+x_rM*C;P`KE?>!^ER$@^RkMt;;PX%BYWfOcF*7!4p-N?i7U)@|O z3XN~tarNU~3or9p;(1sV53nA3)Tb_<$JWHpT2c(>@c3)ER%3l}Hk0BkUgDGFbqs6* z{PpRfrA7Ij+!>gcb2+&i>S0AhbCDcj;a}d7_=N}`Z}KYkkQSBX%A_?4EJk$t*0gxe zasTt#myfp9{xT7RYd32fYaDb#j$;lAZa~KYatS4780FY3e_u|I(*z$tK~3W!{_R$T-IbGMF>k#}t$`3|y7GH$4?YJx(B$a-}hhYkN>h-csUD#i}A ng$L=c{}5ol7x;NK>siiE(plSB=bAhJ<(H!8&VRM^kF@v$w%X}$ diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index bc87b2f5b..ca6ab3fa3 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -1,12 +1,31 @@ -# FIXME Add copyright header - +__copyright__ = "Copyright (C) 2023 Addison Alvey-Blanco" + +__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 islpy as isl from islpy import dim_type + import pymbolic.primitives as p from dataclasses import dataclass -from islpy import Map from typing import Optional from loopy import LoopKernel @@ -17,7 +36,7 @@ @dataclass(frozen=True) class HappensAfter: variable_name: Optional[str] - instances_rel: Optional[Map] + instances_rel: Optional[isl.Map] class AccessMapMapper(WalkMapper): """ @@ -116,112 +135,109 @@ def compute_happens_after(knl: LoopKernel) -> LoopKernel: # return the kernel with the new instructions return knl.copy(instructions=new_insns) -def add_lexicographic_happens_after_orig(knl: LoopKernel) -> None: +@for_each_kernel +def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: """ - TODO properly format this documentation. + TODO update documentation to follow the proper format - Creates a dependency relation between two instructions based on a - lexicographic ordering of the statements in a program. + Determine a coarse "happens-before" relationship between an instruction and + the instruction immediately preceeding it. This strict execution order is + relaxed in accordance with the data dependency relations. - For example, the C-like execution order (i.e. sequential ordering) of a - program. + See `loopy.dependency.compute_happens_after` for data dependency generation. """ - # we want to modify the output dimension and OUT = 3 - dim_type = isl.dim_type.out - - # generate an unordered mapping from statement instances to points in the - # loop domain - insn_number = 0 - schedules = {} - for insn in knl.instructions: - domain = knl.get_inames_domain(insn.within_inames) - - # if we do not set the dim name, the name is set as None - domain = domain.insert_dims(dim_type, 0, 1).set_dim_name(dim_type, 0, - insn.id) - - space = domain.get_space() - domain = domain.add_constraint( - isl.Constraint.eq_from_names(space, {1: -1*insn_number, insn.id: 1}) - ) - - # this may not be the final way we keep track of the schedules - schedule = isl.Map.from_domain_and_range(domain, domain) - schedules[insn.id] = schedule - - insn_number += 1 - - # determine a lexicographic order on the space the schedules belong to - - -@for_each_kernel -def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: - new_insns = [] for iafter, insn_after in enumerate(knl.instructions): + + # the first instruction does not have anything preceding it if iafter == 0: new_insns.append(insn_after) + + # all other instructions "happen after" the instruction before it else: + + # not currently used + # unshared_before = insn_before.within_inames + + # get information about the preceding instruction insn_before = knl.instructions[iafter - 1] shared_inames = insn_after.within_inames & insn_before.within_inames - unshared_before = insn_before.within_inames + # generate a map from the preceding insn to the current insn domain_before = knl.get_inames_domain(insn_before.within_inames) domain_after = knl.get_inames_domain(insn_after.within_inames) - happens_before = isl.Map.from_domain_and_range( - domain_before, domain_after) + domain_before, domain_after + ) + + # update inames so they are unique for idim in range(happens_before.dim(dim_type.out)): happens_before = happens_before.set_dim_name( dim_type.out, idim, happens_before.get_dim_name(dim_type.out, idim) + "'") + + # generate a set containing all inames (from both domains) n_inames_before = happens_before.dim(dim_type.in_) happens_before_set = happens_before.move_dims( dim_type.out, 0, dim_type.in_, 0, n_inames_before).range() + # verify the order of the inames shared_inames_order_before = [ domain_before.get_dim_name(dim_type.out, idim) for idim in range(domain_before.dim(dim_type.out)) if domain_before.get_dim_name(dim_type.out, idim) - in shared_inames] + in shared_inames + ] shared_inames_order_after = [ domain_after.get_dim_name(dim_type.out, idim) for idim in range(domain_after.dim(dim_type.out)) if domain_after.get_dim_name(dim_type.out, idim) - in shared_inames] - + in shared_inames + ] assert shared_inames_order_after == shared_inames_order_before shared_inames_order = shared_inames_order_after + # generate lexicographical map from space of preceding to current insn affs = isl.affs_from_space(happens_before_set.space) + # start with an empty set lex_set = isl.Set.empty(happens_before_set.space) for iinnermost, innermost_iname in enumerate(shared_inames_order): + innermost_set = affs[innermost_iname].lt_set( - affs[innermost_iname+"'"]) - + affs[innermost_iname+"'"] + ) + for outer_iname in shared_inames_order[:iinnermost]: innermost_set = innermost_set & ( - affs[outer_iname].eq_set(affs[outer_iname + "'"])) + affs[outer_iname].eq_set(affs[outer_iname + "'"]) + ) + # update the set lex_set = lex_set | innermost_set + # create the map lex_map = isl.Map.from_range(lex_set).move_dims( dim_type.in_, 0, dim_type.out, 0, n_inames_before) + # update happens_before map happens_before = happens_before & lex_map - pu.db + # create HappensAfter and add the relation to it + new_happens_after = { + insn_before.id: HappensAfter(None, happens_before) + } + insn_after = insn_after.copy(happens_after=new_happens_after) + + # update instructions new_insns.append(insn_after) return knl.copy(instructions=new_insns) - - From f6b75ddb4cf5822fe63e89d05111e241ae928a65 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Sat, 28 Jan 2023 22:21:31 -0600 Subject: [PATCH 09/52] New version of data dependency finding, still WIP --- loopy/kernel/dependency.py | 154 +++++++++++++++++++++++++------------ test/test_dependencies.py | 23 +++++- 2 files changed, 126 insertions(+), 51 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index ca6ab3fa3..86205a742 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,24 +20,25 @@ THE SOFTWARE. """ +from dataclasses import dataclass +from typing import Optional + import islpy as isl from islpy import dim_type import pymbolic.primitives as p -from dataclasses import dataclass -from typing import Optional - from loopy import LoopKernel from loopy.symbolic import WalkMapper from loopy.translation_unit import for_each_kernel -from loopy.typing import ExpressionT + @dataclass(frozen=True) class HappensAfter: variable_name: Optional[str] instances_rel: Optional[isl.Map] + class AccessMapMapper(WalkMapper): """ TODO Update this documentation so it reflects proper formatting @@ -55,9 +56,9 @@ def __init__(self, kernel: LoopKernel, var_names: set): defaultdict(lambda: defaultdict(lambda: None))) - super.__init__() + super().__init__() - def map_subscript(self, expr: ExpressionT, inames: frozenset, insn_id: str): + def map_subscript(self, expr: p.Subscript, inames: frozenset, insn_id: str): domain = self.kernel.get_inames_domain(inames) @@ -82,59 +83,113 @@ def map_subscript(self, expr: ExpressionT, inames: frozenset, insn_id: str): if self.access_maps[insn_id][arg_name][inames] is None: self.access_maps[insn_id][arg_name][inames] = access_map -def compute_happens_after(knl: LoopKernel) -> LoopKernel: - """ - TODO Update documentation to reflect the proper format. - Determine dependency relations that exist between instructions. Similar to - apply_single_writer_dependency_heuristic. Extremely rough draft. +@for_each_kernel +def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: """ - writer_map = knl.writer_map() - variables = knl.all_variable_names - knl.inames.keys() + TODO Update documentation to reflect the proper format - # initialize the mapper - amap = AccessMapMapper(knl, variables) + Relax the lexicographic dependencies generated in + `add_lexicographic_happens_after` according to data dependencies in the + program. + """ - for insn in knl.instructions: - amap(insn.assignee, insn.within_inames) - amap(insn.expression, insn.within_inames) + writer_map = knl.writer_map() - # compute data dependencies - dep_map = { + reads = { insn.id: insn.read_dependency_names() - insn.within_inames for insn in knl.instructions } + writes = { + insn.id: insn.write_dependency_names() - insn.within_inames + for insn in knl.instructions + } - new_insns = [] + variables = knl.all_variable_names() + + amap = AccessMapMapper(knl, variables) for insn in knl.instructions: - current_insn = insn.id - inames = insn.within_inames + amap(insn.assignee, insn.within_inames, insn.id) + amap(insn.expression, insn.within_inames, insn.id) - new_happens_after = [] - for var in dep_map[insn.id]: - for writer in (writer_map.get(var, set()) - { current_insn }): + def get_relation(insn_id: Optional[str], variable: str, + inames: frozenset) -> isl.Map: + return amap.access_maps[insn_id][variable][inames] - # get relation for current instruction and a write instruction - cur_relation = amap.access_maps[current_insn][var][inames] - write_relation = amap.access_maps[writer][var][inames] + def get_unordered_deps(R: isl.Map, S: isl.Map) -> isl.Map: + return S.apply_range(R.reverse()) - # compute the dependency relation - dep_relation = cur_relation.apply_range(write_relation.reverse()) + new_insns = [] + for cur_insn in knl.instructions: - # create the mapping from writer -> (variable, dependency rel'n) - happens_after = HappensAfter(var, dep_relation) - happens_after_mapping = { writer: happens_after } + new_insn = cur_insn.copy() - # add to the new list of dependencies - new_happens_after |= happens_after_mapping + # handle read-write case + for var in reads[cur_insn.id]: + for writer in writer_map.get(var, set()) - {cur_insn.id}: - # update happens_after of our current instruction with the mapping - insn = insn.copy(happens_after=new_happens_after) - new_insns.append(insn) + write_insn = writer + for insn in knl.instructions: + if writer == insn.id: + write_insn = insn.copy() + + # writer precedes current read instruction + if writer in cur_insn.happens_after: + read_rel = get_relation(cur_insn.id, var, + cur_insn.within_inames) + write_rel = get_relation(write_insn.id, var, + write_insn.within_inames) + + read_write = get_unordered_deps(read_rel, write_rel) + write_read = get_unordered_deps(write_rel, read_rel) + unordered_deps_full = read_write | write_read + + lex_map = cur_insn.happens_after[writer].instances_rel + + deps = unordered_deps_full & lex_map + + new_insn.happens_after.update( + {writer: HappensAfter(var, deps)} + ) + + # TODO implement writer does not precede read insn + else: + pass + + # handle write-write case + for var in writes[cur_insn.id]: + for writer in writer_map.get(var, set()) - {cur_insn.id}: + + write_insn = writer + for insn in knl.instructions: + if writer == insn.id: + write_insn = insn.copy() + + if writer in cur_insn.happens_after: + before_write_rel = get_relation(cur_insn.id, var, + cur_insn.within_inames) + after_write_rel = get_relation(write_insn.id, var, + write_insn.within_inames) + + unordered_deps = get_unordered_deps(before_write_rel, + after_write_rel) + + lex_map = cur_insn.happens_after[writer].instances_rel + deps = unordered_deps & lex_map + + new_insn.happens_after.update( + {writer: HappensAfter(var, deps)} + ) + + # TODO implement writer does not precede current writer + else: + pass + + new_insns.append(new_insn) - # return the kernel with the new instructions return knl.copy(instructions=new_insns) + @for_each_kernel def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: """ @@ -154,13 +209,13 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: # the first instruction does not have anything preceding it if iafter == 0: new_insns.append(insn_after) - + # all other instructions "happen after" the instruction before it else: # not currently used # unshared_before = insn_before.within_inames - + # get information about the preceding instruction insn_before = knl.instructions[iafter - 1] shared_inames = insn_after.within_inames & insn_before.within_inames @@ -172,12 +227,12 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: domain_before, domain_after ) - # update inames so they are unique + # update inames so they are unique for idim in range(happens_before.dim(dim_type.out)): happens_before = happens_before.set_dim_name( dim_type.out, idim, happens_before.get_dim_name(dim_type.out, idim) + "'") - + # generate a set containing all inames (from both domains) n_inames_before = happens_before.dim(dim_type.in_) happens_before_set = happens_before.move_dims( @@ -207,12 +262,12 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: # start with an empty set lex_set = isl.Set.empty(happens_before_set.space) for iinnermost, innermost_iname in enumerate(shared_inames_order): - + innermost_set = affs[innermost_iname].lt_set( affs[innermost_iname+"'"] ) - - for outer_iname in shared_inames_order[:iinnermost]: + + for outer_iname in shared_inames_order[:iinnermost]: innermost_set = innermost_set & ( affs[outer_iname].eq_set(affs[outer_iname + "'"]) ) @@ -230,8 +285,8 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: happens_before = happens_before & lex_map # create HappensAfter and add the relation to it - new_happens_after = { - insn_before.id: HappensAfter(None, happens_before) + new_happens_after = { + insn_before.id: HappensAfter(None, happens_before) } insn_after = insn_after.copy(happens_after=new_happens_after) @@ -241,3 +296,4 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: return knl.copy(instructions=new_insns) +# vim: foldmethod=marker diff --git a/test/test_dependencies.py b/test/test_dependencies.py index 9bb28c598..7b2229dff 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -1,5 +1,24 @@ -# FIXME Add copyright header - +__copyright__ = "Copyright (C) 2023 Addison Alvey-Blanco" + +__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 From 8327c70ce6a7c7d48a6ce5ec81803088bcfb0a63 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Sat, 4 Feb 2023 16:27:00 -0600 Subject: [PATCH 10/52] added read-after-write, write-after-read, write-after-write dep finding for successive instructions --- loopy/kernel/dependency.py | 104 +++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 86205a742..8798e7ffe 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -29,6 +29,7 @@ import pymbolic.primitives as p from loopy import LoopKernel +from loopy import InstructionBase from loopy.symbolic import WalkMapper from loopy.translation_unit import for_each_kernel @@ -95,6 +96,7 @@ def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: """ writer_map = knl.writer_map() + reader_map = knl.reader_map() reads = { insn.id: insn.read_dependency_names() - insn.within_inames @@ -106,17 +108,16 @@ def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: } variables = knl.all_variable_names() - amap = AccessMapMapper(knl, variables) for insn in knl.instructions: amap(insn.assignee, insn.within_inames, insn.id) amap(insn.expression, insn.within_inames, insn.id) - def get_relation(insn_id: Optional[str], variable: str, - inames: frozenset) -> isl.Map: - return amap.access_maps[insn_id][variable][inames] + def get_relation(insn: InstructionBase, variable: Optional[str]) -> isl.Map: + return amap.access_maps[insn.id][variable][insn.within_inames] def get_unordered_deps(R: isl.Map, S: isl.Map) -> isl.Map: + # equivalent to the composition R^{-1} o S return S.apply_range(R.reverse()) new_insns = [] @@ -124,64 +125,91 @@ def get_unordered_deps(R: isl.Map, S: isl.Map) -> isl.Map: new_insn = cur_insn.copy() - # handle read-write case + # handle read-after-write case for var in reads[cur_insn.id]: for writer in writer_map.get(var, set()) - {cur_insn.id}: + # grab writer from knl.instructions write_insn = writer for insn in knl.instructions: if writer == insn.id: write_insn = insn.copy() + break - # writer precedes current read instruction + # writer is immediately before reader if writer in cur_insn.happens_after: - read_rel = get_relation(cur_insn.id, var, - cur_insn.within_inames) - write_rel = get_relation(write_insn.id, var, - write_insn.within_inames) + read_rel = get_relation(cur_insn, var) + write_rel = get_relation(write_insn, var) read_write = get_unordered_deps(read_rel, write_rel) - write_read = get_unordered_deps(write_rel, read_rel) - unordered_deps_full = read_write | write_read lex_map = cur_insn.happens_after[writer].instances_rel - deps = unordered_deps_full & lex_map + deps = lex_map & read_write new_insn.happens_after.update( {writer: HappensAfter(var, deps)} ) - # TODO implement writer does not precede read insn + # TODO else: pass - # handle write-write case + # handle write-after-read and write-after-write for var in writes[cur_insn.id]: + + # TODO write-after-read + for reader in reader_map.get(var, set()) - {cur_insn.id}: + + read_insn = reader + for insn in knl.instructions: + if reader == insn.id: + read_insn = insn.copy() + break + + if reader in cur_insn.happens_after: + write_rel = get_relation(cur_insn, var) + read_rel = get_relation(read_insn, var) + + write_read = get_unordered_deps(write_rel, read_rel) + + lex_map = cur_insn.happens_after[reader].instances_rel + + deps = lex_map & write_read + + new_insn.happens_after.update( + {reader: HappensAfter(var, deps)} + ) + + # TODO + else: + pass + + # write-after-write for writer in writer_map.get(var, set()) - {cur_insn.id}: - write_insn = writer + other_writer = writer for insn in knl.instructions: if writer == insn.id: - write_insn = insn.copy() + other_writer = insn.copy() + break + # other writer is immediately before current writer if writer in cur_insn.happens_after: - before_write_rel = get_relation(cur_insn.id, var, - cur_insn.within_inames) - after_write_rel = get_relation(write_insn.id, var, - write_insn.within_inames) + before_write_rel = get_relation(other_writer, var) + after_write_rel = get_relation(cur_insn, var) - unordered_deps = get_unordered_deps(before_write_rel, - after_write_rel) + write_write = get_unordered_deps(after_write_rel, + before_write_rel) lex_map = cur_insn.happens_after[writer].instances_rel - deps = unordered_deps & lex_map + + deps = lex_map & write_write new_insn.happens_after.update( {writer: HappensAfter(var, deps)} ) - # TODO implement writer does not precede current writer else: pass @@ -206,60 +234,49 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: for iafter, insn_after in enumerate(knl.instructions): - # the first instruction does not have anything preceding it if iafter == 0: new_insns.append(insn_after) - # all other instructions "happen after" the instruction before it else: - # not currently used - # unshared_before = insn_before.within_inames - - # get information about the preceding instruction insn_before = knl.instructions[iafter - 1] shared_inames = insn_after.within_inames & insn_before.within_inames - # generate a map from the preceding insn to the current insn domain_before = knl.get_inames_domain(insn_before.within_inames) domain_after = knl.get_inames_domain(insn_after.within_inames) happens_before = isl.Map.from_domain_and_range( domain_before, domain_after ) - # update inames so they are unique for idim in range(happens_before.dim(dim_type.out)): happens_before = happens_before.set_dim_name( dim_type.out, idim, - happens_before.get_dim_name(dim_type.out, idim) + "'") + happens_before.get_dim_name(dim_type.out, idim) + "'" + ) - # generate a set containing all inames (from both domains) n_inames_before = happens_before.dim(dim_type.in_) happens_before_set = happens_before.move_dims( dim_type.out, 0, dim_type.in_, 0, n_inames_before).range() - # verify the order of the inames shared_inames_order_before = [ domain_before.get_dim_name(dim_type.out, idim) for idim in range(domain_before.dim(dim_type.out)) if domain_before.get_dim_name(dim_type.out, idim) in shared_inames - ] + ] shared_inames_order_after = [ domain_after.get_dim_name(dim_type.out, idim) for idim in range(domain_after.dim(dim_type.out)) if domain_after.get_dim_name(dim_type.out, idim) in shared_inames - ] + ] assert shared_inames_order_after == shared_inames_order_before shared_inames_order = shared_inames_order_after - # generate lexicographical map from space of preceding to current insn affs = isl.affs_from_space(happens_before_set.space) - # start with an empty set lex_set = isl.Set.empty(happens_before_set.space) for iinnermost, innermost_iname in enumerate(shared_inames_order): @@ -270,28 +287,23 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: for outer_iname in shared_inames_order[:iinnermost]: innermost_set = innermost_set & ( affs[outer_iname].eq_set(affs[outer_iname + "'"]) - ) + ) - # update the set lex_set = lex_set | innermost_set - # create the map lex_map = isl.Map.from_range(lex_set).move_dims( dim_type.in_, 0, dim_type.out, 0, n_inames_before) - # update happens_before map happens_before = happens_before & lex_map - # create HappensAfter and add the relation to it new_happens_after = { insn_before.id: HappensAfter(None, happens_before) } insn_after = insn_after.copy(happens_after=new_happens_after) - # update instructions new_insns.append(insn_after) return knl.copy(instructions=new_insns) From 999446fda093d14e6871560e320a5c61bf11b283 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Sun, 5 Feb 2023 21:08:02 -0600 Subject: [PATCH 11/52] update documentation --- loopy/kernel/dependency.py | 47 ++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 8798e7ffe..dd217c59e 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,7 +20,6 @@ THE SOFTWARE. """ -from dataclasses import dataclass from typing import Optional import islpy as isl @@ -32,20 +31,21 @@ from loopy import InstructionBase from loopy.symbolic import WalkMapper from loopy.translation_unit import for_each_kernel +from loopy.kernel.instruction import HappensAfter -@dataclass(frozen=True) -class HappensAfter: - variable_name: Optional[str] - instances_rel: Optional[isl.Map] +class AccessMapMapper(WalkMapper): + """A subclass of :class:`loopy.symbolic.WalkMapper` used to generate + individual array access maps of instructions. Similar to + :class:`loopy.symbolic.BatchedAccessMapMapper`, except that it generates + single access maps instead of combined access maps. + .. attribute:: access_maps -class AccessMapMapper(WalkMapper): - """ - TODO Update this documentation so it reflects proper formatting + A dict containing the access map of a particular array as accessed by an + instruction. These maps can be found via - Used instead of BatchedAccessMapMapper to get single access maps for each - instruction. + access_maps[insn_id][variable_name][inames] """ def __init__(self, kernel: LoopKernel, var_names: set): @@ -87,12 +87,14 @@ def map_subscript(self, expr: p.Subscript, inames: frozenset, insn_id: str): @for_each_kernel def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: - """ - TODO Update documentation to reflect the proper format + """Determine precise data dependencies between dynamic statement instances + using the access relations of statements. Relies on there being an existing + lexicographic ordering of statements. - Relax the lexicographic dependencies generated in - `add_lexicographic_happens_after` according to data dependencies in the - program. + :arg knl: + + A :class:`loopy.LoopKernel` containing instructions to find the + statement instance level dependencies of. """ writer_map = knl.writer_map() @@ -116,9 +118,9 @@ def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: def get_relation(insn: InstructionBase, variable: Optional[str]) -> isl.Map: return amap.access_maps[insn.id][variable][insn.within_inames] - def get_unordered_deps(R: isl.Map, S: isl.Map) -> isl.Map: + def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: # equivalent to the composition R^{-1} o S - return S.apply_range(R.reverse()) + return s.apply_range(r.reverse()) new_insns = [] for cur_insn in knl.instructions: @@ -220,14 +222,9 @@ def get_unordered_deps(R: isl.Map, S: isl.Map) -> isl.Map: @for_each_kernel def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: - """ - TODO update documentation to follow the proper format - - Determine a coarse "happens-before" relationship between an instruction and - the instruction immediately preceeding it. This strict execution order is - relaxed in accordance with the data dependency relations. - - See `loopy.dependency.compute_happens_after` for data dependency generation. + """Compute an initial lexicographic happens-after ordering of the statments + in a :class:`loopy.LoopKernel`. Statements are ordered in a sequential + (C-like) manner. """ new_insns = [] From f110b3083911968490548aa73a2f14e7e9b2a499 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Sat, 11 Feb 2023 23:02:00 -0600 Subject: [PATCH 12/52] Added first implementation of dependency finding for non-successive instructions --- loopy/kernel/dependency.py | 107 ++++++++++++++++++++++++++++--------- test/test_dependencies.py | 21 +++++++- 2 files changed, 103 insertions(+), 25 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index dd217c59e..f047d4ccc 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -138,29 +138,47 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: write_insn = insn.copy() break + read_rel = get_relation(cur_insn, var) + write_rel = get_relation(write_insn, var) + read_write = get_unordered_deps(read_rel, write_rel) + # writer is immediately before reader if writer in cur_insn.happens_after: - read_rel = get_relation(cur_insn, var) - write_rel = get_relation(write_insn, var) - - read_write = get_unordered_deps(read_rel, write_rel) - lex_map = cur_insn.happens_after[writer].instances_rel deps = lex_map & read_write - new_insn.happens_after.update( {writer: HappensAfter(var, deps)} ) - # TODO + # writer is not immediately before reader else: - pass + # generate a lexicographical map between the instructions + lex_map = read_write.lex_lt_map(read_write) + + if lex_map.space != read_write.space: + + # names may not be unique, make out names unique + for i in range(lex_map.dim(dim_type.out)): + iname = lex_map.get_dim_name(dim_type.out, i) + "'" + lex_map = lex_map.set_dim_name(dim_type.out, i, + iname) + for i in range(read_write.dim(dim_type.out)): + iname = read_write.get_dim_name(dim_type.out, i)+"'" + read_write = read_write.set_dim_name(dim_type.out, + i, iname) + + lex_map, read_write = isl.align_two(lex_map, read_write) + + deps = lex_map & read_write + new_insn.happens_after.update( + {writer: HappensAfter(var, deps)} + ) - # handle write-after-read and write-after-write + # handle write-after-read and write-after-write for var in writes[cur_insn.id]: - # TODO write-after-read + # write-after-read for reader in reader_map.get(var, set()) - {cur_insn.id}: read_insn = reader @@ -169,23 +187,43 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: read_insn = insn.copy() break - if reader in cur_insn.happens_after: - write_rel = get_relation(cur_insn, var) - read_rel = get_relation(read_insn, var) + write_rel = get_relation(cur_insn, var) + read_rel = get_relation(read_insn, var) - write_read = get_unordered_deps(write_rel, read_rel) + # calculate dependency map + write_read = get_unordered_deps(write_rel, read_rel) + # reader is immediately before writer + if reader in cur_insn.happens_after: lex_map = cur_insn.happens_after[reader].instances_rel - deps = lex_map & write_read new_insn.happens_after.update( {reader: HappensAfter(var, deps)} ) - # TODO + # reader is not immediately before writer else: - pass + lex_map = write_read.lex_lt_map(write_read) + + if lex_map.space != write_read.space: + + # inames may not be unique, make out inames unique + for i in range(lex_map.dim(dim_type.out)): + iname = lex_map.get_dim_name(dim_type.out, i) + "'" + lex_map = lex_map.set_dim_name(dim_type.out, i, + iname) + for i in range(write_read.dim(dim_type.out)): + iname = write_read.get_dim_name(dim_type.out, i)+"'" + write_read = write_read.set_dim_name(dim_type.out, + i, iname) + + lex_map, write_read = isl.align_two(lex_map, write_read) + + deps = lex_map & write_read + new_insn.happens_after.update( + {reader: HappensAfter(var, deps)} + ) # write-after-write for writer in writer_map.get(var, set()) - {cur_insn.id}: @@ -196,14 +234,14 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: other_writer = insn.copy() break - # other writer is immediately before current writer - if writer in cur_insn.happens_after: - before_write_rel = get_relation(other_writer, var) - after_write_rel = get_relation(cur_insn, var) + before_write_rel = get_relation(other_writer, var) + after_write_rel = get_relation(cur_insn, var) - write_write = get_unordered_deps(after_write_rel, - before_write_rel) + write_write = get_unordered_deps(after_write_rel, + before_write_rel) + # other writer is immediately before current writer + if writer in cur_insn.happens_after: lex_map = cur_insn.happens_after[writer].instances_rel deps = lex_map & write_write @@ -212,8 +250,29 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: {writer: HappensAfter(var, deps)} ) + # there is not a writer immediately before current writer else: - pass + lex_map = write_write.lex_lt_map(write_write) + + if lex_map.space != write_write.space: + + # make inames unique + for i in range(lex_map.dim(dim_type.out)): + iname = lex_map.get_dim_name(dim_type.out, i) + "'" + lex_map = lex_map.set_dim_name(dim_type.out, i, + iname) + for i in range(write_write.dim(dim_type.out)): + iname = write_write.get_dim_name(dim_type.out, i)+"'" + write_write = write_write.set_dim_name(dim_type.out, + i, iname) + + lex_map, write_write = isl.align_two(lex_map, + write_write) + + deps = lex_map & write_write + new_insn.happens_after.update( + {writer: HappensAfter(var, deps)} + ) new_insns.append(new_insn) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index 7b2229dff..d7eb12d2d 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -27,7 +27,7 @@ def test_lex_dependencies(): knl = lp.make_kernel( [ - "{[a,b]:0<=a,b<7}", + "{[a,b]: 0<=a,b<7}", "{[i,j]: 0<=i,j 1: exec(sys.argv[1]) From a6717628043eb1e596b6c0dbc0229a50c38af9d9 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Mon, 13 Feb 2023 19:21:33 -0600 Subject: [PATCH 13/52] Minor fixes for some failing tests --- loopy/kernel/creation.py | 1 + loopy/kernel/instruction.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index 7a0e6918b..af6210774 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -2465,6 +2465,7 @@ def make_function(domains, instructions, kernel_data=None, **kwargs): check_for_duplicate_insn_ids(knl) if seq_dependencies: + from loopy.kernel.dependency import add_lexicographic_happens_after knl = add_lexicographic_happens_after(knl) assert len(knl.instructions) == len(inames_to_dup) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 77a33e645..19c89d58d 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -24,7 +24,6 @@ from typing import FrozenSet, Optional, Mapping, Tuple, Type, Union from sys import intern from functools import cached_property -from typing import FrozenSet from collections.abc import Mapping as MappingABC @@ -275,7 +274,7 @@ def __init__(self, predicates: Optional[FrozenSet[str]], tags: Optional[FrozenSet[Tag]], *, - depends_on: Union[FrozenSet[str], str, None]=None, + depends_on: Union[FrozenSet[str], str, None] = None, ) -> None: if predicates is None: From 34f5b9046f76ba1bdfd3619ec21e86bda8d84980 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Wed, 15 Feb 2023 19:09:38 -0600 Subject: [PATCH 14/52] Slight refactor to improve readability and remove repeated code --- loopy/kernel/dependency.py | 47 ++++++++++++-------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index f047d4ccc..77ccad2ca 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -122,6 +122,14 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: # equivalent to the composition R^{-1} o S return s.apply_range(r.reverse()) + def make_out_inames_unique(relation: isl.Map) -> isl.Map: + ndim = relation.dim(dim_type.out) + for i in range(ndim): + iname = relation.get_dim_name(dim_type.out, i) + "'" + relation = relation.set_dim_name(dim_type.out, i, iname) + + return relation + new_insns = [] for cur_insn in knl.instructions: @@ -157,17 +165,9 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: lex_map = read_write.lex_lt_map(read_write) if lex_map.space != read_write.space: - # names may not be unique, make out names unique - for i in range(lex_map.dim(dim_type.out)): - iname = lex_map.get_dim_name(dim_type.out, i) + "'" - lex_map = lex_map.set_dim_name(dim_type.out, i, - iname) - for i in range(read_write.dim(dim_type.out)): - iname = read_write.get_dim_name(dim_type.out, i)+"'" - read_write = read_write.set_dim_name(dim_type.out, - i, iname) - + lex_map = make_out_inames_unique(lex_map) + read_write = make_out_inames_unique(read_write) lex_map, read_write = isl.align_two(lex_map, read_write) deps = lex_map & read_write @@ -197,7 +197,6 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: if reader in cur_insn.happens_after: lex_map = cur_insn.happens_after[reader].instances_rel deps = lex_map & write_read - new_insn.happens_after.update( {reader: HappensAfter(var, deps)} ) @@ -207,17 +206,9 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: lex_map = write_read.lex_lt_map(write_read) if lex_map.space != write_read.space: - # inames may not be unique, make out inames unique - for i in range(lex_map.dim(dim_type.out)): - iname = lex_map.get_dim_name(dim_type.out, i) + "'" - lex_map = lex_map.set_dim_name(dim_type.out, i, - iname) - for i in range(write_read.dim(dim_type.out)): - iname = write_read.get_dim_name(dim_type.out, i)+"'" - write_read = write_read.set_dim_name(dim_type.out, - i, iname) - + lex_map = make_out_inames_unique(lex_map) + write_read = make_out_inames_unique(write_read) lex_map, write_read = isl.align_two(lex_map, write_read) deps = lex_map & write_read @@ -243,9 +234,7 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: # other writer is immediately before current writer if writer in cur_insn.happens_after: lex_map = cur_insn.happens_after[writer].instances_rel - deps = lex_map & write_write - new_insn.happens_after.update( {writer: HappensAfter(var, deps)} ) @@ -255,17 +244,9 @@ def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: lex_map = write_write.lex_lt_map(write_write) if lex_map.space != write_write.space: - # make inames unique - for i in range(lex_map.dim(dim_type.out)): - iname = lex_map.get_dim_name(dim_type.out, i) + "'" - lex_map = lex_map.set_dim_name(dim_type.out, i, - iname) - for i in range(write_write.dim(dim_type.out)): - iname = write_write.get_dim_name(dim_type.out, i)+"'" - write_write = write_write.set_dim_name(dim_type.out, - i, iname) - + lex_map = make_out_inames_unique(lex_map) + write_write = make_out_inames_unique(write_write) lex_map, write_write = isl.align_two(lex_map, write_write) From a7eb3648a3ef7c7ad71e0b43cc7d736bcf55b72d Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Wed, 15 Feb 2023 19:38:44 -0600 Subject: [PATCH 15/52] More changes to fix failing CI --- loopy/kernel/instruction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 6e998c40f..13f8d6cab 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -1066,7 +1066,7 @@ def __init__(self, super().__init__( id=id, - depends_on=depends_on, + happens_after=depends_on, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, @@ -1248,7 +1248,7 @@ def modify_assignee_for_array_call(assignee): def make_assignment(assignees, expression, temp_var_types=None, **kwargs): if temp_var_types is None: - temp_var_types = (Optional(),) * len(assignees) + temp_var_types = (LoopyOptional(),) * len(assignees) if len(assignees) != 1 or is_array_call(assignees, expression): atomicity = kwargs.pop("atomicity", ()) @@ -1366,7 +1366,7 @@ def __init__(self, InstructionBase.__init__(self, id=id, - depends_on=depends_on, + happens_after=depends_on, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, no_sync_with=no_sync_with, @@ -1524,7 +1524,7 @@ def __init__(self, id=None, depends_on=None, depends_on_is_final=None, predicates=None, tags=None): super().__init__( id=id, - depends_on=depends_on, + happens_after=depends_on, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, @@ -1588,7 +1588,7 @@ def __init__(self, id, depends_on=None, depends_on_is_final=None, super().__init__( id=id, - depends_on=depends_on, + happens_after=depends_on, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, From c23eff4b86f4411db365458c68e059fcb24eff2c Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Thu, 16 Feb 2023 13:31:52 -0600 Subject: [PATCH 16/52] Some slight changes to fix failing CI and improve readability --- loopy/kernel/dependency.py | 35 ++++++++++++++++++++--------------- loopy/transform/dependency.py | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 77ccad2ca..097f17087 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,7 +20,7 @@ THE SOFTWARE. """ -from typing import Optional +from typing import Optional, FrozenSet, Tuple, DefaultDict, Set import islpy as isl from islpy import dim_type @@ -29,10 +29,10 @@ from loopy import LoopKernel from loopy import InstructionBase +from loopy.kernel.instruction import HappensAfter from loopy.symbolic import WalkMapper from loopy.translation_unit import for_each_kernel -from loopy.kernel.instruction import HappensAfter - +from loopy.symbolic import get_access_map class AccessMapMapper(WalkMapper): """A subclass of :class:`loopy.symbolic.WalkMapper` used to generate @@ -46,20 +46,27 @@ class AccessMapMapper(WalkMapper): instruction. These maps can be found via access_maps[insn_id][variable_name][inames] + + .. warning:: + This implementation of finding and storing access maps for instructions + is subject to change. """ - def __init__(self, kernel: LoopKernel, var_names: set): + def __init__(self, kernel: LoopKernel, variable_names: Set): self.kernel = kernel - self._var_names = var_names - + self._variable_names = variable_names + + # possibly not the final implementation of this from collections import defaultdict - self.access_maps = defaultdict(lambda: + Dict = DefaultDict + self.access_maps: Dict[str, Dict[str, Dict[FrozenSet, isl.Map]]] = \ + defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: None))) super().__init__() - def map_subscript(self, expr: p.Subscript, inames: frozenset, insn_id: str): + def map_subscript(self, expr: p.Subscript, inames: FrozenSet, insn_id: str): domain = self.kernel.get_inames_domain(inames) @@ -67,22 +74,20 @@ def map_subscript(self, expr: p.Subscript, inames: frozenset, insn_id: str): assert isinstance(expr.aggregate, p.Variable) - if expr.aggregate.name not in self._var_names: - return - - arg_name = expr.aggregate.name + variable_name = expr.aggregate.name subscript = expr.index_tuple + if variable_name not in self._variable_names: + return from loopy.diagnostic import UnableToDetermineAccessRangeError - from loopy.symbolic import get_access_map try: access_map = get_access_map(domain, subscript) except UnableToDetermineAccessRangeError: return - if self.access_maps[insn_id][arg_name][inames] is None: - self.access_maps[insn_id][arg_name][inames] = access_map + if self.access_maps[insn_id][variable_name][inames] is None: + self.access_maps[insn_id][variable_name][inames] = access_map @for_each_kernel diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index b895ce453..89a57934d 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -25,5 +25,5 @@ @for_each_kernel -def narrow_dependencies(kernel: LoopKernel) -> LoopKernel: +def narrow_dependencies(kernel: LoopKernel) -> None: pass From 1647d5a5cdeced699701d8b3cd9928ab9acb31cd Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Thu, 16 Feb 2023 14:15:59 -0600 Subject: [PATCH 17/52] More fixes for CI --- loopy/kernel/dependency.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 097f17087..83d90f767 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,7 +20,7 @@ THE SOFTWARE. """ -from typing import Optional, FrozenSet, Tuple, DefaultDict, Set +from typing import Optional, FrozenSet, Set import islpy as isl from islpy import dim_type @@ -34,6 +34,7 @@ from loopy.translation_unit import for_each_kernel from loopy.symbolic import get_access_map + class AccessMapMapper(WalkMapper): """A subclass of :class:`loopy.symbolic.WalkMapper` used to generate individual array access maps of instructions. Similar to @@ -55,14 +56,16 @@ class AccessMapMapper(WalkMapper): def __init__(self, kernel: LoopKernel, variable_names: Set): self.kernel = kernel self._variable_names = variable_names - + # possibly not the final implementation of this from collections import defaultdict - Dict = DefaultDict - self.access_maps: Dict[str, Dict[str, Dict[FrozenSet, isl.Map]]] = \ - defaultdict(lambda: - defaultdict(lambda: - defaultdict(lambda: None))) + from typing import DefaultDict as Dict + self.access_maps: Dict[Optional[str],\ + Dict[Optional[str],\ + Dict[FrozenSet, isl.Map]]] =\ + defaultdict(lambda: + defaultdict(lambda: + defaultdict(lambda: None))) super().__init__() From 055d7e3c6dd8b837abcbf946c4ed5db9e8c1d1cd Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Sat, 25 Feb 2023 08:07:04 -0600 Subject: [PATCH 18/52] Add knl.id_to_insn to find instructions from ids instead of looping through instructions to find a match --- loopy/kernel/dependency.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 83d90f767..4b4091303 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -60,8 +60,8 @@ def __init__(self, kernel: LoopKernel, variable_names: Set): # possibly not the final implementation of this from collections import defaultdict from typing import DefaultDict as Dict - self.access_maps: Dict[Optional[str],\ - Dict[Optional[str],\ + self.access_maps: Dict[Optional[str], + Dict[Optional[str], Dict[FrozenSet, isl.Map]]] =\ defaultdict(lambda: defaultdict(lambda: @@ -148,11 +148,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: for writer in writer_map.get(var, set()) - {cur_insn.id}: # grab writer from knl.instructions - write_insn = writer - for insn in knl.instructions: - if writer == insn.id: - write_insn = insn.copy() - break + write_insn = knl.id_to_insn[writer] read_rel = get_relation(cur_insn, var) write_rel = get_relation(write_insn, var) @@ -189,11 +185,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: # write-after-read for reader in reader_map.get(var, set()) - {cur_insn.id}: - read_insn = reader - for insn in knl.instructions: - if reader == insn.id: - read_insn = insn.copy() - break + read_insn = knl.id_to_insn[reader] write_rel = get_relation(cur_insn, var) read_rel = get_relation(read_insn, var) @@ -227,11 +219,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: # write-after-write for writer in writer_map.get(var, set()) - {cur_insn.id}: - other_writer = writer - for insn in knl.instructions: - if writer == insn.id: - other_writer = insn.copy() - break + other_writer = knl.id_to_insn[writer] before_write_rel = get_relation(other_writer, var) after_write_rel = get_relation(cur_insn, var) From 888291f5d848f91479b0ca0ee36b227acb8f34cc Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Tue, 28 Feb 2023 12:09:02 -0600 Subject: [PATCH 19/52] Change the way the new happens_after is updated when finding dependencies --- loopy/kernel/dependency.py | 43 ++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 4b4091303..4b8a73027 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -99,9 +99,7 @@ def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: using the access relations of statements. Relies on there being an existing lexicographic ordering of statements. - :arg knl: - - A :class:`loopy.LoopKernel` containing instructions to find the + :arg knl: A :class:`loopy.LoopKernel` containing instructions to find the statement instance level dependencies of. """ @@ -141,7 +139,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: new_insns = [] for cur_insn in knl.instructions: - new_insn = cur_insn.copy() + new_happens_after = {} # handle read-after-write case for var in reads[cur_insn.id]: @@ -159,9 +157,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: lex_map = cur_insn.happens_after[writer].instances_rel deps = lex_map & read_write - new_insn.happens_after.update( - {writer: HappensAfter(var, deps)} - ) + new_happens_after |= {writer: HappensAfter(var, deps)} # writer is not immediately before reader else: @@ -172,12 +168,13 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: # names may not be unique, make out names unique lex_map = make_out_inames_unique(lex_map) read_write = make_out_inames_unique(read_write) + + # align maps lex_map, read_write = isl.align_two(lex_map, read_write) deps = lex_map & read_write - new_insn.happens_after.update( - {writer: HappensAfter(var, deps)} - ) + + new_happens_after |= {writer: HappensAfter(var, deps)} # handle write-after-read and write-after-write for var in writes[cur_insn.id]: @@ -197,9 +194,8 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: if reader in cur_insn.happens_after: lex_map = cur_insn.happens_after[reader].instances_rel deps = lex_map & write_read - new_insn.happens_after.update( - {reader: HappensAfter(var, deps)} - ) + + new_happens_after |= {reader: HappensAfter(var, deps)} # reader is not immediately before writer else: @@ -209,12 +205,13 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: # inames may not be unique, make out inames unique lex_map = make_out_inames_unique(lex_map) write_read = make_out_inames_unique(write_read) + + # align maps lex_map, write_read = isl.align_two(lex_map, write_read) deps = lex_map & write_read - new_insn.happens_after.update( - {reader: HappensAfter(var, deps)} - ) + + new_happens_after |= {reader: HappensAfter(var, deps)} # write-after-write for writer in writer_map.get(var, set()) - {cur_insn.id}: @@ -231,9 +228,8 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: if writer in cur_insn.happens_after: lex_map = cur_insn.happens_after[writer].instances_rel deps = lex_map & write_write - new_insn.happens_after.update( - {writer: HappensAfter(var, deps)} - ) + + new_happens_after |= {writer: HappensAfter(var, deps)} # there is not a writer immediately before current writer else: @@ -243,15 +239,16 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: # make inames unique lex_map = make_out_inames_unique(lex_map) write_write = make_out_inames_unique(write_write) + + # align maps lex_map, write_write = isl.align_two(lex_map, write_write) deps = lex_map & write_write - new_insn.happens_after.update( - {writer: HappensAfter(var, deps)} - ) - new_insns.append(new_insn) + new_happens_after |= {writer: HappensAfter(var, deps)} + + new_insns.append(cur_insn.copy(happens_after=new_happens_after)) return knl.copy(instructions=new_insns) From b785738a637c1974aaafc0be45b63433e6864955 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 6 Mar 2023 17:27:48 -0600 Subject: [PATCH 20/52] Temporary changes for how to handle cases in which happens_after and depends_on are both used --- loopy/kernel/creation.py | 4 ++-- loopy/kernel/dependency.py | 4 ++-- loopy/kernel/instruction.py | 14 +++++++++++++- loopy/transform/realize_reduction.py | 14 +++++++++++++- test/test_einsum.py | 2 ++ 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index 5c321c26a..468e8384c 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -684,6 +684,7 @@ def _count_open_paren_symbols(s): def parse_instructions(instructions, defines): + if isinstance(instructions, str): instructions = [instructions] @@ -1894,8 +1895,7 @@ def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True, if error_if_used: raise LoopyError(msg) -# insn = insn.copy(depends_on=new_deps) - insn = insn.copy(happens_after=new_deps) + insn = insn.copy(depends_on=new_deps) changed = True new_insns.append(insn) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 4b8a73027..829eb5943 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,7 +20,7 @@ THE SOFTWARE. """ -from typing import Optional, FrozenSet, Set +from typing import Optional, FrozenSet, Set, Mapping import islpy as isl from islpy import dim_type @@ -139,7 +139,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: new_insns = [] for cur_insn in knl.instructions: - new_happens_after = {} + new_happens_after: Mapping[str, HappensAfter] = {} # handle read-after-write case for var in reads[cur_insn.id]: diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 13f8d6cab..47e492365 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -300,7 +300,19 @@ def __init__(self, # {{{ process happens_after/depends_on if happens_after is not None and depends_on is not None: - raise TypeError("may not pass both happens_after and depends_on") + # + # TODO come up with a better way of handling the fact that + # depends_on and happens_after co-exist in multiple situations. Most + # of the time it seems to be the case that instructions are + # initialized with happens_after = {} and other parts of loopy are + # still using depends_on as an argument when updating instructions. + # + # a particular case where this occurs is during parse_instructions() + # + happens_after = depends_on + warn("depends_on is deprecated and will stop working in 2024. " + "Instead, use happens_after", DeprecationWarning, stacklevel=2) + # raise TypeError("may not pass both happens_after and depends_on") elif depends_on is not None: happens_after = depends_on diff --git a/loopy/transform/realize_reduction.py b/loopy/transform/realize_reduction.py index b8ddabbbc..8b551cdff 100644 --- a/loopy/transform/realize_reduction.py +++ b/loopy/transform/realize_reduction.py @@ -2013,7 +2013,19 @@ def realize_reduction_for_single_kernel(kernel, callables_table, | red_realize_ctx.surrounding_insn_add_within_inames)) kwargs.pop("id") - kwargs.pop("depends_on") + kwargs.pop("happens_after") + + # + # TODO is this the best way of handling this? + # + try: + kwargs.pop("depends_on") + except KeyError: + from warnings import warn + warn("depends_on is deprecated and will no longer be used in " + "2024. Instead use happens_after", DeprecationWarning, + stacklevel=2) + kwargs.pop("expression") kwargs.pop("assignee", None) kwargs.pop("assignees", None) diff --git a/test/test_einsum.py b/test/test_einsum.py index bada5c8c9..a487e310e 100644 --- a/test/test_einsum.py +++ b/test/test_einsum.py @@ -31,6 +31,8 @@ pytest_generate_tests_for_pyopencl as pytest_generate_tests # noqa +import pudb + def test_make_einsum_error_handling(): with pytest.raises(ValueError): lp.make_einsum("ij,j->j", ("a",)) From 457cb6f1a00d4717e9d85f9779ca99799af0a2e0 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 6 Mar 2023 17:29:35 -0600 Subject: [PATCH 21/52] Revert a change to a test that was not supposed to be changed --- test/test_einsum.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_einsum.py b/test/test_einsum.py index a487e310e..bada5c8c9 100644 --- a/test/test_einsum.py +++ b/test/test_einsum.py @@ -31,8 +31,6 @@ pytest_generate_tests_for_pyopencl as pytest_generate_tests # noqa -import pudb - def test_make_einsum_error_handling(): with pytest.raises(ValueError): lp.make_einsum("ij,j->j", ("a",)) From 096e68b55ec8f7e88ded8b8ef39ecb9201d28fff Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 20 Mar 2023 14:40:57 -0500 Subject: [PATCH 22/52] pushing --- loopy/kernel/dependency.py | 89 +++++++++++++++++++++++++++- loopy/kernel/instruction.py | 34 +++++++---- loopy/transform/realize_reduction.py | 13 +--- 3 files changed, 108 insertions(+), 28 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 829eb5943..94175e858 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -143,7 +143,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: # handle read-after-write case for var in reads[cur_insn.id]: - for writer in writer_map.get(var, set()) - {cur_insn.id}: + for writer in writer_map.get(var, set()): # grab writer from knl.instructions write_insn = knl.id_to_insn[writer] @@ -180,7 +180,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: for var in writes[cur_insn.id]: # write-after-read - for reader in reader_map.get(var, set()) - {cur_insn.id}: + for reader in reader_map.get(var, set()): read_insn = knl.id_to_insn[reader] @@ -214,7 +214,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: new_happens_after |= {reader: HappensAfter(var, deps)} # write-after-write - for writer in writer_map.get(var, set()) - {cur_insn.id}: + for writer in writer_map.get(var, set()): other_writer = knl.id_to_insn[writer] @@ -253,6 +253,89 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: return knl.copy(instructions=new_insns) +@for_each_kernel +def add_lexicographic_happens_before(knl: LoopKernel) -> LoopKernel: + """Compute initial lexicographic happens-before ordering of statements in a + :class:`loopy.LoopKernel`. + """ + + new_insns = [] + n_insns = len(knl.instructions) + for ibefore, insn_before in enumerate(knl.instructions): + if ibefore == n_insns - 1: + new_insns.append(insn_before) + + else: + insn_after = knl.instructions[ibefore + 1] + shared_inames = insn_after.within_inames & insn_before.within_inames + + domain_before = knl.get_inames_domain(insn_before.within_inames) + domain_after = knl.get_inames_domain(insn_after.within_inames) + happens_before = isl.Map.from_domain_and_range( + domain_before, domain_after + ) + + for idim in range(happens_before.dim(dim_type.out)): + happens_before = happens_before.set_dim_name( + dim_type.out, idim, + happens_before.get_dim_name(dim_type.out, idim) + "'") + + n_inames_before = happens_before.dim(dim_type.in_) + happens_before_set = happens_before.move_dims( + dim_type.out, 0, + dim_type.in_, 0, + n_inames_before).range() + + shared_inames_order_before = [ + domain_before.get_dim_name(dim_type.out, idim) + for idim in range(domain_before.dim(dim_type.out)) + if domain_before.get_dim_name(dim_type.out, idim) + in shared_inames + ] + shared_inames_order_after = [ + domain_after.get_dim_name(dim_type.out, idim) + for idim in range(domain_after.dim(dim_type.out)) + if domain_after.get_dim_name(dim_type.out, idim) + in shared_inames + ] + + assert shared_inames_order_before == shared_inames_order_after + shared_inames_order = shared_inames_order_before + + affs = isl.affs_from_space(happens_before_set.space) + + lex_set = isl.Set.empty(happens_before_set.space) + for iinnermost, innermost_iname in enumerate(shared_inames_order): + innermost_set = affs[innermost_iname].lt_set( + affs[innermost_iname + "'"] + ) + + for outer_iname in shared_inames_order[:iinnermost]: + innermost_set = innermost_set & ( + affs[outer_iname].eq_set(affs[outer_iname + "'"]) + ) + + lex_set = lex_set | innermost_set + + lex_map = isl.Map.from_range(lex_set).move_dims( + dim_type.in_, 0, + dim_type.out, 0, + n_inames_before + ) + + happens_before = happens_before & lex_map + + new_happens_before = { + insn_after.id: HappensAfter(None, happens_before) + } + + insn_before = insn_before.copy(happens_after=new_happens_before) + + new_insns.append(insn_before) + + return knl.copy(instructions=new_insns) + + @for_each_kernel def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: """Compute an initial lexicographic happens-after ordering of the statments diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 47e492365..6671420a1 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -300,19 +300,27 @@ def __init__(self, # {{{ process happens_after/depends_on if happens_after is not None and depends_on is not None: - # - # TODO come up with a better way of handling the fact that - # depends_on and happens_after co-exist in multiple situations. Most - # of the time it seems to be the case that instructions are - # initialized with happens_after = {} and other parts of loopy are - # still using depends_on as an argument when updating instructions. - # - # a particular case where this occurs is during parse_instructions() - # - happens_after = depends_on - warn("depends_on is deprecated and will stop working in 2024. " - "Instead, use happens_after", DeprecationWarning, stacklevel=2) - # raise TypeError("may not pass both happens_after and depends_on") + # There are many points at which depends_on is explicitly specified + # during kernel creation. For example, see: + # 1. loopy.kernel.creation:838 + # 2. loopy.transform.precompute:323 + # Thus, it seems we cannot escape situations in which depends_on and + # happens_after are both set. + warn("Using depends_on is deprecated and will stop working in " + "2024. Use happens_after instead.", DeprecationWarning, + stacklevel=2) + + assert isinstance(depends_on, frozenset) + ids_not_in_happens_after = frozenset(happens_after) - depends_on + new_happens_after: Mapping = { + after_id: HappensAfter( + variable_name=None, + instances_rel=None) + for after_id in ids_not_in_happens_after + } + + happens_after |= new_happens_after + elif depends_on is not None: happens_after = depends_on diff --git a/loopy/transform/realize_reduction.py b/loopy/transform/realize_reduction.py index 8b551cdff..bb5c1d22d 100644 --- a/loopy/transform/realize_reduction.py +++ b/loopy/transform/realize_reduction.py @@ -2014,18 +2014,7 @@ def realize_reduction_for_single_kernel(kernel, callables_table, kwargs.pop("id") kwargs.pop("happens_after") - - # - # TODO is this the best way of handling this? - # - try: - kwargs.pop("depends_on") - except KeyError: - from warnings import warn - warn("depends_on is deprecated and will no longer be used in " - "2024. Instead use happens_after", DeprecationWarning, - stacklevel=2) - + # kwargs.pop("depends_on") kwargs.pop("expression") kwargs.pop("assignee", None) kwargs.pop("assignees", None) From 1b9f186802acc3b83e8e4302ade2d24454f3e905 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Tue, 21 Mar 2023 08:04:36 -0500 Subject: [PATCH 23/52] Revert "pushing" This reverts commit 096e68b55ec8f7e88ded8b8ef39ecb9201d28fff. --- loopy/kernel/dependency.py | 89 +--------------------------- loopy/kernel/instruction.py | 34 ++++------- loopy/transform/realize_reduction.py | 13 +++- 3 files changed, 28 insertions(+), 108 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 94175e858..829eb5943 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -143,7 +143,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: # handle read-after-write case for var in reads[cur_insn.id]: - for writer in writer_map.get(var, set()): + for writer in writer_map.get(var, set()) - {cur_insn.id}: # grab writer from knl.instructions write_insn = knl.id_to_insn[writer] @@ -180,7 +180,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: for var in writes[cur_insn.id]: # write-after-read - for reader in reader_map.get(var, set()): + for reader in reader_map.get(var, set()) - {cur_insn.id}: read_insn = knl.id_to_insn[reader] @@ -214,7 +214,7 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: new_happens_after |= {reader: HappensAfter(var, deps)} # write-after-write - for writer in writer_map.get(var, set()): + for writer in writer_map.get(var, set()) - {cur_insn.id}: other_writer = knl.id_to_insn[writer] @@ -253,89 +253,6 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: return knl.copy(instructions=new_insns) -@for_each_kernel -def add_lexicographic_happens_before(knl: LoopKernel) -> LoopKernel: - """Compute initial lexicographic happens-before ordering of statements in a - :class:`loopy.LoopKernel`. - """ - - new_insns = [] - n_insns = len(knl.instructions) - for ibefore, insn_before in enumerate(knl.instructions): - if ibefore == n_insns - 1: - new_insns.append(insn_before) - - else: - insn_after = knl.instructions[ibefore + 1] - shared_inames = insn_after.within_inames & insn_before.within_inames - - domain_before = knl.get_inames_domain(insn_before.within_inames) - domain_after = knl.get_inames_domain(insn_after.within_inames) - happens_before = isl.Map.from_domain_and_range( - domain_before, domain_after - ) - - for idim in range(happens_before.dim(dim_type.out)): - happens_before = happens_before.set_dim_name( - dim_type.out, idim, - happens_before.get_dim_name(dim_type.out, idim) + "'") - - n_inames_before = happens_before.dim(dim_type.in_) - happens_before_set = happens_before.move_dims( - dim_type.out, 0, - dim_type.in_, 0, - n_inames_before).range() - - shared_inames_order_before = [ - domain_before.get_dim_name(dim_type.out, idim) - for idim in range(domain_before.dim(dim_type.out)) - if domain_before.get_dim_name(dim_type.out, idim) - in shared_inames - ] - shared_inames_order_after = [ - domain_after.get_dim_name(dim_type.out, idim) - for idim in range(domain_after.dim(dim_type.out)) - if domain_after.get_dim_name(dim_type.out, idim) - in shared_inames - ] - - assert shared_inames_order_before == shared_inames_order_after - shared_inames_order = shared_inames_order_before - - affs = isl.affs_from_space(happens_before_set.space) - - lex_set = isl.Set.empty(happens_before_set.space) - for iinnermost, innermost_iname in enumerate(shared_inames_order): - innermost_set = affs[innermost_iname].lt_set( - affs[innermost_iname + "'"] - ) - - for outer_iname in shared_inames_order[:iinnermost]: - innermost_set = innermost_set & ( - affs[outer_iname].eq_set(affs[outer_iname + "'"]) - ) - - lex_set = lex_set | innermost_set - - lex_map = isl.Map.from_range(lex_set).move_dims( - dim_type.in_, 0, - dim_type.out, 0, - n_inames_before - ) - - happens_before = happens_before & lex_map - - new_happens_before = { - insn_after.id: HappensAfter(None, happens_before) - } - - insn_before = insn_before.copy(happens_after=new_happens_before) - - new_insns.append(insn_before) - - return knl.copy(instructions=new_insns) - - @for_each_kernel def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: """Compute an initial lexicographic happens-after ordering of the statments diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 6671420a1..47e492365 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -300,27 +300,19 @@ def __init__(self, # {{{ process happens_after/depends_on if happens_after is not None and depends_on is not None: - # There are many points at which depends_on is explicitly specified - # during kernel creation. For example, see: - # 1. loopy.kernel.creation:838 - # 2. loopy.transform.precompute:323 - # Thus, it seems we cannot escape situations in which depends_on and - # happens_after are both set. - warn("Using depends_on is deprecated and will stop working in " - "2024. Use happens_after instead.", DeprecationWarning, - stacklevel=2) - - assert isinstance(depends_on, frozenset) - ids_not_in_happens_after = frozenset(happens_after) - depends_on - new_happens_after: Mapping = { - after_id: HappensAfter( - variable_name=None, - instances_rel=None) - for after_id in ids_not_in_happens_after - } - - happens_after |= new_happens_after - + # + # TODO come up with a better way of handling the fact that + # depends_on and happens_after co-exist in multiple situations. Most + # of the time it seems to be the case that instructions are + # initialized with happens_after = {} and other parts of loopy are + # still using depends_on as an argument when updating instructions. + # + # a particular case where this occurs is during parse_instructions() + # + happens_after = depends_on + warn("depends_on is deprecated and will stop working in 2024. " + "Instead, use happens_after", DeprecationWarning, stacklevel=2) + # raise TypeError("may not pass both happens_after and depends_on") elif depends_on is not None: happens_after = depends_on diff --git a/loopy/transform/realize_reduction.py b/loopy/transform/realize_reduction.py index bb5c1d22d..8b551cdff 100644 --- a/loopy/transform/realize_reduction.py +++ b/loopy/transform/realize_reduction.py @@ -2014,7 +2014,18 @@ def realize_reduction_for_single_kernel(kernel, callables_table, kwargs.pop("id") kwargs.pop("happens_after") - # kwargs.pop("depends_on") + + # + # TODO is this the best way of handling this? + # + try: + kwargs.pop("depends_on") + except KeyError: + from warnings import warn + warn("depends_on is deprecated and will no longer be used in " + "2024. Instead use happens_after", DeprecationWarning, + stacklevel=2) + kwargs.pop("expression") kwargs.pop("assignee", None) kwargs.pop("assignees", None) From 5f3a249c63d1612704bdbd66fe7af8d340436bc3 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Thu, 23 Mar 2023 18:29:47 -0500 Subject: [PATCH 24/52] Refactor dependency finding code to eliminate bugs. --- loopy/kernel/dependency.py | 318 ++++++++++++++++++++----------------- test/test_dependencies.py | 4 +- 2 files changed, 172 insertions(+), 150 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 829eb5943..618e55db7 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,115 +20,58 @@ THE SOFTWARE. """ -from typing import Optional, FrozenSet, Set, Mapping +from typing import Mapping import islpy as isl from islpy import dim_type -import pymbolic.primitives as p - from loopy import LoopKernel -from loopy import InstructionBase from loopy.kernel.instruction import HappensAfter -from loopy.symbolic import WalkMapper +from loopy.symbolic import ArrayAccessFinder from loopy.translation_unit import for_each_kernel from loopy.symbolic import get_access_map - -class AccessMapMapper(WalkMapper): - """A subclass of :class:`loopy.symbolic.WalkMapper` used to generate - individual array access maps of instructions. Similar to - :class:`loopy.symbolic.BatchedAccessMapMapper`, except that it generates - single access maps instead of combined access maps. - - .. attribute:: access_maps - - A dict containing the access map of a particular array as accessed by an - instruction. These maps can be found via - - access_maps[insn_id][variable_name][inames] - - .. warning:: - This implementation of finding and storing access maps for instructions - is subject to change. - """ - - def __init__(self, kernel: LoopKernel, variable_names: Set): - self.kernel = kernel - self._variable_names = variable_names - - # possibly not the final implementation of this - from collections import defaultdict - from typing import DefaultDict as Dict - self.access_maps: Dict[Optional[str], - Dict[Optional[str], - Dict[FrozenSet, isl.Map]]] =\ - defaultdict(lambda: - defaultdict(lambda: - defaultdict(lambda: None))) - - super().__init__() - - def map_subscript(self, expr: p.Subscript, inames: FrozenSet, insn_id: str): - - domain = self.kernel.get_inames_domain(inames) - - WalkMapper.map_subscript(self, expr, inames) - - assert isinstance(expr.aggregate, p.Variable) - - variable_name = expr.aggregate.name - subscript = expr.index_tuple - if variable_name not in self._variable_names: - return - - from loopy.diagnostic import UnableToDetermineAccessRangeError - - try: - access_map = get_access_map(domain, subscript) - except UnableToDetermineAccessRangeError: - return - - if self.access_maps[insn_id][variable_name][inames] is None: - self.access_maps[insn_id][variable_name][inames] = access_map +import operator +from functools import reduce @for_each_kernel -def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: - """Determine precise data dependencies between dynamic statement instances - using the access relations of statements. Relies on there being an existing - lexicographic ordering of statements. - - :arg knl: A :class:`loopy.LoopKernel` containing instructions to find the - statement instance level dependencies of. +def narrow_dependencies(knl: LoopKernel) -> LoopKernel: + """Compute statement-instance-level data dependencies between statements in + a program. Relies on an existing lexicographic ordering, i.e. computed by + add_lexicographic_happens_after. + + In particular, this function computes three dependency relations: + 1. The flow dependency relation, aka read-after-write + 2. The anti-dependecny relation, aka write-after-reads + 3. The output dependency relation, aka write-after-write + + The full dependency relation between a two statements in a program is the + union of these three dependency relations. """ writer_map = knl.writer_map() reader_map = knl.reader_map() reads = { - insn.id: insn.read_dependency_names() - insn.within_inames - for insn in knl.instructions + insn.id: insn.read_dependency_names() - insn.within_inames + for insn in knl.instructions } + writes = { - insn.id: insn.write_dependency_names() - insn.within_inames - for insn in knl.instructions + insn.id: insn.write_dependency_names() - insn.within_inames + for insn in knl.instructions } - variables = knl.all_variable_names() - amap = AccessMapMapper(knl, variables) - for insn in knl.instructions: - amap(insn.assignee, insn.within_inames, insn.id) - amap(insn.expression, insn.within_inames, insn.id) - - def get_relation(insn: InstructionBase, variable: Optional[str]) -> isl.Map: - return amap.access_maps[insn.id][variable][insn.within_inames] - def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: - # equivalent to the composition R^{-1} o S + """Equivalent to computing the relation R^{-1} o S + """ return s.apply_range(r.reverse()) - def make_out_inames_unique(relation: isl.Map) -> isl.Map: + def make_inames_unique(relation: isl.Map) -> isl.Map: + """Make the inames of a particular map unique by adding a single quote + to each iname in the range of the map + """ ndim = relation.dim(dim_type.out) for i in range(ndim): iname = relation.get_dim_name(dim_type.out, i) + "'" @@ -136,119 +79,198 @@ def make_out_inames_unique(relation: isl.Map) -> isl.Map: return relation + access_finder = ArrayAccessFinder() new_insns = [] - for cur_insn in knl.instructions: + for insn in knl.instructions: new_happens_after: Mapping[str, HappensAfter] = {} - # handle read-after-write case - for var in reads[cur_insn.id]: - for writer in writer_map.get(var, set()) - {cur_insn.id}: - - # grab writer from knl.instructions + # compute flow dependencies + for variable in reads[insn.id]: + for writer in writer_map.get(variable, set()): write_insn = knl.id_to_insn[writer] - read_rel = get_relation(cur_insn, var) - write_rel = get_relation(write_insn, var) - read_write = get_unordered_deps(read_rel, write_rel) + read_domain = knl.get_inames_domain(insn.within_inames) + write_domain = knl.get_inames_domain(write_insn.within_inames) + + read_subscripts = access_finder(insn.expression) + write_subscripts = access_finder(write_insn.assignee) + + assert isinstance(read_subscripts, set) + assert isinstance(write_subscripts, set) + + read_maps = [] + for sub in read_subscripts: + if sub.aggregate.name == variable: + read_maps.append( + get_access_map(read_domain, sub.index_tuple) + ) + + write_maps = [] + for sub in write_subscripts: + if sub.aggregate.name == variable: + write_maps.append( + get_access_map(write_domain, sub.index_tuple) + ) - # writer is immediately before reader - if writer in cur_insn.happens_after: - lex_map = cur_insn.happens_after[writer].instances_rel + assert len(read_maps) > 0 + assert len(write_maps) > 0 - deps = lex_map & read_write - new_happens_after |= {writer: HappensAfter(var, deps)} + read_map = reduce(operator.or_, read_maps) + write_map = reduce(operator.or_, write_maps) + + read_write = get_unordered_deps(read_map, write_map) + + if writer in insn.happens_after: + lex_map = insn.happens_after[writer].instances_rel - # writer is not immediately before reader else: - # generate a lexicographical map between the instructions lex_map = read_write.lex_lt_map(read_write) if lex_map.space != read_write.space: - # names may not be unique, make out names unique - lex_map = make_out_inames_unique(lex_map) - read_write = make_out_inames_unique(read_write) + lex_map = make_inames_unique(lex_map) + read_write = make_inames_unique(read_write) - # align maps lex_map, read_write = isl.align_two(lex_map, read_write) - deps = lex_map & read_write + flow_dependencies = read_write & lex_map - new_happens_after |= {writer: HappensAfter(var, deps)} + if not flow_dependencies.is_empty(): + if writer in new_happens_after: + old_rel = new_happens_after[writer].instances_rel + flow_dependencies |= old_rel - # handle write-after-read and write-after-write - for var in writes[cur_insn.id]: + new_happens_after |= { + writer: HappensAfter(variable, + flow_dependencies) + } - # write-after-read - for reader in reader_map.get(var, set()) - {cur_insn.id}: + # compute anti and output dependencies + for variable in writes[insn.id]: + # compute anti dependencies + for reader in reader_map.get(variable, set()): read_insn = knl.id_to_insn[reader] - write_rel = get_relation(cur_insn, var) - read_rel = get_relation(read_insn, var) + read_domain = knl.get_inames_domain(read_insn.within_inames) + write_domain = knl.get_inames_domain(insn.within_inames) + + read_subscripts = access_finder(read_insn.expression) + write_subscripts = access_finder(insn.assignee) + + assert isinstance(read_subscripts, set) + assert isinstance(write_subscripts, set) + + read_maps = [] + for sub in read_subscripts: + if sub.aggregate.name == variable: + read_maps.append( + get_access_map(read_domain, sub.index_tuple) + ) - # calculate dependency map - write_read = get_unordered_deps(write_rel, read_rel) + write_maps = [] + for sub in write_subscripts: + if sub.aggregate.name == variable: + write_maps.append( + get_access_map(write_domain, sub.index_tuple) + ) - # reader is immediately before writer - if reader in cur_insn.happens_after: - lex_map = cur_insn.happens_after[reader].instances_rel - deps = lex_map & write_read + assert len(read_maps) > 0 + assert len(write_maps) > 0 - new_happens_after |= {reader: HappensAfter(var, deps)} + read_map = reduce(operator.or_, read_maps) + write_map = reduce(operator.or_, write_maps) + + write_read = get_unordered_deps(write_map, read_map) + + if reader in insn.happens_after: + lex_map = insn.happens_after[reader].instances_rel - # reader is not immediately before writer else: lex_map = write_read.lex_lt_map(write_read) if lex_map.space != write_read.space: - # inames may not be unique, make out inames unique - lex_map = make_out_inames_unique(lex_map) - write_read = make_out_inames_unique(write_read) + lex_map = make_inames_unique(lex_map) + write_read = make_inames_unique(lex_map) - # align maps lex_map, write_read = isl.align_two(lex_map, write_read) - deps = lex_map & write_read + anti_dependencies = write_read & lex_map + + if not anti_dependencies.is_empty(): + if reader in new_happens_after: + old_rel = new_happens_after[reader].instances_rel - new_happens_after |= {reader: HappensAfter(var, deps)} + anti_dependencies |= old_rel - # write-after-write - for writer in writer_map.get(var, set()) - {cur_insn.id}: + new_happens_after |= { + reader: HappensAfter(variable, anti_dependencies) + } + + # compute output dependencies + for writer in writer_map.get(variable, set()): + before_write = knl.id_to_insn[writer] + + before_domain = knl.get_inames_domain( + before_write.within_inames + ) + after_domain = knl.get_inames_domain(insn.within_inames) - other_writer = knl.id_to_insn[writer] + before_subscripts = access_finder(before_write.assignee) + after_subscripts = access_finder(insn.assignee) - before_write_rel = get_relation(other_writer, var) - after_write_rel = get_relation(cur_insn, var) + assert isinstance(before_subscripts, set) + assert isinstance(after_subscripts, set) - write_write = get_unordered_deps(after_write_rel, - before_write_rel) + before_maps = [] + for sub in before_subscripts: + if sub.aggregate.name == variable: + before_maps.append( + get_access_map(before_domain, sub.index_tuple) + ) - # other writer is immediately before current writer - if writer in cur_insn.happens_after: - lex_map = cur_insn.happens_after[writer].instances_rel - deps = lex_map & write_write + after_maps = [] + for sub in after_subscripts: + if sub.aggregate.name == variable: + after_maps.append( + get_access_map(after_domain, sub.index_tuple) + ) - new_happens_after |= {writer: HappensAfter(var, deps)} + assert len(before_maps) > 0 + assert len(after_maps) > 0 + + before_map = reduce(operator.or_, before_maps) + after_map = reduce(operator.or_, before_maps) + + write_write = get_unordered_deps(after_map, before_map) + + if writer in insn.happens_after: + lex_map = insn.happens_after[writer].instances_rel - # there is not a writer immediately before current writer else: lex_map = write_write.lex_lt_map(write_write) if lex_map.space != write_write.space: - # make inames unique - lex_map = make_out_inames_unique(lex_map) - write_write = make_out_inames_unique(write_write) + lex_map = make_inames_unique(lex_map) + write_write = make_inames_unique(write_write) + + lex_map, write_write = isl.align_two( + lex_map, write_write + ) + + output_dependencies = write_write & lex_map - # align maps - lex_map, write_write = isl.align_two(lex_map, - write_write) + if not output_dependencies.is_empty(): + if writer in new_happens_after[writer]: + old_rel = new_happens_after[writer].instances_rel - deps = lex_map & write_write + output_dependencies |= old_rel - new_happens_after |= {writer: HappensAfter(var, deps)} + new_happens_after |= { + writer: HappensAfter(variable, output_dependencies) + } - new_insns.append(cur_insn.copy(happens_after=new_happens_after)) + new_insns.append(insn.copy(happens_after=new_happens_after)) return knl.copy(instructions=new_insns) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index d7eb12d2d..53b464672 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -54,10 +54,10 @@ def test_data_dependencies(): """) from loopy.kernel.dependency import add_lexicographic_happens_after,\ - compute_data_dependencies + narrow_dependencies knl = add_lexicographic_happens_after(knl) - compute_data_dependencies(knl) + narrow_dependencies(knl) if __name__ == "__main__": From 169e808eb9cf617ceaf1f2261bf166a0f46504b1 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Thu, 23 Mar 2023 21:01:34 -0500 Subject: [PATCH 25/52] Attempt at fixing CI failures due to replacing happens_after with depends_on --- loopy/kernel/creation.py | 49 +++++++++++--------- loopy/kernel/instruction.py | 30 ++++--------- loopy/transform/add_barrier.py | 2 +- loopy/transform/buffer.py | 8 ++-- loopy/transform/callable.py | 8 ++-- loopy/transform/fusion.py | 2 +- loopy/transform/instruction.py | 6 +-- loopy/transform/pack_and_unpack_args.py | 8 ++-- loopy/transform/precompute.py | 6 +-- loopy/transform/realize_reduction.py | 59 ++++++++++--------------- loopy/transform/save.py | 4 +- test/test_loopy.py | 2 +- 12 files changed, 85 insertions(+), 99 deletions(-) diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index 468e8384c..d019469e7 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -36,6 +36,7 @@ SubstitutionRule, AddressSpace, ValueArg) from loopy.translation_unit import for_each_kernel from loopy.diagnostic import LoopyError, warn_with_kernel +from loopy.kernel.instruction import HappensAfter import islpy as isl from islpy import dim_type from pytools import ProcessLogger @@ -193,7 +194,7 @@ def subst_func(var): def get_default_insn_options_dict(): return { - "depends_on": frozenset(), + "happens_after": frozenset(), "depends_on_is_final": False, "no_sync_with": frozenset(), "groups": frozenset(), @@ -289,14 +290,15 @@ def parse_nosync_option(opt_value): result["depends_on_is_final"] = True opt_value = (opt_value[1:]).strip() - result["depends_on"] = result["depends_on"].union(frozenset( + result["happens_after"] = result["happens_after"].union(frozenset( intern(dep.strip()) for dep in opt_value.split(":") if dep.strip())) elif opt_key == "dep_query" and opt_value is not None: from loopy.match import parse_match match = parse_match(opt_value) - result["depends_on"] = result["depends_on"].union(frozenset([match])) + result["happens_after"] = result["happens_after"].union( + frozenset([match])) elif opt_key == "nosync" and opt_value is not None: if is_with_block: @@ -717,8 +719,8 @@ def intern_if_str(s): copy_args = { "id": intern_if_str(insn.id), - "depends_on": frozenset(intern_if_str(dep) - for dep in insn.depends_on), + "happens_after": frozenset(intern_if_str(dep) + for dep in insn.happens_after), "groups": frozenset(checked_intern(grp) for grp in insn.groups), "conflicts_with_groups": frozenset( checked_intern(grp) for grp in insn.conflicts_with_groups), @@ -835,11 +837,12 @@ def intern_if_str(s): # If it's inside a for/with block, then it's # final now. bool(local_w_inames)), - depends_on=( + happens_after=( (insn.depends_on - | insn_options_stack[-1]["depends_on"]) - if insn_options_stack[-1]["depends_on"] is not None - else insn.depends_on), + | insn_options_stack[-1]["happens_after"]) + if insn_options_stack[-1]["happens_after"] is \ + not None + else insn.happens_after), tags=( insn.tags | insn_options_stack[-1]["tags"]), @@ -1769,18 +1772,20 @@ def resolve_dependencies(knl): new_insns = [] for insn in knl.instructions: - depends_on = _resolve_dependencies( - "a dependency", knl, insn, insn.depends_on) + happens_after = _resolve_dependencies( + "a dependency", knl, insn, insn.happens_after) no_sync_with = frozenset( (resolved_insn_id, nosync_scope) for nosync_dep, nosync_scope in insn.no_sync_with for resolved_insn_id in _resolve_dependencies("nosync", knl, insn, (nosync_dep,))) - if depends_on == insn.depends_on and no_sync_with == insn.no_sync_with: + if happens_after == insn.happens_after and \ + no_sync_with == insn.no_sync_with: new_insn = insn else: - new_insn = insn.copy(depends_on=depends_on, no_sync_with=no_sync_with) + new_insn = insn.copy(happens_after=happens_after, + no_sync_with=no_sync_with) new_insns.append(new_insn) return knl.copy(instructions=new_insns) @@ -1873,14 +1878,18 @@ def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True, - {insn.id}) # }}} + + happens_after = insn.happens_after + + if not isinstance(happens_after, frozenset): + happens_after = frozenset(happens_after) - depends_on = insn.depends_on - if depends_on is None: - depends_on = frozenset() + if happens_after is None: + happens_after = frozenset() - new_deps = frozenset(auto_deps) | depends_on + new_deps = frozenset(auto_deps) | frozenset(happens_after) - if new_deps != depends_on: + if new_deps != happens_after: msg = ( "The single-writer dependency heuristic added dependencies " "on instruction ID(s) '%s' to instruction ID '%s' after " @@ -1889,13 +1898,13 @@ def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True, "To fix this, ensure that instruction dependencies " "are added/resolved as soon as possible, ideally at kernel " "creation time." - % (", ".join(new_deps - depends_on), insn.id)) + % (", ".join(new_deps - happens_after), insn.id)) if warn_if_used: warn_with_kernel(kernel, "single_writer_after_creation", msg) if error_if_used: raise LoopyError(msg) - insn = insn.copy(depends_on=new_deps) + insn = insn.copy(happens_after=new_deps) changed = True new_insns.append(insn) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 47e492365..57e6a3b9e 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -300,19 +300,7 @@ def __init__(self, # {{{ process happens_after/depends_on if happens_after is not None and depends_on is not None: - # - # TODO come up with a better way of handling the fact that - # depends_on and happens_after co-exist in multiple situations. Most - # of the time it seems to be the case that instructions are - # initialized with happens_after = {} and other parts of loopy are - # still using depends_on as an argument when updating instructions. - # - # a particular case where this occurs is during parse_instructions() - # - happens_after = depends_on - warn("depends_on is deprecated and will stop working in 2024. " - "Instead, use happens_after", DeprecationWarning, stacklevel=2) - # raise TypeError("may not pass both happens_after and depends_on") + raise TypeError("may not pass both happens_after and depends_on") elif depends_on is not None: happens_after = depends_on @@ -1065,7 +1053,7 @@ class CallInstruction(MultiAssignmentBase): def __init__(self, assignees, expression, id=None, - depends_on=None, + happens_after=None, depends_on_is_final=None, groups=None, conflicts_with_groups=None, @@ -1078,7 +1066,7 @@ def __init__(self, super().__init__( id=id, - happens_after=depends_on, + happens_after=happens_after, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, @@ -1360,7 +1348,7 @@ class CInstruction(InstructionBase): def __init__(self, iname_exprs, code, read_variables=frozenset(), assignees=(), - id=None, depends_on=None, depends_on_is_final=None, + id=None, happens_after=None, depends_on_is_final=None, groups=None, conflicts_with_groups=None, no_sync_with=None, within_inames_is_final=None, within_inames=None, @@ -1378,7 +1366,7 @@ def __init__(self, InstructionBase.__init__(self, id=id, - happens_after=depends_on, + happens_after=happens_after, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, no_sync_with=no_sync_with, @@ -1528,7 +1516,7 @@ class NoOpInstruction(_DataObliviousInstruction): ... nop """ - def __init__(self, id=None, depends_on=None, depends_on_is_final=None, + def __init__(self, id=None, happens_after=None, depends_on_is_final=None, groups=None, conflicts_with_groups=None, no_sync_with=None, within_inames_is_final=None, within_inames=None, @@ -1536,7 +1524,7 @@ def __init__(self, id=None, depends_on=None, depends_on_is_final=None, predicates=None, tags=None): super().__init__( id=id, - happens_after=depends_on, + happens_after=happens_after, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, @@ -1587,7 +1575,7 @@ class BarrierInstruction(_DataObliviousInstruction): fields = _DataObliviousInstruction.fields | {"synchronization_kind", "mem_kind"} - def __init__(self, id, depends_on=None, depends_on_is_final=None, + def __init__(self, id, happens_after=None, depends_on_is_final=None, groups=None, conflicts_with_groups=None, no_sync_with=None, within_inames_is_final=None, within_inames=None, @@ -1600,7 +1588,7 @@ def __init__(self, id, depends_on=None, depends_on_is_final=None, super().__init__( id=id, - happens_after=depends_on, + happens_after=happens_after, depends_on_is_final=depends_on_is_final, groups=groups, conflicts_with_groups=conflicts_with_groups, diff --git a/loopy/transform/add_barrier.py b/loopy/transform/add_barrier.py index 7a220418f..16412e529 100644 --- a/loopy/transform/add_barrier.py +++ b/loopy/transform/add_barrier.py @@ -80,7 +80,7 @@ def add_barrier(kernel, insn_before="", insn_after="", id_based_on=None, else: insns_before = None - barrier_to_add = BarrierInstruction(depends_on=insns_before, + barrier_to_add = BarrierInstruction(happens_after=insns_before, depends_on_is_final=True, id=id, within_inames=within_inames, diff --git a/loopy/transform/buffer.py b/loopy/transform/buffer.py index b3fc69671..b043cb77f 100644 --- a/loopy/transform/buffer.py +++ b/loopy/transform/buffer.py @@ -391,7 +391,7 @@ def buffer_array_for_single_kernel(kernel, callables_table, var_name, within_inames=( frozenset(within_inames) | frozenset(non1_init_inames)), - depends_on=frozenset(), + happens_after=frozenset(), depends_on_is_final=True) # }}} @@ -422,7 +422,7 @@ def none_to_empty_set(s): if insn.id in aar.modified_insn_ids: new_insns.append( insn.copy( - depends_on=( + happens_after=( none_to_empty_set(insn.depends_on) | frozenset([init_insn_id])))) else: @@ -464,7 +464,7 @@ def none_to_empty_set(s): from loopy.kernel.data import Assignment store_instruction = Assignment( id=kernel.make_unique_instruction_id(based_on="store_"+var_name), - depends_on=frozenset(aar.modified_insn_ids), + happens_after=frozenset(aar.modified_insn_ids), no_sync_with=frozenset([(init_insn_id, "any")]), assignee=store_target, expression=store_expression, @@ -481,7 +481,7 @@ def none_to_empty_set(s): # new_insns_with_redirected_deps: if an insn depends on a modified # insn, then it should also depend on the store insn. new_insns_with_redirected_deps = [ - insn.copy(depends_on=(insn.depends_on | {store_instruction.id})) + insn.copy(happens_after=(insn.depends_on | {store_instruction.id})) if insn.depends_on & aar.modified_insn_ids else insn for insn in new_insns diff --git a/loopy/transform/callable.py b/loopy/transform/callable.py index 3b239dfc7..52fa734a8 100644 --- a/loopy/transform/callable.py +++ b/loopy/transform/callable.py @@ -389,13 +389,13 @@ def _inline_call_instruction(caller_knl, callee_knl, call_insn): noop_start = NoOpInstruction( id=ing(callee_label+"_start"), within_inames=call_insn.within_inames, - depends_on=call_insn.depends_on, + happens_after=call_insn.depends_on, predicates=call_insn.predicates, ) noop_end = NoOpInstruction( id=call_insn.id, within_inames=call_insn.within_inames, - depends_on=frozenset(insn_id_map.values()), + happens_after=frozenset(insn_id_map.values()), depends_on_is_final=True, predicates=call_insn.predicates, ) @@ -422,7 +422,7 @@ def _inline_call_instruction(caller_knl, callee_knl, call_insn): insn = insn.copy( id=insn_id_map[insn.id], within_inames=new_within_inames, - depends_on=new_depends_on, + happens_after=new_depends_on, depends_on_is_final=True, tags=insn.tags | call_insn.tags, atomicity=new_atomicity, @@ -433,7 +433,7 @@ def _inline_call_instruction(caller_knl, callee_knl, call_insn): insn = insn.copy( id=new_id, within_inames=new_within_inames, - depends_on=new_depends_on, + happens_after=new_depends_on, tags=insn.tags | call_insn.tags, no_sync_with=new_no_sync_with, predicates=insn.predicates | call_insn.predicates, diff --git a/loopy/transform/fusion.py b/loopy/transform/fusion.py index 2fd39cfc2..bc50e627c 100644 --- a/loopy/transform/fusion.py +++ b/loopy/transform/fusion.py @@ -436,7 +436,7 @@ def fuse_kernels(kernels, suffixes=None, data_flow=None): for insn_id in kernel_insn_ids[to_kernel]: insn = id_to_insn[insn_id] if var_name in insn.dependency_names(): - insn = insn.copy(depends_on=insn.depends_on | from_writer_ids) + insn = insn.copy(happens_after=insn.depends_on | from_writer_ids) id_to_insn[insn_id] = insn diff --git a/loopy/transform/instruction.py b/loopy/transform/instruction.py index 605736fe0..921a65699 100644 --- a/loopy/transform/instruction.py +++ b/loopy/transform/instruction.py @@ -131,7 +131,7 @@ def add_dep(insn): else: new_deps = new_deps | added_deps - return insn.copy(depends_on=new_deps) + return insn.copy(happens_after=new_deps) result = map_instructions(kernel, insn_match, add_dep) @@ -245,7 +245,7 @@ def remove_instructions(kernel, insn_ids): if insn_id not in insn_ids) new_insns.append( - insn.copy(depends_on=new_deps, no_sync_with=new_no_sync_with)) + insn.copy(happens_after=new_deps, no_sync_with=new_no_sync_with)) return kernel.copy( instructions=new_insns) @@ -278,7 +278,7 @@ def replace_instruction_ids_in_insn(insn, replacements): if changed: return insn.copy( - depends_on=frozenset(new_depends_on + extra_depends_on), + happens_after=frozenset(new_depends_on + extra_depends_on), no_sync_with=frozenset(new_no_sync_with)) else: return insn diff --git a/loopy/transform/pack_and_unpack_args.py b/loopy/transform/pack_and_unpack_args.py index daf9316cd..50bda363d 100644 --- a/loopy/transform/pack_and_unpack_args.py +++ b/loopy/transform/pack_and_unpack_args.py @@ -230,7 +230,7 @@ def pack_and_unpack_args_for_call_for_single_kernel(kernel, within_inames=insn.within_inames - ilp_inames | { new_pack_inames[i].name for i in p.swept_inames} | ( new_ilp_inames), - depends_on=insn.depends_on, + happens_after=insn.depends_on, id=ing(insn.id+"_pack"), depends_on_is_final=True )) @@ -243,7 +243,7 @@ def pack_and_unpack_args_for_call_for_single_kernel(kernel, new_unpack_inames[i].name for i in p.swept_inames} | ( new_ilp_inames), id=ing(insn.id+"_unpack"), - depends_on=frozenset([insn.id]), + happens_after=frozenset([insn.id]), depends_on_is_final=True )) @@ -280,7 +280,7 @@ def pack_and_unpack_args_for_call_for_single_kernel(kernel, new_assignees = tuple(subst_mapper(new_id_to_parameters[-i-1]) for i, _ in enumerate(insn.assignees)) new_call_insn = new_call_insn.copy( - depends_on=new_call_insn.depends_on | { + happens_after=new_call_insn.depends_on | { pack.id for pack in packing_insns}, within_inames=new_call_insn.within_inames - ilp_inames | ( new_ilp_inames), @@ -306,7 +306,7 @@ def pack_and_unpack_args_for_call_for_single_kernel(kernel, for old_insn_id in insn.depends_on & set(old_insn_to_new_insns): new_depends_on |= frozenset(i.id for i in old_insn_to_new_insns[old_insn_id]) - new_instructions.append(insn.copy(depends_on=new_depends_on)) + new_instructions.append(insn.copy(happens_after=new_depends_on)) kernel = kernel.copy( domains=kernel.domains + new_domains, instructions=new_instructions, diff --git a/loopy/transform/precompute.py b/loopy/transform/precompute.py index aab295741..f11d3bcad 100644 --- a/loopy/transform/precompute.py +++ b/loopy/transform/precompute.py @@ -321,7 +321,7 @@ def map_kernel(self, kernel): if self.replaced_something: insn = insn.copy( - depends_on=( + happens_after=( insn.depends_on | frozenset([self.compute_dep_id]))) @@ -952,7 +952,7 @@ def add_assumptions(d): from loopy.kernel.instruction import BarrierInstruction barrier_insn = BarrierInstruction( id=barrier_insn_id, - depends_on=frozenset([compute_insn_id]), + happens_after=frozenset([compute_insn_id]), synchronization_kind="global", mem_kind="global") compute_dep_id = barrier_insn_id @@ -985,7 +985,7 @@ def add_assumptions(d): kernel = kernel.copy( instructions=[ - insn.copy(depends_on=frozenset(invr.compute_insn_depends_on)) + insn.copy(happens_after=frozenset(invr.compute_insn_depends_on)) if insn.id == compute_insn_id else insn for insn in kernel.instructions]) diff --git a/loopy/transform/realize_reduction.py b/loopy/transform/realize_reduction.py index 8b551cdff..a683bc34f 100644 --- a/loopy/transform/realize_reduction.py +++ b/loopy/transform/realize_reduction.py @@ -136,7 +136,7 @@ def changes_made(self): # }}} - def new_subinstruction(self, *, within_inames, depends_on, + def new_subinstruction(self, *, within_inames, happens_after, no_sync_with=None, predicates=None): if no_sync_with is None: no_sync_with = self.surrounding_no_sync_with @@ -145,7 +145,7 @@ def new_subinstruction(self, *, within_inames, depends_on, return replace(self, surrounding_within_inames=within_inames, - surrounding_depends_on=depends_on, + surrounding_depends_on=happens_after, surrounding_no_sync_with=no_sync_with, surrounding_predicates=predicates, @@ -159,7 +159,7 @@ def get_insn_kwargs(self): self.surrounding_within_inames | frozenset(self.surrounding_insn_add_within_inames)), "within_inames_is_final": True, - "depends_on": ( + "happens_after": ( self.surrounding_depends_on | frozenset(self.surrounding_insn_add_depends_on)), "no_sync_with": ( @@ -668,7 +668,8 @@ def _add_to_depends_on(insn_id, new_depends_on_params): insn = new_or_updated_instructions.get(insn_id, insn) new_or_updated_instructions[insn_id] = ( insn.copy( - depends_on=insn.depends_on | frozenset(new_depends_on_params))) + happens_after=insn.depends_on | frozenset( + new_depends_on_params))) # }}} @@ -737,7 +738,7 @@ def _add_to_depends_on(insn_id, new_depends_on_params): assignees=(assignee,), expression=new_assignee, id=new_assignment_id, - depends_on=frozenset([last_added_insn_id]), + happens_after=frozenset([last_added_insn_id]), depends_on_is_final=True, no_sync_with=( insn.no_sync_with | frozenset([(insn.id, "any")])), @@ -943,7 +944,7 @@ def expand_inner_reduction( id=id, assignees=temp_vars, expression=expr, - depends_on=depends_on, + happens_after=depends_on, within_inames=within_inames, within_inames_is_final=True, predicates=predicates) @@ -983,7 +984,7 @@ def map_reduction_seq(red_realize_ctx, expr, nresults, arg_dtypes, reduction_dty assignees=acc_vars, within_inames=red_realize_ctx.surrounding_within_inames, within_inames_is_final=True, - depends_on=frozenset(), + happens_after=frozenset(), expression=expression, # Do not inherit predicates: Those might read variables @@ -1005,7 +1006,7 @@ def map_reduction_seq(red_realize_ctx, expr, nresults, arg_dtypes, reduction_dty within_inames=( red_realize_ctx.surrounding_within_inames | frozenset(expr.inames)), - depends_on=( + happens_after=( frozenset({init_id}) | red_realize_ctx.surrounding_depends_on)) @@ -1155,7 +1156,7 @@ def map_reduction_local(red_realize_ctx, expr, nresults, arg_dtypes, red_realize_ctx.surrounding_within_inames | frozenset([base_exec_iname])), within_inames_is_final=True, - depends_on=frozenset(), + happens_after=frozenset(), # Do not inherit predicates: Those might read variables # that may not yet be set, and we don't have a great way # of figuring out what the dependencies of the accumulator @@ -1177,7 +1178,7 @@ def map_reduction_local(red_realize_ctx, expr, nresults, arg_dtypes, red_realize_ctx.surrounding_within_inames | frozenset([base_exec_iname])), within_inames_is_final=True, - depends_on=frozenset(), + happens_after={}, predicates=red_realize_ctx.surrounding_predicates, ) red_realize_ctx.additional_insns.append(init_neutral_insn) @@ -1188,7 +1189,7 @@ def map_reduction_local(red_realize_ctx, expr, nresults, arg_dtypes, within_inames=( red_realize_ctx.surrounding_within_inames | frozenset([red_iname])), - depends_on=( + happens_after=( red_realize_ctx.surrounding_depends_on | frozenset([init_id, init_neutral_id])), no_sync_with=( @@ -1286,7 +1287,7 @@ def map_reduction_local(red_realize_ctx, expr, nresults, arg_dtypes, red_realize_ctx.surrounding_within_inames | frozenset([stage_exec_iname])), within_inames_is_final=True, - depends_on=frozenset([prev_id]), + happens_after=frozenset([prev_id]), predicates=red_realize_ctx.surrounding_predicates, ) @@ -1416,7 +1417,7 @@ def map_scan_seq(red_realize_ctx, expr, nresults, arg_dtypes, red_realize_ctx.surrounding_within_inames - frozenset((scan_param.sweep_iname,) + expr.inames)), within_inames_is_final=True, - depends_on=init_insn_depends_on, + happens_after=init_insn_depends_on, expression=expression, # Do not inherit predicates: Those might read variables # that may not yet be set, and we don't have a great way @@ -1436,7 +1437,7 @@ def map_scan_seq(red_realize_ctx, expr, nresults, arg_dtypes, within_inames=( red_realize_ctx.surrounding_within_inames | frozenset({scan_param.scan_iname})), - depends_on=red_realize_ctx.surrounding_depends_on) + happens_after=red_realize_ctx.surrounding_depends_on) reduction_expr = red_realize_ctx.mapper( expr.expr, red_realize_ctx=scan_red_realize_ctx, @@ -1466,7 +1467,7 @@ def map_scan_seq(red_realize_ctx, expr, nresults, arg_dtypes, | frozenset( scan_red_realize_ctx.surrounding_insn_add_within_inames) | {track_iname}), - depends_on=( + happens_after=( frozenset(scan_insn_depends_on) | frozenset(scan_red_realize_ctx.surrounding_insn_add_depends_on) ), @@ -1579,7 +1580,7 @@ def map_scan_local(red_realize_ctx, expr, nresults, arg_dtypes, expression=neutral, within_inames=base_iname_deps | frozenset([base_exec_iname]), within_inames_is_final=True, - depends_on=frozenset(), + happens_after=frozenset(), # Do not inherit predicates: Those might read variables # that may not yet be set, and we don't have a great way # of figuring out what the dependencies of the accumulator @@ -1599,7 +1600,7 @@ def map_scan_local(red_realize_ctx, expr, nresults, arg_dtypes, within_inames=( red_realize_ctx.surrounding_within_inames | frozenset({scan_param.scan_iname})), - depends_on=red_realize_ctx.surrounding_depends_on) + happens_after=red_realize_ctx.surrounding_depends_on) reduction_expr = red_realize_ctx.mapper( expr.expr, red_realize_ctx=transfer_red_realize_ctx, @@ -1643,7 +1644,7 @@ def map_scan_local(red_realize_ctx, expr, nresults, arg_dtypes, | transfer_red_realize_ctx.surrounding_insn_add_within_inames | frozenset({scan_param.sweep_iname})), within_inames_is_final=True, - depends_on=( + happens_after=( transfer_insn_depends_on | transfer_red_realize_ctx.surrounding_insn_add_depends_on), no_sync_with=( @@ -1684,7 +1685,7 @@ def map_scan_local(red_realize_ctx, expr, nresults, arg_dtypes, within_inames=( base_iname_deps | frozenset([stage_exec_iname])), within_inames_is_final=True, - depends_on=prev_ids, + happens_after=prev_ids, predicates=red_realize_ctx.surrounding_predicates, ) @@ -1722,7 +1723,7 @@ def map_scan_local(red_realize_ctx, expr, nresults, arg_dtypes, within_inames=( base_iname_deps | frozenset([stage_exec_iname])), within_inames_is_final=True, - depends_on=prev_ids, + happens_after=prev_ids, predicates=red_realize_ctx.surrounding_predicates, ) @@ -2014,18 +2015,6 @@ def realize_reduction_for_single_kernel(kernel, callables_table, kwargs.pop("id") kwargs.pop("happens_after") - - # - # TODO is this the best way of handling this? - # - try: - kwargs.pop("depends_on") - except KeyError: - from warnings import warn - warn("depends_on is deprecated and will no longer be used in " - "2024. Instead use happens_after", DeprecationWarning, - stacklevel=2) - kwargs.pop("expression") kwargs.pop("assignee", None) kwargs.pop("assignees", None) @@ -2040,7 +2029,7 @@ def realize_reduction_for_single_kernel(kernel, callables_table, replacement_insns = [ Assignment( id=result_assignment_ids[i], - depends_on=( + happens_after=( result_assignment_dep_on | (frozenset([result_assignment_ids[i-1]]) if i else frozenset())), @@ -2059,7 +2048,7 @@ def realize_reduction_for_single_kernel(kernel, callables_table, replacement_insns = [ make_assignment( id=insn.id, - depends_on=result_assignment_dep_on, + happens_after=result_assignment_dep_on, assignees=insn.assignees, expression=new_expr, **kwargs) @@ -2087,7 +2076,7 @@ def realize_reduction_for_single_kernel(kernel, callables_table, if global_barrier is not None: gb_dep = frozenset([global_barrier]) additional_insns = [addl_insn.copy( - depends_on=addl_insn.depends_on | gb_dep) + happens_after=addl_insn.depends_on | gb_dep) for addl_insn in additional_insns] # }}} diff --git a/loopy/transform/save.py b/loopy/transform/save.py index 6bf1c1543..813efd2b1 100644 --- a/loopy/transform/save.py +++ b/loopy/transform/save.py @@ -561,7 +561,7 @@ def add_subscript_if_subscript_nonempty(agg, subscript=()): self.subkernel_to_surrounding_inames[subkernel] | frozenset(hw_inames + dim_inames)), within_inames_is_final=True, - depends_on=depends_on) + happens_after=depends_on) if mode == "save": self.temporary_to_save_ids[temporary].add(save_or_load_insn_id) @@ -575,7 +575,7 @@ def add_subscript_if_subscript_nonempty(agg, subscript=()): for insn_id in update_deps: insn = self.insns_to_update.get(insn_id, self.kernel.id_to_insn[insn_id]) self.insns_to_update[insn_id] = insn.copy( - depends_on=insn.depends_on | frozenset([save_or_load_insn_id])) + happens_after=insn.depends_on | frozenset([save_or_load_insn_id])) self.updated_temporary_variables[promoted_temporary.name] = ( promoted_temporary.as_kernel_temporary(self.kernel)) diff --git a/test/test_loopy.py b/test/test_loopy.py index f9a6902ce..5859ca205 100644 --- a/test/test_loopy.py +++ b/test/test_loopy.py @@ -2027,7 +2027,7 @@ def test_unscheduled_insn_detection(): prog = lp.linearize(prog) insn1, = lp.find_instructions(prog, "id:insn1") insns = prog["loopy_kernel"].instructions[:] - insns.append(insn1.copy(id="insn2", depends_on=frozenset({"insn1"}))) + insns.append(insn1.copy(id="insn2", happens_after=frozenset({"insn1"}))) prog = prog.with_kernel(prog["loopy_kernel"].copy(instructions=insns)) from loopy.diagnostic import UnscheduledInstructionError From faa5f23a782a6caeda2ef82542381585d2bab6b0 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Thu, 23 Mar 2023 21:04:15 -0500 Subject: [PATCH 26/52] Remove HappensAfter import from where it never should have been --- loopy/kernel/creation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index d019469e7..e1eebbe6a 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -36,7 +36,6 @@ SubstitutionRule, AddressSpace, ValueArg) from loopy.translation_unit import for_each_kernel from loopy.diagnostic import LoopyError, warn_with_kernel -from loopy.kernel.instruction import HappensAfter import islpy as isl from islpy import dim_type from pytools import ProcessLogger From d3224d1f96ed590c0a1acb307c52b85de5144280 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Thu, 6 Apr 2023 11:47:51 -0500 Subject: [PATCH 27/52] Minor tweaks. Add a function to print dependency info of a knl --- .DS_Store | Bin 0 -> 10244 bytes doc/.DS_Store | Bin 0 -> 6148 bytes loopy/kernel/creation.py | 7 ++-- loopy/kernel/dependency.py | 64 +++++++++++++++++++++++++------------ 4 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 .DS_Store create mode 100644 doc/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3429492e992cfbd017ff9b6886a13504006a1972 GIT binary patch literal 10244 zcmeHMO=uHA6n@*JW@}Y~D0mQEs)$HqTeN>D7;E&R;!i{mHl#_DCQCQFA=xyRQiz3K zY{g!S2Yd7Cv5Hy{MG-7s6uc;S_Tt4uLG;b+B$-JzX}uH+GhycK&U6YR02%DGY_O#x7Pv6~jW_LB>g8k0dLOO8Yh7R7Nz z>9{H^#ZVk~?AMrGkjauG9d}Sz@}aQE3d>Lw8Xd>is5vM>N6J<*AQ`ACSzeoHqRpoHQR&<1#ds;u!4@KZeY}WcR&;#K z?}fXo^B-tTPG61crWfM=_=rP;uN)WBP=L4)9rpjKx8O0XEmY=RGfu{;B57_j(a4U+{Ie_2!k#JDF?qD>*$OzFkBJ9y>R^hDUh5Lihm2Uri;axvwa;1S3dXIm;Nw4wu0DkObp7hAsyG^3iFkq-3}=}@Jfrbq zKFi+X<#^W6^LBUlXQjpCZQbTmeQLWJ38$@M*pAtS{7@`+o7~;7EF)gNj>T{4$zs&M z=bUBQG2Kk&sDqwJqRH?r-Aq`;A*)~}^3*r%DAYE!E$ZJlHPzdHqEqV`=$Y=+rUnO3 zc53~7{nOKJ>fS?Lr>;lGjf`pCBXIHK%|7z@hKOYjZ`o{w)qHMWscLvOBhzcA&%9&1 z6=i{cmg=?d_& z@fe%g31}6%$F(9F3xBw;2#6wqZ-|J~@>s4d&ORM@PgxQys?VFdg%CfrgaKezH9qcc zN!;gC5Fl?KjDaN}0xi9sJ&&=Y^x);AHxy&j7(90y^%3R)BV-=!$Y2h6w6O*%JG8jU zO7uu$OCI}yOUo}9_6FnfsMog^@G4ITUT+zr!12MWy}XXcba`_A)9hQyXT_IC;iebj zb}W#%vPcFb1CjyBfMh^2u(cTQIB+#`{(rvl|NmQSQWC9XKr--83@F}EI5dPV<+Quj zo1C?4SVypuLhMS8q%PPH6VgwQV^stLv2bnB6lDeZGbw{~Le+IC#Oqu^X kXS(j$J)aG_TL}4JYKbgq)7)IhO=bSioh$HMZ%+RI7m<{!mH+?% literal 0 HcmV?d00001 diff --git a/doc/.DS_Store b/doc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..2368045ebaac5550311e75f96bef3d59cc932b44 GIT binary patch literal 6148 zcmeHKJ&zMH5FKAa&f$cV0>qV;u8l+@K0xbB?R4>)G z<+j|BFa0Au_ou;hmiL0mbNYIsbrPQSgYaoQoDUjTAL?ux#My9S0^(?hE-#+NS)}JZ zJ#{y*>@F6!J2#uEeYd@AsztYZtEoD7I?H8UUfa8||9J2=Ni+S?k|^-b zF>;0R434o=+EX%W`>%C6HV$7Not(F)y5HwJOLpbyhkM_?K3MmO{^&D4T3P4xJ1tkr z;H0) z`*I982L3AsM78htd$=WjTh}&6Z>>Uog-Swxg+&vB4L`@GLr?K8su8p)>L7ZIg+&}e QaX$iv2G=+S{wf1M0okyL!2kdN literal 0 HcmV?d00001 diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index e1eebbe6a..8f783c000 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -839,8 +839,7 @@ def intern_if_str(s): happens_after=( (insn.depends_on | insn_options_stack[-1]["happens_after"]) - if insn_options_stack[-1]["happens_after"] is \ - not None + if insn_options_stack[-1]["happens_after"] is not None else insn.happens_after), tags=( insn.tags @@ -1877,9 +1876,9 @@ def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True, - {insn.id}) # }}} - + happens_after = insn.happens_after - + if not isinstance(happens_after, frozenset): happens_after = frozenset(happens_after) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 618e55db7..a9f9331bb 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -66,7 +66,10 @@ def narrow_dependencies(knl: LoopKernel) -> LoopKernel: def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: """Equivalent to computing the relation R^{-1} o S """ - return s.apply_range(r.reverse()) + dependency = s.apply_range(r.reverse()) + + # do not return a map with self dependencies + return dependency - dependency.identity(dependency.get_space()) def make_inames_unique(relation: isl.Map) -> isl.Map: """Make the inames of a particular map unique by adding a single quote @@ -113,13 +116,10 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: get_access_map(write_domain, sub.index_tuple) ) - assert len(read_maps) > 0 - assert len(write_maps) > 0 - read_map = reduce(operator.or_, read_maps) write_map = reduce(operator.or_, write_maps) - read_write = get_unordered_deps(read_map, write_map) + read_write = get_unordered_deps(write_map, read_map) if writer in insn.happens_after: lex_map = insn.happens_after[writer].instances_rel @@ -140,10 +140,10 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: old_rel = new_happens_after[writer].instances_rel flow_dependencies |= old_rel - new_happens_after |= { + new_happens_after.update({ writer: HappensAfter(variable, flow_dependencies) - } + }) # compute anti and output dependencies for variable in writes[insn.id]: @@ -175,13 +175,10 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: get_access_map(write_domain, sub.index_tuple) ) - assert len(read_maps) > 0 - assert len(write_maps) > 0 - read_map = reduce(operator.or_, read_maps) write_map = reduce(operator.or_, write_maps) - write_read = get_unordered_deps(write_map, read_map) + write_read = get_unordered_deps(read_map, write_map) if reader in insn.happens_after: lex_map = insn.happens_after[reader].instances_rel @@ -203,9 +200,9 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: anti_dependencies |= old_rel - new_happens_after |= { + new_happens_after.update({ reader: HappensAfter(variable, anti_dependencies) - } + }) # compute output dependencies for writer in writer_map.get(variable, set()): @@ -236,13 +233,10 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: get_access_map(after_domain, sub.index_tuple) ) - assert len(before_maps) > 0 - assert len(after_maps) > 0 - before_map = reduce(operator.or_, before_maps) after_map = reduce(operator.or_, before_maps) - write_write = get_unordered_deps(after_map, before_map) + write_write = get_unordered_deps(before_map, after_map) if writer in insn.happens_after: lex_map = insn.happens_after[writer].instances_rel @@ -261,14 +255,14 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: output_dependencies = write_write & lex_map if not output_dependencies.is_empty(): - if writer in new_happens_after[writer]: + if writer in new_happens_after: old_rel = new_happens_after[writer].instances_rel output_dependencies |= old_rel - new_happens_after |= { + new_happens_after.update({ writer: HappensAfter(variable, output_dependencies) - } + }) new_insns.append(insn.copy(happens_after=new_happens_after)) @@ -360,4 +354,34 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: return knl.copy(instructions=new_insns) + +def print_dependency_info(knl: LoopKernel) -> None: + + dependencies = [] + for insn in knl.instructions: + dep_string = f"{insn.id} depends on " + + if not insn.happens_after: + dep_string += "nothing" + + else: + for dep in insn.happens_after: + + dep_string += f"{dep} " + + if insn.happens_after[dep].variable_name is None: + dep_string += "" + + else: + dep_string += "at variable " + dep_string += f"'{insn.happens_after[dep].variable_name}' " + + dep_string += "with relation \n" + dep_string += f"{insn.happens_after[dep].instances_rel}\n" + + dependencies.append(dep_string) + + for s in dependencies: + print(s) + # vim: foldmethod=marker From a2155119b8e4d4715159e61d0756785f7dccbc41 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 10 Apr 2023 13:24:28 -0500 Subject: [PATCH 28/52] Refactored code. Much shorter with better access map finding. --- .DS_Store | Bin 10244 -> 0 bytes doc/.DS_Store | Bin 6148 -> 0 bytes loopy/kernel/dependency.py | 316 ++++++++++++++----------------------- 3 files changed, 117 insertions(+), 199 deletions(-) delete mode 100644 .DS_Store delete mode 100644 doc/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 3429492e992cfbd017ff9b6886a13504006a1972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMO=uHA6n@*JW@}Y~D0mQEs)$HqTeN>D7;E&R;!i{mHl#_DCQCQFA=xyRQiz3K zY{g!S2Yd7Cv5Hy{MG-7s6uc;S_Tt4uLG;b+B$-JzX}uH+GhycK&U6YR02%DGY_O#x7Pv6~jW_LB>g8k0dLOO8Yh7R7Nz z>9{H^#ZVk~?AMrGkjauG9d}Sz@}aQE3d>Lw8Xd>is5vM>N6J<*AQ`ACSzeoHqRpoHQR&<1#ds;u!4@KZeY}WcR&;#K z?}fXo^B-tTPG61crWfM=_=rP;uN)WBP=L4)9rpjKx8O0XEmY=RGfu{;B57_j(a4U+{Ie_2!k#JDF?qD>*$OzFkBJ9y>R^hDUh5Lihm2Uri;axvwa;1S3dXIm;Nw4wu0DkObp7hAsyG^3iFkq-3}=}@Jfrbq zKFi+X<#^W6^LBUlXQjpCZQbTmeQLWJ38$@M*pAtS{7@`+o7~;7EF)gNj>T{4$zs&M z=bUBQG2Kk&sDqwJqRH?r-Aq`;A*)~}^3*r%DAYE!E$ZJlHPzdHqEqV`=$Y=+rUnO3 zc53~7{nOKJ>fS?Lr>;lGjf`pCBXIHK%|7z@hKOYjZ`o{w)qHMWscLvOBhzcA&%9&1 z6=i{cmg=?d_& z@fe%g31}6%$F(9F3xBw;2#6wqZ-|J~@>s4d&ORM@PgxQys?VFdg%CfrgaKezH9qcc zN!;gC5Fl?KjDaN}0xi9sJ&&=Y^x);AHxy&j7(90y^%3R)BV-=!$Y2h6w6O*%JG8jU zO7uu$OCI}yOUo}9_6FnfsMog^@G4ITUT+zr!12MWy}XXcba`_A)9hQyXT_IC;iebj zb}W#%vPcFb1CjyBfMh^2u(cTQIB+#`{(rvl|NmQSQWC9XKr--83@F}EI5dPV<+Quj zo1C?4SVypuLhMS8q%PPH6VgwQV^stLv2bnB6lDeZGbw{~Le+IC#Oqu^X kXS(j$J)aG_TL}4JYKbgq)7)IhO=bSioh$HMZ%+RI7m<{!mH+?% diff --git a/doc/.DS_Store b/doc/.DS_Store deleted file mode 100644 index 2368045ebaac5550311e75f96bef3d59cc932b44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ&zMH5FKAa&f$cV0>qV;u8l+@K0xbB?R4>)G z<+j|BFa0Au_ou;hmiL0mbNYIsbrPQSgYaoQoDUjTAL?ux#My9S0^(?hE-#+NS)}JZ zJ#{y*>@F6!J2#uEeYd@AsztYZtEoD7I?H8UUfa8||9J2=Ni+S?k|^-b zF>;0R434o=+EX%W`>%C6HV$7Not(F)y5HwJOLpbyhkM_?K3MmO{^&D4T3P4xJ1tkr z;H0) z`*I982L3AsM78htd$=WjTh}&6Z>>Uog-Swxg+&vB4L`@GLr?K8su8p)>L7ZIg+&}e QaX$iv2G=+S{wf1M0okyL!2kdN diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index a9f9331bb..cc64d360e 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,251 +20,168 @@ THE SOFTWARE. """ +# TODO +# 1. Array access discovery needs work +# 2. Can we reduce the length of the code without making things more +# complicated +# +# Problem with BatchedAccessMapMapper is that it we cannot distinguish between +# accesses of particular instructions. +# +# Problem with AccessMapMapper is that it misses important array access +# information. Most importantly, it did not detect when an instruction +# accessed an array multiple times in the same statement +# +# For example a[i,j] = a[i+1,j+1] - a[i-1,j-1] +# +# Probably an implementation that is "in the middle" would work for this case + from typing import Mapping import islpy as isl from islpy import dim_type +from collections import defaultdict + from loopy import LoopKernel from loopy.kernel.instruction import HappensAfter -from loopy.symbolic import ArrayAccessFinder +from loopy.symbolic import UncachedWalkMapper as WalkMapper from loopy.translation_unit import for_each_kernel from loopy.symbolic import get_access_map -import operator -from functools import reduce +import pymbolic.primitives as p -@for_each_kernel -def narrow_dependencies(knl: LoopKernel) -> LoopKernel: - """Compute statement-instance-level data dependencies between statements in - a program. Relies on an existing lexicographic ordering, i.e. computed by - add_lexicographic_happens_after. - - In particular, this function computes three dependency relations: - 1. The flow dependency relation, aka read-after-write - 2. The anti-dependecny relation, aka write-after-reads - 3. The output dependency relation, aka write-after-write - - The full dependency relation between a two statements in a program is the - union of these three dependency relations. +class AccessMapFinder(WalkMapper): + """Finds and stores relations representing the accesses of an array by + statement instances. Access maps can be found using an instruction's ID and + a variable's name. Essentially a specialized version of + BatchedAccessMapMapper. """ - writer_map = knl.writer_map() - reader_map = knl.reader_map() - - reads = { - insn.id: insn.read_dependency_names() - insn.within_inames - for insn in knl.instructions - } - - writes = { - insn.id: insn.write_dependency_names() - insn.within_inames - for insn in knl.instructions - } - - def get_unordered_deps(r: isl.Map, s: isl.Map) -> isl.Map: - """Equivalent to computing the relation R^{-1} o S - """ - dependency = s.apply_range(r.reverse()) - - # do not return a map with self dependencies - return dependency - dependency.identity(dependency.get_space()) - - def make_inames_unique(relation: isl.Map) -> isl.Map: - """Make the inames of a particular map unique by adding a single quote - to each iname in the range of the map - """ - ndim = relation.dim(dim_type.out) - for i in range(ndim): - iname = relation.get_dim_name(dim_type.out, i) + "'" - relation = relation.set_dim_name(dim_type.out, i, iname) - - return relation - - access_finder = ArrayAccessFinder() - new_insns = [] - for insn in knl.instructions: - - new_happens_after: Mapping[str, HappensAfter] = {} - - # compute flow dependencies - for variable in reads[insn.id]: - for writer in writer_map.get(variable, set()): - write_insn = knl.id_to_insn[writer] + def __init__(self, knl: LoopKernel) -> None: + self.kernel = knl + self.access_maps = defaultdict(lambda: defaultdict(lambda: None)) - read_domain = knl.get_inames_domain(insn.within_inames) - write_domain = knl.get_inames_domain(write_insn.within_inames) + super().__init__() - read_subscripts = access_finder(insn.expression) - write_subscripts = access_finder(write_insn.assignee) - - assert isinstance(read_subscripts, set) - assert isinstance(write_subscripts, set) - - read_maps = [] - for sub in read_subscripts: - if sub.aggregate.name == variable: - read_maps.append( - get_access_map(read_domain, sub.index_tuple) - ) - - write_maps = [] - for sub in write_subscripts: - if sub.aggregate.name == variable: - write_maps.append( - get_access_map(write_domain, sub.index_tuple) - ) - - read_map = reduce(operator.or_, read_maps) - write_map = reduce(operator.or_, write_maps) - - read_write = get_unordered_deps(write_map, read_map) - - if writer in insn.happens_after: - lex_map = insn.happens_after[writer].instances_rel - - else: - lex_map = read_write.lex_lt_map(read_write) + def get_map(self, insn_id: str, variable_name: str) -> isl.Map: + try: + return self.access_maps[insn_id][variable_name] + except KeyError: + raise RuntimeError("error while trying to retrieve an access map " + "for instruction %s and array %s" + % (insn_id, variable_name)) - if lex_map.space != read_write.space: - lex_map = make_inames_unique(lex_map) - read_write = make_inames_unique(read_write) + def map_subscript(self, expr, insn): + domain = self.kernel.get_inames_domain(insn.within_inames) + WalkMapper.map_subscript(self, expr, insn) - lex_map, read_write = isl.align_two(lex_map, read_write) + assert isinstance(expr.aggregate, p.Variable) - flow_dependencies = read_write & lex_map + arg_name = expr.aggregate.name + subscript = expr.index_tuple - if not flow_dependencies.is_empty(): - if writer in new_happens_after: - old_rel = new_happens_after[writer].instances_rel - flow_dependencies |= old_rel + access_map = get_access_map( + domain, subscript, self.kernel.assumptions + ) - new_happens_after.update({ - writer: HappensAfter(variable, - flow_dependencies) - }) - - # compute anti and output dependencies - for variable in writes[insn.id]: - - # compute anti dependencies - for reader in reader_map.get(variable, set()): - read_insn = knl.id_to_insn[reader] - - read_domain = knl.get_inames_domain(read_insn.within_inames) - write_domain = knl.get_inames_domain(insn.within_inames) - - read_subscripts = access_finder(read_insn.expression) - write_subscripts = access_finder(insn.assignee) - - assert isinstance(read_subscripts, set) - assert isinstance(write_subscripts, set) - - read_maps = [] - for sub in read_subscripts: - if sub.aggregate.name == variable: - read_maps.append( - get_access_map(read_domain, sub.index_tuple) - ) - - write_maps = [] - for sub in write_subscripts: - if sub.aggregate.name == variable: - write_maps.append( - get_access_map(write_domain, sub.index_tuple) - ) - - read_map = reduce(operator.or_, read_maps) - write_map = reduce(operator.or_, write_maps) - - write_read = get_unordered_deps(read_map, write_map) + if self.access_maps[insn.id][arg_name]: + self.access_maps[insn.id][arg_name] |= access_map + else: + self.access_maps[insn.id][arg_name] = access_map - if reader in insn.happens_after: - lex_map = insn.happens_after[reader].instances_rel + def map_linear_subscript(self, expr, insn): + self.rec(expr.index, insn) - else: - lex_map = write_read.lex_lt_map(write_read) + def map_reduction(self, expr, insn): + return WalkMapper.map_reduction(self, expr, insn) - if lex_map.space != write_read.space: - lex_map = make_inames_unique(lex_map) - write_read = make_inames_unique(lex_map) + def map_type_cast(self, expr, inames): + return self.rec(expr.child, inames) - lex_map, write_read = isl.align_two(lex_map, write_read) + # TODO implement this + def map_sub_array_ref(self, expr, insn): + raise NotImplementedError("functionality for sub-array reference " + "access map finding has not yet been " + "implemented") - anti_dependencies = write_read & lex_map - if not anti_dependencies.is_empty(): - if reader in new_happens_after: - old_rel = new_happens_after[reader].instances_rel +@for_each_kernel +def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: + """Compute data dependencies between statements in a kernel. Can utilize an + existing lexicographic ordering, i.e. one generated by + `add_lexicographic_happens_after`. + """ - anti_dependencies |= old_rel + def get_unordered_deps(frm: isl.Map, to: isl.Map) -> isl.Map: + # equivalent to R^{-1} o S + dependency = frm.apply_range(to.reverse()) - new_happens_after.update({ - reader: HappensAfter(variable, anti_dependencies) - }) - - # compute output dependencies - for writer in writer_map.get(variable, set()): - before_write = knl.id_to_insn[writer] + return dependency - dependency.identity(dependency.get_space()) - before_domain = knl.get_inames_domain( - before_write.within_inames - ) - after_domain = knl.get_inames_domain(insn.within_inames) + def make_inames_unique(relation: isl.Map) -> isl.Map: + """Add a single quote to the output inames of an isl.Map + """ + for idim in range(relation.dim(dim_type.out)): + iname = relation.get_dim_name(dim_type.out, idim) + "'" + relation = relation.set_dim_name(dim_type.out, idim, iname) - before_subscripts = access_finder(before_write.assignee) - after_subscripts = access_finder(insn.assignee) + return relation - assert isinstance(before_subscripts, set) - assert isinstance(after_subscripts, set) + writer_map = knl.writer_map() + reader_map = knl.reader_map() - before_maps = [] - for sub in before_subscripts: - if sub.aggregate.name == variable: - before_maps.append( - get_access_map(before_domain, sub.index_tuple) - ) + # consider all accesses since we union all dependencies anyway + accesses = { + insn.id: (insn.read_dependency_names() | + insn.write_dependency_names()) - insn.within_inames + for insn in knl.instructions + } - after_maps = [] - for sub in after_subscripts: - if sub.aggregate.name == variable: - after_maps.append( - get_access_map(after_domain, sub.index_tuple) - ) + amf = AccessMapFinder(knl) + for insn in knl.instructions: + amf(insn.assignee, insn) + amf(insn.expression, insn) - before_map = reduce(operator.or_, before_maps) - after_map = reduce(operator.or_, before_maps) + new_insns = [] + for before_insn in knl.instructions: + new_happens_after: Mapping[str, HappensAfter] = {} - write_write = get_unordered_deps(before_map, after_map) + assert isinstance(before_insn.id, str) # stop complaints - if writer in insn.happens_after: - lex_map = insn.happens_after[writer].instances_rel + for variable in accesses[before_insn.id]: + # get all instruction ids that also access the current variable + accessed_by = reader_map.get(variable, set()) | \ + writer_map.get(variable, set()) - else: - lex_map = write_write.lex_lt_map(write_write) + # dependency computation + for after_insn in accessed_by: + before_map = amf.get_map(before_insn.id, variable) + after_map = amf.get_map(after_insn, variable) - if lex_map.space != write_write.space: - lex_map = make_inames_unique(lex_map) - write_write = make_inames_unique(write_write) + unordered_deps = get_unordered_deps(before_map, after_map) - lex_map, write_write = isl.align_two( - lex_map, write_write - ) + # may not permanently construct lex maps this way + lex_map = unordered_deps.lex_lt_map(unordered_deps) - output_dependencies = write_write & lex_map + # may not be needed if we can resolve issues above + if lex_map.space != unordered_deps.space: + lex_map = make_inames_unique(lex_map) + unordered_deps = make_inames_unique(unordered_deps) - if not output_dependencies.is_empty(): - if writer in new_happens_after: - old_rel = new_happens_after[writer].instances_rel + lex_map, unordered_deps = isl.align_two( + lex_map, unordered_deps + ) - output_dependencies |= old_rel + deps = unordered_deps & lex_map + if not deps.is_empty(): new_happens_after.update({ - writer: HappensAfter(variable, output_dependencies) + after_insn: HappensAfter(variable, deps) }) - new_insns.append(insn.copy(happens_after=new_happens_after)) + new_insns.append(before_insn.copy(happens_after=new_happens_after)) return knl.copy(instructions=new_insns) @@ -355,11 +272,12 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: return knl.copy(instructions=new_insns) +@for_each_kernel def print_dependency_info(knl: LoopKernel) -> None: dependencies = [] for insn in knl.instructions: - dep_string = f"{insn.id} depends on " + dep_string = f"{insn.id} depends on \n" if not insn.happens_after: dep_string += "nothing" From 72ab7e700deabd3c3f5e45211d13eef176859007 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 10 Apr 2023 15:41:21 -0500 Subject: [PATCH 29/52] Remove giant todo list from top of file, change printing code --- loopy/kernel/dependency.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index cc64d360e..2535602a5 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -20,22 +20,6 @@ THE SOFTWARE. """ -# TODO -# 1. Array access discovery needs work -# 2. Can we reduce the length of the code without making things more -# complicated -# -# Problem with BatchedAccessMapMapper is that it we cannot distinguish between -# accesses of particular instructions. -# -# Problem with AccessMapMapper is that it misses important array access -# information. Most importantly, it did not detect when an instruction -# accessed an array multiple times in the same statement -# -# For example a[i,j] = a[i+1,j+1] - a[i-1,j-1] -# -# Probably an implementation that is "in the middle" would work for this case - from typing import Mapping import islpy as isl @@ -295,8 +279,9 @@ def print_dependency_info(knl: LoopKernel) -> None: dep_string += f"'{insn.happens_after[dep].variable_name}' " dep_string += "with relation \n" - dep_string += f"{insn.happens_after[dep].instances_rel}\n" + dep_string += f"{insn.happens_after[dep].instances_rel}\n\n" + dep_string += ((80*"-") + "\n") dependencies.append(dep_string) for s in dependencies: From 590a8cf280431881199594b833572e6610bdf1e3 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 10 Apr 2023 16:06:25 -0500 Subject: [PATCH 30/52] Minor grammatical fix --- loopy/kernel/dependency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 2535602a5..6e944ebb3 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -37,7 +37,7 @@ class AccessMapFinder(WalkMapper): - """Finds and stores relations representing the accesses of an array by + """Finds and stores relations representing the accesses to an array by statement instances. Access maps can be found using an instruction's ID and a variable's name. Essentially a specialized version of BatchedAccessMapMapper. From 5208fb6ea5cf379187f7898a3bd8c49fe84dc138 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 10 Apr 2023 16:16:10 -0500 Subject: [PATCH 31/52] Minor print function change --- loopy/kernel/dependency.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 6e944ebb3..7b9e1c180 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -271,10 +271,7 @@ def print_dependency_info(knl: LoopKernel) -> None: dep_string += f"{dep} " - if insn.happens_after[dep].variable_name is None: - dep_string += "" - - else: + if insn.happens_after[dep].variable_name is not None: dep_string += "at variable " dep_string += f"'{insn.happens_after[dep].variable_name}' " From cf4b9bdeb3a171028402562a3fffefc78662ae12 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Tue, 11 Apr 2023 08:22:25 -0500 Subject: [PATCH 32/52] Implement map_sub_array_ref in AccessMapFinder --- .DS_Store | Bin 0 -> 10244 bytes doc/.DS_Store | Bin 0 -> 6148 bytes loopy/kernel/dependency.py | 27 +++++++++++++++++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 .DS_Store create mode 100644 doc/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3429492e992cfbd017ff9b6886a13504006a1972 GIT binary patch literal 10244 zcmeHMO=uHA6n@*JW@}Y~D0mQEs)$HqTeN>D7;E&R;!i{mHl#_DCQCQFA=xyRQiz3K zY{g!S2Yd7Cv5Hy{MG-7s6uc;S_Tt4uLG;b+B$-JzX}uH+GhycK&U6YR02%DGY_O#x7Pv6~jW_LB>g8k0dLOO8Yh7R7Nz z>9{H^#ZVk~?AMrGkjauG9d}Sz@}aQE3d>Lw8Xd>is5vM>N6J<*AQ`ACSzeoHqRpoHQR&<1#ds;u!4@KZeY}WcR&;#K z?}fXo^B-tTPG61crWfM=_=rP;uN)WBP=L4)9rpjKx8O0XEmY=RGfu{;B57_j(a4U+{Ie_2!k#JDF?qD>*$OzFkBJ9y>R^hDUh5Lihm2Uri;axvwa;1S3dXIm;Nw4wu0DkObp7hAsyG^3iFkq-3}=}@Jfrbq zKFi+X<#^W6^LBUlXQjpCZQbTmeQLWJ38$@M*pAtS{7@`+o7~;7EF)gNj>T{4$zs&M z=bUBQG2Kk&sDqwJqRH?r-Aq`;A*)~}^3*r%DAYE!E$ZJlHPzdHqEqV`=$Y=+rUnO3 zc53~7{nOKJ>fS?Lr>;lGjf`pCBXIHK%|7z@hKOYjZ`o{w)qHMWscLvOBhzcA&%9&1 z6=i{cmg=?d_& z@fe%g31}6%$F(9F3xBw;2#6wqZ-|J~@>s4d&ORM@PgxQys?VFdg%CfrgaKezH9qcc zN!;gC5Fl?KjDaN}0xi9sJ&&=Y^x);AHxy&j7(90y^%3R)BV-=!$Y2h6w6O*%JG8jU zO7uu$OCI}yOUo}9_6FnfsMog^@G4ITUT+zr!12MWy}XXcba`_A)9hQyXT_IC;iebj zb}W#%vPcFb1CjyBfMh^2u(cTQIB+#`{(rvl|NmQSQWC9XKr--83@F}EI5dPV<+Quj zo1C?4SVypuLhMS8q%PPH6VgwQV^stLv2bnB6lDeZGbw{~Le+IC#Oqu^X kXS(j$J)aG_TL}4JYKbgq)7)IhO=bSioh$HMZ%+RI7m<{!mH+?% literal 0 HcmV?d00001 diff --git a/doc/.DS_Store b/doc/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..2368045ebaac5550311e75f96bef3d59cc932b44 GIT binary patch literal 6148 zcmeHKJ&zMH5FKAa&f$cV0>qV;u8l+@K0xbB?R4>)G z<+j|BFa0Au_ou;hmiL0mbNYIsbrPQSgYaoQoDUjTAL?ux#My9S0^(?hE-#+NS)}JZ zJ#{y*>@F6!J2#uEeYd@AsztYZtEoD7I?H8UUfa8||9J2=Ni+S?k|^-b zF>;0R434o=+EX%W`>%C6HV$7Not(F)y5HwJOLpbyhkM_?K3MmO{^&D4T3P4xJ1tkr z;H0) z`*I982L3AsM78htd$=WjTh}&6Z>>Uog-Swxg+&vB4L`@GLr?K8su8p)>L7ZIg+&}e QaX$iv2G=+S{wf1M0okyL!2kdN literal 0 HcmV?d00001 diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 7b9e1c180..72566ec5e 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -70,13 +70,16 @@ def map_subscript(self, expr, insn): domain, subscript, self.kernel.assumptions ) - if self.access_maps[insn.id][arg_name]: + if self.access_maps[insn.id][arg_name] is not None: self.access_maps[insn.id][arg_name] |= access_map else: self.access_maps[insn.id][arg_name] = access_map def map_linear_subscript(self, expr, insn): - self.rec(expr.index, insn) + raise NotImplementedError("linear subscripts cannot be used with " + "precise dependency finding. Try using " + "multidimensional accesses to use this " + "feature.") def map_reduction(self, expr, insn): return WalkMapper.map_reduction(self, expr, insn) @@ -84,11 +87,23 @@ def map_reduction(self, expr, insn): def map_type_cast(self, expr, inames): return self.rec(expr.child, inames) - # TODO implement this def map_sub_array_ref(self, expr, insn): - raise NotImplementedError("functionality for sub-array reference " - "access map finding has not yet been " - "implemented") + arg_name = expr.aggregate.name + + total_inames = insn.inames | {iname.name for iname in expr.swept_inames} + + self.rec(expr.subscript, insn) + + amap = self.access_maps[arg_name].pop(total_inames) + assert amap is not None # stop complaints + for iname in expr.swept_inames: + dt, pos = amap.get_var_dict()[iname.name] + amap = amap.project_out(dt, pos, 1) + + if self.access_maps[arg_name][insn.id] is not None: + self.access_maps[arg_name][insn.id] |= amap + else: + self.access_maps[arg_name][insn.id] = amap @for_each_kernel From faff25c5578510c21965e55108af96776af8cfb6 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Tue, 11 Apr 2023 08:39:18 -0500 Subject: [PATCH 33/52] Read-after-read dependency finding avoided --- loopy/kernel/dependency.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 72566ec5e..b1430cb0f 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -45,17 +45,15 @@ class AccessMapFinder(WalkMapper): def __init__(self, knl: LoopKernel) -> None: self.kernel = knl - self.access_maps = defaultdict(lambda: defaultdict(lambda: None)) + self._access_maps = defaultdict(lambda: defaultdict(lambda: None)) super().__init__() def get_map(self, insn_id: str, variable_name: str) -> isl.Map: - try: - return self.access_maps[insn_id][variable_name] - except KeyError: - raise RuntimeError("error while trying to retrieve an access map " - "for instruction %s and array %s" - % (insn_id, variable_name)) + """Retrieve an access map indexed by an instruction ID and variable + name. + """ + return self._access_maps[insn_id][variable_name] def map_subscript(self, expr, insn): domain = self.kernel.get_inames_domain(insn.within_inames) @@ -70,10 +68,10 @@ def map_subscript(self, expr, insn): domain, subscript, self.kernel.assumptions ) - if self.access_maps[insn.id][arg_name] is not None: - self.access_maps[insn.id][arg_name] |= access_map + if self._access_maps[insn.id][arg_name] is not None: + self._access_maps[insn.id][arg_name] |= access_map else: - self.access_maps[insn.id][arg_name] = access_map + self._access_maps[insn.id][arg_name] = access_map def map_linear_subscript(self, expr, insn): raise NotImplementedError("linear subscripts cannot be used with " @@ -94,16 +92,16 @@ def map_sub_array_ref(self, expr, insn): self.rec(expr.subscript, insn) - amap = self.access_maps[arg_name].pop(total_inames) + amap = self._access_maps[arg_name].pop(total_inames) assert amap is not None # stop complaints for iname in expr.swept_inames: dt, pos = amap.get_var_dict()[iname.name] amap = amap.project_out(dt, pos, 1) - if self.access_maps[arg_name][insn.id] is not None: - self.access_maps[arg_name][insn.id] |= amap + if self._access_maps[arg_name][insn.id] is not None: + self._access_maps[arg_name][insn.id] |= amap else: - self.access_maps[arg_name][insn.id] = amap + self._access_maps[arg_name][insn.id] = amap @for_each_kernel @@ -156,6 +154,11 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: # dependency computation for after_insn in accessed_by: + # avoid read-after-read + reads = reader_map.get(variable, set()) + if before_insn in reads and after_insn in reads: + continue + before_map = amf.get_map(before_insn.id, variable) after_map = amf.get_map(after_insn, variable) @@ -279,7 +282,7 @@ def print_dependency_info(knl: LoopKernel) -> None: dep_string = f"{insn.id} depends on \n" if not insn.happens_after: - dep_string += "nothing" + dep_string += "nothing\n" else: for dep in insn.happens_after: From d96794912d766b689a626d0193ac1198f733ec6e Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Tue, 11 Apr 2023 21:15:58 -0500 Subject: [PATCH 34/52] Change back to long version of dependency finding. Switch to using pmap --- loopy/kernel/dependency.py | 140 +++++++++++++++++++++++++----------- loopy/kernel/instruction.py | 7 +- 2 files changed, 104 insertions(+), 43 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index b1430cb0f..83d07b294 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -25,8 +25,6 @@ import islpy as isl from islpy import dim_type -from collections import defaultdict - from loopy import LoopKernel from loopy.kernel.instruction import HappensAfter from loopy.symbolic import UncachedWalkMapper as WalkMapper @@ -35,6 +33,8 @@ import pymbolic.primitives as p +from pyrsistent import pmap + class AccessMapFinder(WalkMapper): """Finds and stores relations representing the accesses to an array by @@ -45,7 +45,7 @@ class AccessMapFinder(WalkMapper): def __init__(self, knl: LoopKernel) -> None: self.kernel = knl - self._access_maps = defaultdict(lambda: defaultdict(lambda: None)) + self._access_maps = pmap({}) super().__init__() @@ -68,40 +68,39 @@ def map_subscript(self, expr, insn): domain, subscript, self.kernel.assumptions ) - if self._access_maps[insn.id][arg_name] is not None: - self._access_maps[insn.id][arg_name] |= access_map + # analyze what we have in our access map dict before storing map + insn_to_args = self._access_maps.get(insn.id) + if insn_to_args is not None: + existing_relation = insn_to_args.get(arg_name) + + if existing_relation is not None: + access_map |= existing_relation + + self._access_maps = self._access_maps.set( + insn.id, self._access_maps[insn.id].set( + arg_name, access_map + ) + ) + else: - self._access_maps[insn.id][arg_name] = access_map + self._access_maps = self._access_maps.set( + insn.id, pmap({arg_name: access_map}) + ) def map_linear_subscript(self, expr, insn): raise NotImplementedError("linear subscripts cannot be used with " - "precise dependency finding. Try using " + "precise dependency finding. Use " "multidimensional accesses to use this " "feature.") def map_reduction(self, expr, insn): return WalkMapper.map_reduction(self, expr, insn) - def map_type_cast(self, expr, inames): - return self.rec(expr.child, inames) + def map_type_cast(self, expr, insn): + return self.rec(expr.child, insn) def map_sub_array_ref(self, expr, insn): - arg_name = expr.aggregate.name - - total_inames = insn.inames | {iname.name for iname in expr.swept_inames} - - self.rec(expr.subscript, insn) - - amap = self._access_maps[arg_name].pop(total_inames) - assert amap is not None # stop complaints - for iname in expr.swept_inames: - dt, pos = amap.get_var_dict()[iname.name] - amap = amap.project_out(dt, pos, 1) - - if self._access_maps[arg_name][insn.id] is not None: - self._access_maps[arg_name][insn.id] |= amap - else: - self._access_maps[arg_name][insn.id] = amap + pass @for_each_kernel @@ -115,7 +114,12 @@ def get_unordered_deps(frm: isl.Map, to: isl.Map) -> isl.Map: # equivalent to R^{-1} o S dependency = frm.apply_range(to.reverse()) - return dependency - dependency.identity(dependency.get_space()) + if dependency.dim(dim_type.out) == dependency.dim(dim_type.in_): + identity = dependency.identity(dependency.get_space()) + else: + raise RuntimeError("input and output dimensions do not match") + + return dependency - identity def make_inames_unique(relation: isl.Map) -> isl.Map: """Add a single quote to the output inames of an isl.Map @@ -129,10 +133,13 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: writer_map = knl.writer_map() reader_map = knl.reader_map() - # consider all accesses since we union all dependencies anyway - accesses = { - insn.id: (insn.read_dependency_names() | - insn.write_dependency_names()) - insn.within_inames + writes = { + insn.id: insn.write_dependency_names() - insn.within_inames + for insn in knl.instructions + } + + reads = { + insn.id: insn.read_dependency_names() - insn.within_inames for insn in knl.instructions } @@ -147,17 +154,66 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: assert isinstance(before_insn.id, str) # stop complaints - for variable in accesses[before_insn.id]: - # get all instruction ids that also access the current variable - accessed_by = reader_map.get(variable, set()) | \ - writer_map.get(variable, set()) + for variable in writes[before_insn.id]: + + # dependency computation + for after_insn in reader_map.get(variable, set()): + + before_map = amf.get_map(before_insn.id, variable) + after_map = amf.get_map(after_insn, variable) + + unordered_deps = get_unordered_deps(before_map, after_map) + + # may not permanently construct lex maps this way + lex_map = unordered_deps.lex_lt_map(unordered_deps) + + # may not be needed if we can resolve issues above + if lex_map.space != unordered_deps.space: + lex_map = make_inames_unique(lex_map) + unordered_deps = make_inames_unique(unordered_deps) + + lex_map, unordered_deps = isl.align_two( + lex_map, unordered_deps + ) + + deps = unordered_deps & lex_map + + if not deps.is_empty(): + new_happens_after.update({ + after_insn: HappensAfter(variable, deps) + }) + + # dependency computation + for after_insn in writer_map.get(variable, set()): + + before_map = amf.get_map(before_insn.id, variable) + after_map = amf.get_map(after_insn, variable) + + unordered_deps = get_unordered_deps(before_map, after_map) + + # may not permanently construct lex maps this way + lex_map = unordered_deps.lex_lt_map(unordered_deps) + + # may not be needed if we can resolve issues above + if lex_map.space != unordered_deps.space: + lex_map = make_inames_unique(lex_map) + unordered_deps = make_inames_unique(unordered_deps) + + lex_map, unordered_deps = isl.align_two( + lex_map, unordered_deps + ) + + deps = unordered_deps & lex_map + + if not deps.is_empty(): + new_happens_after.update({ + after_insn: HappensAfter(variable, deps) + }) + + for variable in reads[before_insn.id]: # dependency computation - for after_insn in accessed_by: - # avoid read-after-read - reads = reader_map.get(variable, set()) - if before_insn in reads and after_insn in reads: - continue + for after_insn in writer_map.get(variable, set()): before_map = amf.get_map(before_insn.id, variable) after_map = amf.get_map(after_insn, variable) @@ -183,7 +239,9 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: after_insn: HappensAfter(variable, deps) }) - new_insns.append(before_insn.copy(happens_after=new_happens_after)) + new_insns.append(before_insn.copy( + happens_after=pmap(new_happens_after) + )) return knl.copy(instructions=new_insns) @@ -267,7 +325,7 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: insn_before.id: HappensAfter(None, happens_before) } - insn_after = insn_after.copy(happens_after=new_happens_after) + insn_after = insn_after.copy(happens_after=pmap(new_happens_after)) new_insns.append(insn_after) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 57e6a3b9e..21ff9cb4e 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -32,6 +32,8 @@ from pytools import ImmutableRecord, memoize_method from pytools.tag import Tag, tag_dataclass, Taggable +import pyrsistent as ps + from loopy.diagnostic import LoopyError from loopy.tools import Optional as LoopyOptional from loopy.typing import ExpressionT @@ -311,7 +313,7 @@ def __init__(self, "actually specifying happens_after/depends_on") if happens_after is None: - happens_after = {} + happens_after = ps.pmap({}) elif isinstance(happens_after, str): warn("Passing a string for happens_after/depends_on is deprecated and " "will stop working in 2024. Instead, pass a full-fledged " @@ -330,7 +332,8 @@ def __init__(self, instances_rel=None) for after_id in happens_after} elif isinstance(happens_after, MappingABC): - pass + if isinstance(happens_after, dict): + happens_after = ps.pmap(happens_after) else: raise TypeError("'happens_after' has unexpected type: " f"{type(happens_after)}") From 307aa83fcdae2e148510baa141d5a5275ad646f9 Mon Sep 17 00:00:00 2001 From: "Addison J. Alvey-Blanco" Date: Wed, 12 Apr 2023 09:19:35 -0500 Subject: [PATCH 35/52] Nested array access finding --- loopy/kernel/dependency.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 83d07b294..1340c4e01 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -27,6 +27,7 @@ from loopy import LoopKernel from loopy.kernel.instruction import HappensAfter +from loopy.symbolic import UnableToDetermineAccessRangeError from loopy.symbolic import UncachedWalkMapper as WalkMapper from loopy.translation_unit import for_each_kernel from loopy.symbolic import get_access_map @@ -46,6 +47,8 @@ class AccessMapFinder(WalkMapper): def __init__(self, knl: LoopKernel) -> None: self.kernel = knl self._access_maps = pmap({}) + from collections import defaultdict + self.bad_subscripts = defaultdict(list) super().__init__() @@ -64,9 +67,13 @@ def map_subscript(self, expr, insn): arg_name = expr.aggregate.name subscript = expr.index_tuple - access_map = get_access_map( - domain, subscript, self.kernel.assumptions - ) + try: + access_map = get_access_map( + domain, subscript, self.kernel.assumptions + ) + except UnableToDetermineAccessRangeError: + self.bad_subscripts[arg_name].append(expr) + return # analyze what we have in our access map dict before storing map insn_to_args = self._access_maps.get(insn.id) @@ -100,7 +107,8 @@ def map_type_cast(self, expr, insn): return self.rec(expr.child, insn) def map_sub_array_ref(self, expr, insn): - pass + raise NotImplementedError("subarray references are a WIP for " + "precise dependency finding.") @for_each_kernel From 808cef6401514a8d4a9ff95c95d0fb832d7b7da3 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Sun, 16 Apr 2023 19:52:21 -0500 Subject: [PATCH 36/52] Allow depends_on to be passed as an argument for legacy purposes --- .DS_Store | Bin 10244 -> 0 bytes doc/.DS_Store | Bin 6148 -> 0 bytes loopy/kernel/dependency.py | 10 +++++----- loopy/kernel/instruction.py | 30 +++++++++++++++++------------- 4 files changed, 22 insertions(+), 18 deletions(-) delete mode 100644 .DS_Store delete mode 100644 doc/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 3429492e992cfbd017ff9b6886a13504006a1972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMO=uHA6n@*JW@}Y~D0mQEs)$HqTeN>D7;E&R;!i{mHl#_DCQCQFA=xyRQiz3K zY{g!S2Yd7Cv5Hy{MG-7s6uc;S_Tt4uLG;b+B$-JzX}uH+GhycK&U6YR02%DGY_O#x7Pv6~jW_LB>g8k0dLOO8Yh7R7Nz z>9{H^#ZVk~?AMrGkjauG9d}Sz@}aQE3d>Lw8Xd>is5vM>N6J<*AQ`ACSzeoHqRpoHQR&<1#ds;u!4@KZeY}WcR&;#K z?}fXo^B-tTPG61crWfM=_=rP;uN)WBP=L4)9rpjKx8O0XEmY=RGfu{;B57_j(a4U+{Ie_2!k#JDF?qD>*$OzFkBJ9y>R^hDUh5Lihm2Uri;axvwa;1S3dXIm;Nw4wu0DkObp7hAsyG^3iFkq-3}=}@Jfrbq zKFi+X<#^W6^LBUlXQjpCZQbTmeQLWJ38$@M*pAtS{7@`+o7~;7EF)gNj>T{4$zs&M z=bUBQG2Kk&sDqwJqRH?r-Aq`;A*)~}^3*r%DAYE!E$ZJlHPzdHqEqV`=$Y=+rUnO3 zc53~7{nOKJ>fS?Lr>;lGjf`pCBXIHK%|7z@hKOYjZ`o{w)qHMWscLvOBhzcA&%9&1 z6=i{cmg=?d_& z@fe%g31}6%$F(9F3xBw;2#6wqZ-|J~@>s4d&ORM@PgxQys?VFdg%CfrgaKezH9qcc zN!;gC5Fl?KjDaN}0xi9sJ&&=Y^x);AHxy&j7(90y^%3R)BV-=!$Y2h6w6O*%JG8jU zO7uu$OCI}yOUo}9_6FnfsMog^@G4ITUT+zr!12MWy}XXcba`_A)9hQyXT_IC;iebj zb}W#%vPcFb1CjyBfMh^2u(cTQIB+#`{(rvl|NmQSQWC9XKr--83@F}EI5dPV<+Quj zo1C?4SVypuLhMS8q%PPH6VgwQV^stLv2bnB6lDeZGbw{~Le+IC#Oqu^X kXS(j$J)aG_TL}4JYKbgq)7)IhO=bSioh$HMZ%+RI7m<{!mH+?% diff --git a/doc/.DS_Store b/doc/.DS_Store deleted file mode 100644 index 2368045ebaac5550311e75f96bef3d59cc932b44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ&zMH5FKAa&f$cV0>qV;u8l+@K0xbB?R4>)G z<+j|BFa0Au_ou;hmiL0mbNYIsbrPQSgYaoQoDUjTAL?ux#My9S0^(?hE-#+NS)}JZ zJ#{y*>@F6!J2#uEeYd@AsztYZtEoD7I?H8UUfa8||9J2=Ni+S?k|^-b zF>;0R434o=+EX%W`>%C6HV$7Not(F)y5HwJOLpbyhkM_?K3MmO{^&D4T3P4xJ1tkr z;H0) z`*I982L3AsM78htd$=WjTh}&6Z>>Uog-Swxg+&vB4L`@GLr?K8su8p)>L7ZIg+&}e QaX$iv2G=+S{wf1M0okyL!2kdN diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 1340c4e01..f04f27d92 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -26,11 +26,10 @@ from islpy import dim_type from loopy import LoopKernel -from loopy.kernel.instruction import HappensAfter -from loopy.symbolic import UnableToDetermineAccessRangeError -from loopy.symbolic import UncachedWalkMapper as WalkMapper +from loopy.kernel.instruction import HappensAfter, MultiAssignmentBase from loopy.translation_unit import for_each_kernel -from loopy.symbolic import get_access_map +from loopy.symbolic import UnableToDetermineAccessRangeError, get_access_map +from loopy.symbolic import UncachedWalkMapper as WalkMapper import pymbolic.primitives as p @@ -47,7 +46,7 @@ class AccessMapFinder(WalkMapper): def __init__(self, knl: LoopKernel) -> None: self.kernel = knl self._access_maps = pmap({}) - from collections import defaultdict + from collections import defaultdict # FIXME remove this self.bad_subscripts = defaultdict(list) super().__init__() @@ -72,6 +71,7 @@ def map_subscript(self, expr, insn): domain, subscript, self.kernel.assumptions ) except UnableToDetermineAccessRangeError: + # may not have enough info to generate access map at current point self.bad_subscripts[arg_name].append(expr) return diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 21ff9cb4e..413fa1f74 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -32,8 +32,6 @@ from pytools import ImmutableRecord, memoize_method from pytools.tag import Tag, tag_dataclass, Taggable -import pyrsistent as ps - from loopy.diagnostic import LoopyError from loopy.tools import Optional as LoopyOptional from loopy.typing import ExpressionT @@ -313,7 +311,7 @@ def __init__(self, "actually specifying happens_after/depends_on") if happens_after is None: - happens_after = ps.pmap({}) + happens_after = {} elif isinstance(happens_after, str): warn("Passing a string for happens_after/depends_on is deprecated and " "will stop working in 2024. Instead, pass a full-fledged " @@ -333,7 +331,7 @@ def __init__(self, for after_id in happens_after} elif isinstance(happens_after, MappingABC): if isinstance(happens_after, dict): - happens_after = ps.pmap(happens_after) + happens_after = happens_after else: raise TypeError("'happens_after' has unexpected type: " f"{type(happens_after)}") @@ -1065,7 +1063,8 @@ def __init__(self, within_inames=None, tags=None, temp_var_types=None, - priority=0, predicates=frozenset()): + priority=0, predicates=frozenset(), + depends_on=None): super().__init__( id=id, @@ -1078,7 +1077,8 @@ def __init__(self, within_inames=within_inames, priority=priority, predicates=predicates, - tags=tags) + tags=tags, + depends_on=depends_on) from pymbolic.primitives import Call from loopy.symbolic import Reduction @@ -1356,7 +1356,8 @@ def __init__(self, no_sync_with=None, within_inames_is_final=None, within_inames=None, priority=0, - predicates=frozenset(), tags=None): + predicates=frozenset(), tags=None, + depends_on=None): """ :arg iname_exprs: Like :attr:`iname_exprs`, but instead of tuples, simple strings pepresenting inames are also allowed. A single @@ -1375,7 +1376,8 @@ def __init__(self, no_sync_with=no_sync_with, within_inames_is_final=within_inames_is_final, within_inames=within_inames, - priority=priority, predicates=predicates, tags=tags) + priority=priority, predicates=predicates, tags=tags, + depends_on=depends_on) # {{{ normalize iname_exprs @@ -1524,7 +1526,7 @@ def __init__(self, id=None, happens_after=None, depends_on_is_final=None, no_sync_with=None, within_inames_is_final=None, within_inames=None, priority=None, - predicates=None, tags=None): + predicates=None, tags=None, depends_on=None): super().__init__( id=id, happens_after=happens_after, @@ -1536,7 +1538,8 @@ def __init__(self, id=None, happens_after=None, depends_on_is_final=None, within_inames=within_inames, priority=priority, predicates=predicates, - tags=tags) + tags=tags, + depends_on=depends_on) def __str__(self): first_line = "%s: ... nop" % self.id @@ -1584,7 +1587,8 @@ def __init__(self, id, happens_after=None, depends_on_is_final=None, within_inames_is_final=None, within_inames=None, priority=None, predicates=None, tags=None, synchronization_kind="global", - mem_kind="local"): + mem_kind="local", + depends_on=None): if predicates: raise LoopyError("conditional barriers are not supported") @@ -1600,8 +1604,8 @@ def __init__(self, id, happens_after=None, depends_on_is_final=None, within_inames=within_inames, priority=priority, predicates=predicates, - tags=tags - ) + tags=tags, + depends_on=depends_on) self.synchronization_kind = synchronization_kind self.mem_kind = mem_kind From 8a28e7f9a730e5fbdac79ccb19f27edba7794932 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Sun, 16 Apr 2023 20:54:54 -0500 Subject: [PATCH 37/52] Fix issues found by mypy and pylint --- loopy/kernel/dependency.py | 36 +++++++++++++++++++++++------------- test/test_dependencies.py | 4 ++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index f04f27d92..f6bc2f2ec 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -26,7 +26,7 @@ from islpy import dim_type from loopy import LoopKernel -from loopy.kernel.instruction import HappensAfter, MultiAssignmentBase +from loopy.kernel.instruction import HappensAfter from loopy.translation_unit import for_each_kernel from loopy.symbolic import UnableToDetermineAccessRangeError, get_access_map from loopy.symbolic import UncachedWalkMapper as WalkMapper @@ -34,6 +34,10 @@ import pymbolic.primitives as p from pyrsistent import pmap +from pyrsistent import PMap + +from loopy.typing import Expression +from typing import List, Dict class AccessMapFinder(WalkMapper): @@ -45,9 +49,9 @@ class AccessMapFinder(WalkMapper): def __init__(self, knl: LoopKernel) -> None: self.kernel = knl - self._access_maps = pmap({}) + self._access_maps: PMap[str, PMap[str, isl.Map]] = pmap({}) from collections import defaultdict # FIXME remove this - self.bad_subscripts = defaultdict(list) + self.bad_subscripts: Dict[str, List[Expression]] = defaultdict(list) super().__init__() @@ -187,9 +191,11 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: deps = unordered_deps & lex_map if not deps.is_empty(): - new_happens_after.update({ - after_insn: HappensAfter(variable, deps) - }) + new_happens_after = dict( + list(new_happens_after.items()) + + + [(after_insn, HappensAfter(variable, deps))] + ) # dependency computation for after_insn in writer_map.get(variable, set()): @@ -214,9 +220,11 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: deps = unordered_deps & lex_map if not deps.is_empty(): - new_happens_after.update({ - after_insn: HappensAfter(variable, deps) - }) + new_happens_after = dict( + list(new_happens_after.items()) + + + [(after_insn, HappensAfter(variable, deps))] + ) for variable in reads[before_insn.id]: @@ -243,12 +251,14 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: deps = unordered_deps & lex_map if not deps.is_empty(): - new_happens_after.update({ - after_insn: HappensAfter(variable, deps) - }) + new_happens_after = dict( + list(new_happens_after.items()) + + + [(after_insn, HappensAfter(variable, deps))] + ) new_insns.append(before_insn.copy( - happens_after=pmap(new_happens_after) + happens_after=new_happens_after )) return knl.copy(instructions=new_insns) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index 53b464672..d7eb12d2d 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -54,10 +54,10 @@ def test_data_dependencies(): """) from loopy.kernel.dependency import add_lexicographic_happens_after,\ - narrow_dependencies + compute_data_dependencies knl = add_lexicographic_happens_after(knl) - narrow_dependencies(knl) + compute_data_dependencies(knl) if __name__ == "__main__": From 57310251ee345ef589e16e4d462b46d50cca3d72 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 17 Apr 2023 20:02:55 -0500 Subject: [PATCH 38/52] Fix documentation issues, handle cases where depends_on and happens_after are both specified --- doc/ref_kernel.rst | 5 +++++ loopy/__init__.py | 2 ++ loopy/kernel/dependency.py | 7 ++++++- loopy/kernel/instruction.py | 18 +++++++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/doc/ref_kernel.rst b/doc/ref_kernel.rst index c53c56530..56fdc8955 100644 --- a/doc/ref_kernel.rst +++ b/doc/ref_kernel.rst @@ -260,6 +260,7 @@ Instructions .. {{{ +.. autoclass:: HappensAfter .. autoclass:: InstructionBase .. _assignments: @@ -457,6 +458,10 @@ Loopy's expressions are a slight superset of the expressions supported by TODO: Functions TODO: Reductions +Dependencies +^^^^^^^^^^^^ +.. automodule:: loopy.kernel.dependency + Function Call Instructions ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/loopy/__init__.py b/loopy/__init__.py index 7491de6cc..2ba54bad6 100644 --- a/loopy/__init__.py +++ b/loopy/__init__.py @@ -29,6 +29,7 @@ # {{{ imported user interface from loopy.kernel.instruction import ( + HappensAfter, LegacyStringInstructionTag, UseStreamingStoreTag, MemoryOrdering, MemoryScope, @@ -172,6 +173,7 @@ "LoopKernel", "KernelState", + "HappensAfter", "LegacyStringInstructionTag", "UseStreamingStoreTag", "MemoryOrdering", "MemoryScope", diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index f6bc2f2ec..9ce88d1ea 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -1,3 +1,8 @@ +""" +.. autoclass:: AccessMapFinder +.. autofunction:: compute_data_dependencies +""" + __copyright__ = "Copyright (C) 2023 Addison Alvey-Blanco" __license__ = """ @@ -343,7 +348,7 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: insn_before.id: HappensAfter(None, happens_before) } - insn_after = insn_after.copy(happens_after=pmap(new_happens_after)) + insn_after = insn_after.copy(happens_after=new_happens_after) new_insns.append(insn_after) diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 413fa1f74..a3c788217 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -96,7 +96,7 @@ class HappensAfter: .. attribute:: instances_rel - An :class:`isl.Map` representing the happens-after relationship. The + An :class:`islpy.Map` representing the happens-after relationship. The input of the map is an iname tuple and the output of the map is a set of iname tuples that must execute after the input. @@ -398,6 +398,22 @@ def __init__(self, Taggable.__init__(self, tags) + def get_copy_kwargs(self, **kwargs): + passed_depends_on = "depends_on" in kwargs + + if passed_depends_on: + assert "happens_after" not in kwargs + + kwargs = super().get_copy_kwargs(**kwargs) + + if passed_depends_on: + # warn that this is deprecated + warn("depends_on is deprecated and will stop working in 2024. " + "Instead, use happens_after.", DeprecationWarning, stacklevel=2) + del kwargs["happens_after"] + + return kwargs + # {{{ abstract interface def read_dependency_names(self): From e745336736094e35565ccedb85d95505d9a62967 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Fri, 21 Apr 2023 11:00:17 -0500 Subject: [PATCH 39/52] Fix dependency cylce introduced by interaction between add_lexicographic_... and apply_single_writer... --- loopy/kernel/creation.py | 5 ++++- loopy/kernel/dependency.py | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index 8f783c000..c22979d48 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -2522,7 +2522,10 @@ def make_function(domains, instructions, kernel_data=None, **kwargs): knl = guess_arg_shape_if_requested(knl, default_order) knl = apply_default_order_to_args(knl, default_order) knl = resolve_dependencies(knl) - knl = apply_single_writer_depencency_heuristic(knl, warn_if_used=False) + + # precise dependency semantics should not rely on this + if not seq_dependencies: + knl = apply_single_writer_depencency_heuristic(knl, warn_if_used=False) # ------------------------------------------------------------------------- # Ordering dependency: diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 9ce88d1ea..df328a279 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -73,8 +73,7 @@ def map_subscript(self, expr, insn): assert isinstance(expr.aggregate, p.Variable) arg_name = expr.aggregate.name - subscript = expr.index_tuple - + subscript = expr.index_tuple try: access_map = get_access_map( domain, subscript, self.kernel.assumptions @@ -175,7 +174,6 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: # dependency computation for after_insn in reader_map.get(variable, set()): - before_map = amf.get_map(before_insn.id, variable) after_map = amf.get_map(after_insn, variable) From b9cc69147a509670f246c15b45a666ae82a0aa28 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Tue, 25 Apr 2023 16:46:30 -0500 Subject: [PATCH 40/52] Add a possible solution for supporting scalars in dependency finding code --- loopy/check.py | 3 +- loopy/kernel/creation.py | 5 +- loopy/kernel/dependency.py | 315 +++++++++++++++++++------------------ test/test_apps.py | 2 +- test/test_dependencies.py | 21 ++- 5 files changed, 180 insertions(+), 166 deletions(-) diff --git a/loopy/check.py b/loopy/check.py index 3f65ad7ab..d8c1656e1 100644 --- a/loopy/check.py +++ b/loopy/check.py @@ -1057,8 +1057,7 @@ def _check_variable_access_ordered_inner(kernel): # depends_on: mapping from insn_ids to their dependencies depends_on = {insn.id: set() for insn in kernel.instructions} # rev_depends: mapping from insn_ids to their reverse deps. - rev_depends = {insn.id: set() for insn in kernel.instructions} - + rev_depends = {insn.id: set() for insn in kernel.instructions} for insn in kernel.instructions: depends_on[insn.id].update(insn.depends_on) for dep in insn.depends_on: diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index c22979d48..ef7bcb9e0 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -2522,7 +2522,10 @@ def make_function(domains, instructions, kernel_data=None, **kwargs): knl = guess_arg_shape_if_requested(knl, default_order) knl = apply_default_order_to_args(knl, default_order) knl = resolve_dependencies(knl) - + + from loopy.kernel.dependency import narrow_dependencies + knl = narrow_dependencies(knl) + # precise dependency semantics should not rely on this if not seq_dependencies: knl = apply_single_writer_depencency_heuristic(knl, warn_if_used=False) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index df328a279..99c795040 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -25,8 +25,6 @@ THE SOFTWARE. """ -from typing import Mapping - import islpy as isl from islpy import dim_type @@ -34,7 +32,7 @@ from loopy.kernel.instruction import HappensAfter from loopy.translation_unit import for_each_kernel from loopy.symbolic import UnableToDetermineAccessRangeError, get_access_map -from loopy.symbolic import UncachedWalkMapper as WalkMapper +from loopy.symbolic import WalkMapper import pymbolic.primitives as p @@ -44,6 +42,8 @@ from loopy.typing import Expression from typing import List, Dict +from warnings import warn + class AccessMapFinder(WalkMapper): """Finds and stores relations representing the accesses to an array by @@ -60,31 +60,36 @@ def __init__(self, knl: LoopKernel) -> None: super().__init__() - def get_map(self, insn_id: str, variable_name: str) -> isl.Map: + def get_map(self, insn_id: str, variable_name: str) -> isl.Map | None: """Retrieve an access map indexed by an instruction ID and variable name. """ - return self._access_maps[insn_id][variable_name] + try: + return self._access_maps[insn_id][variable_name] + except KeyError: + return None - def map_subscript(self, expr, insn): - domain = self.kernel.get_inames_domain(insn.within_inames) - WalkMapper.map_subscript(self, expr, insn) + def map_subscript(self, expr, insn_id): + domain = self.kernel.get_inames_domain( + self.kernel.id_to_insn[insn_id].within_inames + ) + WalkMapper.map_subscript(self, expr, insn_id) assert isinstance(expr.aggregate, p.Variable) arg_name = expr.aggregate.name - subscript = expr.index_tuple + subscript = expr.index_tuple + try: access_map = get_access_map( - domain, subscript, self.kernel.assumptions - ) + domain, subscript, self.kernel.assumptions) except UnableToDetermineAccessRangeError: # may not have enough info to generate access map at current point self.bad_subscripts[arg_name].append(expr) return # analyze what we have in our access map dict before storing map - insn_to_args = self._access_maps.get(insn.id) + insn_to_args = self._access_maps.get(insn_id) if insn_to_args is not None: existing_relation = insn_to_args.get(arg_name) @@ -92,53 +97,43 @@ def map_subscript(self, expr, insn): access_map |= existing_relation self._access_maps = self._access_maps.set( - insn.id, self._access_maps[insn.id].set( - arg_name, access_map - ) - ) + insn_id, self._access_maps[insn_id].set( + arg_name, access_map)) else: self._access_maps = self._access_maps.set( - insn.id, pmap({arg_name: access_map}) - ) + insn_id, pmap({arg_name: access_map})) - def map_linear_subscript(self, expr, insn): + def map_linear_subscript(self, expr, insn_id): raise NotImplementedError("linear subscripts cannot be used with " "precise dependency finding. Use " - "multidimensional accesses to use this " - "feature.") + "multidimensional accesses to take advantage " + "of this feature.") + + def map_reduction(self, expr, insn_id): + return WalkMapper.map_reduction(self, expr, insn_id) - def map_reduction(self, expr, insn): - return WalkMapper.map_reduction(self, expr, insn) + def map_type_cast(self, expr, insn_id): + return self.rec(expr.child, insn_id) - def map_type_cast(self, expr, insn): - return self.rec(expr.child, insn) + def map_sub_array_ref(self, expr, insn_id): + arg_name = expr.subscript.aggregate.name - def map_sub_array_ref(self, expr, insn): - raise NotImplementedError("subarray references are a WIP for " - "precise dependency finding.") + if arg_name in self.bad_subscripts: + return + inames = self.kernel.id_to_insn[insn_id].within_inames + total_inames = inames | {iname.name for iname in expr.swept_inames} @for_each_kernel -def compute_data_dependencies(knl: LoopKernel) -> LoopKernel: - """Compute data dependencies between statements in a kernel. Can utilize an - existing lexicographic ordering, i.e. one generated by - `add_lexicographic_happens_after`. +def narrow_dependencies(knl: LoopKernel) -> LoopKernel: + """Attempt to relax a strict (lexical) ordering between statements in a + kernel by way of finding data dependencies. """ - def get_unordered_deps(frm: isl.Map, to: isl.Map) -> isl.Map: - # equivalent to R^{-1} o S - dependency = frm.apply_range(to.reverse()) - - if dependency.dim(dim_type.out) == dependency.dim(dim_type.in_): - identity = dependency.identity(dependency.get_space()) - else: - raise RuntimeError("input and output dimensions do not match") - - return dependency - identity - def make_inames_unique(relation: isl.Map) -> isl.Map: - """Add a single quote to the output inames of an isl.Map + """Append a single-quote to all inames in the output dimension of a map + ensure input/output inames do not match """ for idim in range(relation.dim(dim_type.out)): iname = relation.get_dim_name(dim_type.out, idim) + "'" @@ -146,123 +141,159 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: return relation - writer_map = knl.writer_map() - reader_map = knl.reader_map() - writes = { - insn.id: insn.write_dependency_names() - insn.within_inames - for insn in knl.instructions - } + def compute_data_dependencies(before_insn: str, after_insn: str, + variable: str) -> isl.Map: + """Compute a relation from instances of `after_insn` -> instances of + `before_insn` that describes the instances of `after_insn` that must + execute after instances of `before_insn`. - reads = { - insn.id: insn.read_dependency_names() - insn.within_inames - for insn in knl.instructions - } + .. arg:: before_insn + The instruction id of the statement whose instances are in the range + of the relation. - amf = AccessMapFinder(knl) - for insn in knl.instructions: - amf(insn.assignee, insn) - amf(insn.expression, insn) + .. arg:: after_insn + The instruction id of the statement whose instances are in the + domain of the relation. - new_insns = [] - for before_insn in knl.instructions: - new_happens_after: Mapping[str, HappensAfter] = {} + .. arg:: variable + The variable responsible for the data dependency between the two + statements. + """ - assert isinstance(before_insn.id, str) # stop complaints + assert isinstance(insn.id, str) # stop complaints - for variable in writes[before_insn.id]: + before_map = amf.get_map(before_insn, variable) + after_map = amf.get_map(after_insn, variable) - # dependency computation - for after_insn in reader_map.get(variable, set()): - before_map = amf.get_map(before_insn.id, variable) - after_map = amf.get_map(after_insn, variable) + if before_map is None: + warn("unable to determine the access map for %s. " + "Defaulting to a conservative dependency relation between %s " + "and %s" % (before_insn, before_insn, after_insn)) + return - unordered_deps = get_unordered_deps(before_map, after_map) + if after_map is None: + warn("unable to determine the access map for %s. " + "Defaulting to a conservative dependency relation between %s " + "and %s" % (after_insn, before_insn, after_insn)) + return - # may not permanently construct lex maps this way - lex_map = unordered_deps.lex_lt_map(unordered_deps) + dims = [before_map.dim(isl.dim_type.out), + before_map.dim(isl.dim_type.in_), + after_map.dim(isl.dim_type.out), + after_map.dim(isl.dim_type.in_)] + + for i in range(len(dims)): + if i == 0 or i == 1: + scalar_insn = before_insn + else: + scalar_insn = after_insn + + if dims[i] == 0: + warn("found evidence of a scalar access in %s. Defaulting to a " + "conservative dependency relation between " + "%s and %s" % (scalar_insn, before_insn, after_insn)) + return + + # map from after_instances -> before_instances + unordered_deps = after_map.apply_range(before_map.reverse()) + identity = unordered_deps.identity(unordered_deps.get_space()) + unordered_deps -= identity + + if before_insn in insn.happens_after: + lex_map = insn.happens_after[before_insn].instances_rel + else: + lex_map = unordered_deps.lex_lt_map(unordered_deps) - # may not be needed if we can resolve issues above - if lex_map.space != unordered_deps.space: - lex_map = make_inames_unique(lex_map) - unordered_deps = make_inames_unique(unordered_deps) + assert lex_map is not None + if lex_map.space != unordered_deps.space: + lex_map = make_inames_unique(lex_map) + unordered_deps = make_inames_unique(unordered_deps) - lex_map, unordered_deps = isl.align_two( - lex_map, unordered_deps - ) + lex_map, unordered_deps = isl.align_two(lex_map, + unordered_deps) - deps = unordered_deps & lex_map + deps = lex_map & unordered_deps - if not deps.is_empty(): - new_happens_after = dict( - list(new_happens_after.items()) - + - [(after_insn, HappensAfter(variable, deps))] - ) + return deps + # end helper function definitions + + writer_map = knl.writer_map() + reader_map = knl.reader_map() - # dependency computation - for after_insn in writer_map.get(variable, set()): + written_by = {insn.id: insn.write_dependency_names() - insn.within_inames + for insn in knl.instructions} + read_by = {insn.id: insn.write_dependency_names() - insn.within_inames + for insn in knl.instructions} - before_map = amf.get_map(before_insn.id, variable) - after_map = amf.get_map(after_insn, variable) + # used to ensure that we are only storing dependencies at the instructions + # that occur later in the program list + ordered_insns = {insn.id: i for i, insn in enumerate(knl.instructions)} - unordered_deps = get_unordered_deps(before_map, after_map) + amf = AccessMapFinder(knl) + for insn in knl.instructions: + amf(insn.assignee, insn.id) + amf(insn.expression, insn.id) - # may not permanently construct lex maps this way - lex_map = unordered_deps.lex_lt_map(unordered_deps) + new_insns = [] + for insn in knl.instructions: + if ordered_insns[insn.id] == 0: + new_insns.append(insn) + continue - # may not be needed if we can resolve issues above - if lex_map.space != unordered_deps.space: - lex_map = make_inames_unique(lex_map) - unordered_deps = make_inames_unique(unordered_deps) + new_happens_after = {} + for variable in read_by[insn.id]: - lex_map, unordered_deps = isl.align_two( - lex_map, unordered_deps - ) + # handle flow-dependencies (read-after-write) + for before_insn in writer_map.get(variable, set()) - {insn.id}: + if ordered_insns[before_insn] > ordered_insns[insn.id]: + continue - deps = unordered_deps & lex_map + assert isinstance(insn.id, str) # stop complaints + deps = compute_data_dependencies(before_insn, insn.id, variable) - if not deps.is_empty(): + if deps is None or not deps.is_empty(): new_happens_after = dict( - list(new_happens_after.items()) + list(insn.happens_after.items()) + - [(after_insn, HappensAfter(variable, deps))] - ) - - for variable in reads[before_insn.id]: + [(before_insn, HappensAfter(variable, deps))]) - # dependency computation - for after_insn in writer_map.get(variable, set()): + for variable in written_by[insn.id]: - before_map = amf.get_map(before_insn.id, variable) - after_map = amf.get_map(after_insn, variable) + # handle anti dependencies (write-after-read) + for before_insn in reader_map.get(variable, set()) - {insn.id}: + if ordered_insns[before_insn] > ordered_insns[insn.id]: + continue - unordered_deps = get_unordered_deps(before_map, after_map) + assert isinstance(insn.id, str) # stop complaints + deps = compute_data_dependencies(before_insn, insn.id, variable) - # may not permanently construct lex maps this way - lex_map = unordered_deps.lex_lt_map(unordered_deps) - - # may not be needed if we can resolve issues above - if lex_map.space != unordered_deps.space: - lex_map = make_inames_unique(lex_map) - unordered_deps = make_inames_unique(unordered_deps) + if deps is None or not deps.is_empty(): + new_happens_after = dict( + list(insn.happens_after.items()) + + + [(before_insn, HappensAfter(variable, deps))]) - lex_map, unordered_deps = isl.align_two( - lex_map, unordered_deps - ) + # handle output dependencies (write-after-write) + for before_insn in writer_map.get(variable, set()) - {insn.id}: + if ordered_insns[before_insn] > ordered_insns[insn.id]: + continue - deps = unordered_deps & lex_map + assert isinstance(insn.id, str) # stop complaints + deps = compute_data_dependencies(before_insn, insn.id, variable) - if not deps.is_empty(): + if deps is None or not deps.is_empty(): new_happens_after = dict( - list(new_happens_after.items()) + list(insn.happens_after.items()) + - [(after_insn, HappensAfter(variable, deps))] - ) + [(before_insn, HappensAfter(variable, deps))]) - new_insns.append(before_insn.copy( - happens_after=new_happens_after - )) + # clean up any deps that do not contain a variable name + new_happens_after = {insn_id: dep + for insn_id, dep in new_happens_after.items() + if dep.variable_name is not None} + + new_insns.append(insn.copy(happens_after=new_happens_after)) return knl.copy(instructions=new_insns) @@ -353,32 +384,4 @@ def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: return knl.copy(instructions=new_insns) -@for_each_kernel -def print_dependency_info(knl: LoopKernel) -> None: - - dependencies = [] - for insn in knl.instructions: - dep_string = f"{insn.id} depends on \n" - - if not insn.happens_after: - dep_string += "nothing\n" - - else: - for dep in insn.happens_after: - - dep_string += f"{dep} " - - if insn.happens_after[dep].variable_name is not None: - dep_string += "at variable " - dep_string += f"'{insn.happens_after[dep].variable_name}' " - - dep_string += "with relation \n" - dep_string += f"{insn.happens_after[dep].instances_rel}\n\n" - - dep_string += ((80*"-") + "\n") - dependencies.append(dep_string) - - for s in dependencies: - print(s) - # vim: foldmethod=marker diff --git a/test/test_apps.py b/test/test_apps.py index ad7879b1f..c4f3398b9 100644 --- a/test/test_apps.py +++ b/test/test_apps.py @@ -228,7 +228,7 @@ def test_rob_stroud_bernstein(): qpts=np.float32, coeffs=np.float32, tmp=np.float32, - )) + )) print(lp.generate_code_v2(knl)) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index d7eb12d2d..281d65261 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -22,6 +22,8 @@ import sys import loopy as lp +from loopy.kernel.dependency import add_lexicographic_happens_after, \ + narrow_dependencies def test_lex_dependencies(): @@ -36,9 +38,7 @@ def test_lex_dependencies(): v[a,b,k,l] = 2*v[a,b,k,l] """) - from loopy.kernel.dependency import add_lexicographic_happens_after - - add_lexicographic_happens_after(knl) + knl = add_lexicographic_happens_after(knl) def test_data_dependencies(): @@ -53,11 +53,20 @@ def test_data_dependencies(): v[a,b,k,l] = 2*v[a,b,k,l] """) - from loopy.kernel.dependency import add_lexicographic_happens_after,\ - compute_data_dependencies + knl = add_lexicographic_happens_after(knl) + knl = narrow_dependencies(knl) + + +def test_scalar_dependencies(): + knl = lp.make_kernel( + "{ [i]: i = 0 }", + """ + a = 3 + b = a*2 + """) knl = add_lexicographic_happens_after(knl) - compute_data_dependencies(knl) + knl = narrow_dependencies(knl) if __name__ == "__main__": From d361167d7d272815058878385685fb2004b8e94e Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Tue, 25 Apr 2023 17:03:58 -0500 Subject: [PATCH 41/52] Pushing [skip ci] --- loopy/kernel/dependency.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 99c795040..349a53fcc 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -117,13 +117,7 @@ def map_type_cast(self, expr, insn_id): return self.rec(expr.child, insn_id) def map_sub_array_ref(self, expr, insn_id): - arg_name = expr.subscript.aggregate.name - - if arg_name in self.bad_subscripts: - return - - inames = self.kernel.id_to_insn[insn_id].within_inames - total_inames = inames | {iname.name for iname in expr.swept_inames} + raise NotImplementedError("Not yet implemented") @for_each_kernel def narrow_dependencies(knl: LoopKernel) -> LoopKernel: From 16be472b0cfe0729586e208bff544cf3638bc86b Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 1 May 2023 09:10:13 -0500 Subject: [PATCH 42/52] Precompute coarse-grained dependencies, add traces of transitive dependency finding in narrow_dependencies [skip ci] --- loopy/kernel/creation.py | 3 - loopy/kernel/dependency.py | 297 ++++++---------------------------- loopy/transform/dependency.py | 215 +++++++++++++++++++++++- test/test_dependencies.py | 49 +++--- 4 files changed, 291 insertions(+), 273 deletions(-) diff --git a/loopy/kernel/creation.py b/loopy/kernel/creation.py index ef7bcb9e0..bdbc9cc96 100644 --- a/loopy/kernel/creation.py +++ b/loopy/kernel/creation.py @@ -2523,9 +2523,6 @@ def make_function(domains, instructions, kernel_data=None, **kwargs): knl = apply_default_order_to_args(knl, default_order) knl = resolve_dependencies(knl) - from loopy.kernel.dependency import narrow_dependencies - knl = narrow_dependencies(knl) - # precise dependency semantics should not rely on this if not seq_dependencies: knl = apply_single_writer_depencency_heuristic(knl, warn_if_used=False) diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 349a53fcc..90e73bd17 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -1,6 +1,6 @@ """ -.. autoclass:: AccessMapFinder -.. autofunction:: compute_data_dependencies +.. autofunction:: add_lexicographic_happens_after +.. autofunction:: find_data_dependencies """ __copyright__ = "Copyright (C) 2023 Addison Alvey-Blanco" @@ -31,272 +31,71 @@ from loopy import LoopKernel from loopy.kernel.instruction import HappensAfter from loopy.translation_unit import for_each_kernel -from loopy.symbolic import UnableToDetermineAccessRangeError, get_access_map -from loopy.symbolic import WalkMapper -import pymbolic.primitives as p - -from pyrsistent import pmap -from pyrsistent import PMap - -from loopy.typing import Expression -from typing import List, Dict - -from warnings import warn - - -class AccessMapFinder(WalkMapper): - """Finds and stores relations representing the accesses to an array by - statement instances. Access maps can be found using an instruction's ID and - a variable's name. Essentially a specialized version of - BatchedAccessMapMapper. - """ - - def __init__(self, knl: LoopKernel) -> None: - self.kernel = knl - self._access_maps: PMap[str, PMap[str, isl.Map]] = pmap({}) - from collections import defaultdict # FIXME remove this - self.bad_subscripts: Dict[str, List[Expression]] = defaultdict(list) - - super().__init__() - - def get_map(self, insn_id: str, variable_name: str) -> isl.Map | None: - """Retrieve an access map indexed by an instruction ID and variable - name. - """ - try: - return self._access_maps[insn_id][variable_name] - except KeyError: - return None - - def map_subscript(self, expr, insn_id): - domain = self.kernel.get_inames_domain( - self.kernel.id_to_insn[insn_id].within_inames - ) - WalkMapper.map_subscript(self, expr, insn_id) - - assert isinstance(expr.aggregate, p.Variable) - - arg_name = expr.aggregate.name - subscript = expr.index_tuple - - try: - access_map = get_access_map( - domain, subscript, self.kernel.assumptions) - except UnableToDetermineAccessRangeError: - # may not have enough info to generate access map at current point - self.bad_subscripts[arg_name].append(expr) - return - - # analyze what we have in our access map dict before storing map - insn_to_args = self._access_maps.get(insn_id) - if insn_to_args is not None: - existing_relation = insn_to_args.get(arg_name) - - if existing_relation is not None: - access_map |= existing_relation - - self._access_maps = self._access_maps.set( - insn_id, self._access_maps[insn_id].set( - arg_name, access_map)) - - else: - self._access_maps = self._access_maps.set( - insn_id, pmap({arg_name: access_map})) - - def map_linear_subscript(self, expr, insn_id): - raise NotImplementedError("linear subscripts cannot be used with " - "precise dependency finding. Use " - "multidimensional accesses to take advantage " - "of this feature.") - - def map_reduction(self, expr, insn_id): - return WalkMapper.map_reduction(self, expr, insn_id) - - def map_type_cast(self, expr, insn_id): - return self.rec(expr.child, insn_id) - - def map_sub_array_ref(self, expr, insn_id): - raise NotImplementedError("Not yet implemented") @for_each_kernel -def narrow_dependencies(knl: LoopKernel) -> LoopKernel: - """Attempt to relax a strict (lexical) ordering between statements in a - kernel by way of finding data dependencies. +def find_data_dependencies(knl: LoopKernel) -> LoopKernel: + """Compute coarse-grained dependencies between statements in a Loopy program. + Finds data dependencies based on the Bernstein Condition, i.e., statement S2 + depends on statement S1 if the union of true, anti, and flow dependencies + between the two statements is non-empty and the instructions actually + execute. """ - def make_inames_unique(relation: isl.Map) -> isl.Map: - """Append a single-quote to all inames in the output dimension of a map - ensure input/output inames do not match - """ - for idim in range(relation.dim(dim_type.out)): - iname = relation.get_dim_name(dim_type.out, idim) + "'" - relation = relation.set_dim_name(dim_type.out, idim, iname) - - return relation - - - def compute_data_dependencies(before_insn: str, after_insn: str, - variable: str) -> isl.Map: - """Compute a relation from instances of `after_insn` -> instances of - `before_insn` that describes the instances of `after_insn` that must - execute after instances of `before_insn`. - - .. arg:: before_insn - The instruction id of the statement whose instances are in the range - of the relation. - - .. arg:: after_insn - The instruction id of the statement whose instances are in the - domain of the relation. - - .. arg:: variable - The variable responsible for the data dependency between the two - statements. - """ - - assert isinstance(insn.id, str) # stop complaints - - before_map = amf.get_map(before_insn, variable) - after_map = amf.get_map(after_insn, variable) - - if before_map is None: - warn("unable to determine the access map for %s. " - "Defaulting to a conservative dependency relation between %s " - "and %s" % (before_insn, before_insn, after_insn)) - return - - if after_map is None: - warn("unable to determine the access map for %s. " - "Defaulting to a conservative dependency relation between %s " - "and %s" % (after_insn, before_insn, after_insn)) - return - - dims = [before_map.dim(isl.dim_type.out), - before_map.dim(isl.dim_type.in_), - after_map.dim(isl.dim_type.out), - after_map.dim(isl.dim_type.in_)] - - for i in range(len(dims)): - if i == 0 or i == 1: - scalar_insn = before_insn - else: - scalar_insn = after_insn - - if dims[i] == 0: - warn("found evidence of a scalar access in %s. Defaulting to a " - "conservative dependency relation between " - "%s and %s" % (scalar_insn, before_insn, after_insn)) - return - - # map from after_instances -> before_instances - unordered_deps = after_map.apply_range(before_map.reverse()) - identity = unordered_deps.identity(unordered_deps.get_space()) - unordered_deps -= identity - - if before_insn in insn.happens_after: - lex_map = insn.happens_after[before_insn].instances_rel - else: - lex_map = unordered_deps.lex_lt_map(unordered_deps) - - assert lex_map is not None - if lex_map.space != unordered_deps.space: - lex_map = make_inames_unique(lex_map) - unordered_deps = make_inames_unique(unordered_deps) - - lex_map, unordered_deps = isl.align_two(lex_map, - unordered_deps) - - deps = lex_map & unordered_deps - - return deps - # end helper function definitions - - writer_map = knl.writer_map() reader_map = knl.reader_map() + writer_map = knl.writer_map() - written_by = {insn.id: insn.write_dependency_names() - insn.within_inames - for insn in knl.instructions} - read_by = {insn.id: insn.write_dependency_names() - insn.within_inames + readers = {insn.id: insn.read_dependency_names() - insn.within_inames + for insn in knl.instructions} + writers = {insn.id: insn.write_dependency_names() - insn.within_inames for insn in knl.instructions} - # used to ensure that we are only storing dependencies at the instructions - # that occur later in the program list ordered_insns = {insn.id: i for i, insn in enumerate(knl.instructions)} - amf = AccessMapFinder(knl) - for insn in knl.instructions: - amf(insn.assignee, insn.id) - amf(insn.expression, insn.id) - new_insns = [] for insn in knl.instructions: - if ordered_insns[insn.id] == 0: - new_insns.append(insn) - continue - - new_happens_after = {} - for variable in read_by[insn.id]: - - # handle flow-dependencies (read-after-write) - for before_insn in writer_map.get(variable, set()) - {insn.id}: - if ordered_insns[before_insn] > ordered_insns[insn.id]: - continue - - assert isinstance(insn.id, str) # stop complaints - deps = compute_data_dependencies(before_insn, insn.id, variable) - - if deps is None or not deps.is_empty(): - new_happens_after = dict( - list(insn.happens_after.items()) - + - [(before_insn, HappensAfter(variable, deps))]) - - for variable in written_by[insn.id]: - - # handle anti dependencies (write-after-read) - for before_insn in reader_map.get(variable, set()) - {insn.id}: - if ordered_insns[before_insn] > ordered_insns[insn.id]: - continue - - assert isinstance(insn.id, str) # stop complaints - deps = compute_data_dependencies(before_insn, insn.id, variable) - - if deps is None or not deps.is_empty(): - new_happens_after = dict( - list(insn.happens_after.items()) - + - [(before_insn, HappensAfter(variable, deps))]) - - # handle output dependencies (write-after-write) - for before_insn in writer_map.get(variable, set()) - {insn.id}: - if ordered_insns[before_insn] > ordered_insns[insn.id]: - continue - - assert isinstance(insn.id, str) # stop complaints - deps = compute_data_dependencies(before_insn, insn.id, variable) - - if deps is None or not deps.is_empty(): - new_happens_after = dict( - list(insn.happens_after.items()) - + - [(before_insn, HappensAfter(variable, deps))]) - - # clean up any deps that do not contain a variable name - new_happens_after = {insn_id: dep - for insn_id, dep in new_happens_after.items() - if dep.variable_name is not None} - - new_insns.append(insn.copy(happens_after=new_happens_after)) + happens_after = insn.happens_after + + # read after write + for variable in readers[insn.id]: + for writer in writer_map.get(variable, set()) - {insn.id}: + if ordered_insns[writer] < ordered_insns[insn.id]: + happens_after = dict( + list(happens_after.items()) + + [(writer, HappensAfter(variable, None))]) + + for variable in writers[insn.id]: + # write after read + for reader in reader_map.get(variable, set()) - {insn.id}: + if ordered_insns[reader] < ordered_insns[insn.id]: + happens_after = dict( + list(happens_after.items()) + + [(reader, HappensAfter(variable, None))]) + + # write after write + for writer in writer_map.get(variable, set()) - {insn.id}: + if ordered_insns[writer] < ordered_insns[insn.id]: + happens_after = dict( + list(happens_after.items()) + + [(writer, HappensAfter(variable, None))]) + + # remove dependencies that do not specify a variable name + happens_after = { + insn_id: dependency + for insn_id, dependency in happens_after.items() + if dependency.variable_name is not None} + + new_insns.append(insn.copy(happens_after=happens_after)) return knl.copy(instructions=new_insns) @for_each_kernel def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: - """Compute an initial lexicographic happens-after ordering of the statments - in a :class:`loopy.LoopKernel`. Statements are ordered in a sequential - (C-like) manner. + """Construct a sequential dependency specification between each instruction + and the instruction immediately before it. This dependency information + contains a lexicographic map which acts as a description of the precise, + statement-instance level dependencies between statements. """ new_insns = [] diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index 89a57934d..0f9dd9e91 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -1,3 +1,7 @@ +""" +.. autoclass:: AccessMapFinder +.. autofunction:: narrow_dependencies +""" __copyright__ = "Copyright (C) 2022 Addison Alvey-Blanco" __license__ = """ @@ -20,10 +24,217 @@ THE SOFTWARE. """ +import islpy as isl +from islpy import dim_type + +from loopy.kernel.instruction import HappensAfter from loopy.kernel import LoopKernel from loopy.translation_unit import for_each_kernel +from loopy.symbolic import WalkMapper, get_access_map, \ + UnableToDetermineAccessRangeError +from loopy.typing import Expression + +import pymbolic.primitives as p +from typing import List, Dict +from pyrsistent import pmap, PMap +from warnings import warn +from functools import reduce + + +class AccessMapFinder(WalkMapper): + """Finds and stores relations representing the accesses to an array by + statement instances. Access maps can be found using an instruction's ID and + a variable's name. Essentially a specialized version of + BatchedAccessMapMapper. + """ + + def __init__(self, knl: LoopKernel) -> None: + self.kernel = knl + self._access_maps: PMap[str, PMap[str, isl.Map]] = pmap({}) + from collections import defaultdict # FIXME remove this + self.bad_subscripts: Dict[str, List[Expression]] = defaultdict(list) + + super().__init__() + + def get_map(self, insn_id: str, variable_name: str) -> isl.Map | None: + """Retrieve an access map indexed by an instruction ID and variable + name. + """ + try: + return self._access_maps[insn_id][variable_name] + except KeyError: + return None + + def map_subscript(self, expr, insn_id): + domain = self.kernel.get_inames_domain( + self.kernel.id_to_insn[insn_id].within_inames + ) + WalkMapper.map_subscript(self, expr, insn_id) + + assert isinstance(expr.aggregate, p.Variable) + + arg_name = expr.aggregate.name + subscript = expr.index_tuple + + try: + access_map = get_access_map( + domain, subscript, self.kernel.assumptions) + except UnableToDetermineAccessRangeError: + # may not have enough info to generate access map at current point + self.bad_subscripts[arg_name].append(expr) + return + + # analyze what we have in our access map dict before storing map + insn_to_args = self._access_maps.get(insn_id) + if insn_to_args is not None: + existing_relation = insn_to_args.get(arg_name) + + if existing_relation is not None: + access_map |= existing_relation + + self._access_maps = self._access_maps.set( + insn_id, self._access_maps[insn_id].set( + arg_name, access_map)) + + else: + self._access_maps = self._access_maps.set( + insn_id, pmap({arg_name: access_map})) + + def map_linear_subscript(self, expr, insn_id): + raise NotImplementedError("linear subscripts cannot be used with " + "precise dependency finding. Use " + "multidimensional accesses to take advantage " + "of this feature.") + + def map_reduction(self, expr, insn_id): + return WalkMapper.map_reduction(self, expr, insn_id) + + def map_type_cast(self, expr, insn_id): + return self.rec(expr.child, insn_id) + + def map_sub_array_ref(self, expr, insn_id): + raise NotImplementedError("Not yet implemented") @for_each_kernel -def narrow_dependencies(kernel: LoopKernel) -> None: - pass +def narrow_dependencies(knl: LoopKernel) -> LoopKernel: + """Attempt to relax the dependency requirements between instructions in + a loopy program. Computes the precise, statement-instance level dependencies + between statements by using the affine array accesses of each statement. The + :attr:`loopy.Instruction.happens_after` of each instruction is updated + accordingly. + + .. note:: Requires an existing :attr:`loopy.Instruction.happens_after` to be + defined. + """ + + def make_inames_unique(relation: isl.Map) -> isl.Map: + """Append a single quote to all inames in the output of a map to ensure + input/output inames do not match + """ + for idim in range(relation.dim(dim_type.out)): + iname = relation.get_dim_name(dim_type.out, idim) + "'" + relation = relation.set_dim_name(dim_type.out, iname) + + return relation + + # precompute access maps for each instruction + amf = AccessMapFinder(knl) + for insn in knl.instructions: + amf(insn.assignee, insn.id) + amf(insn.expression, insn.id) + + # determine transitive dependencies between statements + transitive_deps = {} + + deps_dag = {insn.id: insn.depends_on for insn in knl.instructions} + + from pytools.graph import compute_topological_order + t_sort = compute_topological_order(deps_dag) + + for insn_id in t_sort: + transitive_deps[insn_id] = reduce( + frozenset.union, + (transitive_deps.get(dep, frozenset([dep])) + for dep in + knl.id_to_insn[insn_id].depends_on), + frozenset()) + + # compute and store precise dependencies + new_insns = [] + for insn in knl.instructions: + happens_after = insn.happens_after + + # get access maps + for dependency, variable in happens_after: + assert isinstance(insn.id, str) # stop complaints + + after_map = amf.get_map(insn.id, variable) + before_map = amf.get_map(dependency, variable) + + # scalar case(s) + if before_map is None or after_map is None: + warn("unable to determine the access map for %s. Defaulting to " + "a conservative dependency relation between %s and %s" % + (dependency, insn.id, dependency)) + + # clean up any deps that do not contain a variable name + happens_after = {insn_id: dep + for insn_id, dep in happens_after.items() + if dep.variable_name is not None} + + continue + + dims = [before_map.dim(dim_type.out), + before_map.dim(dim_type.in_), + after_map.dim(dim_type.out), + after_map.dim(dim_type.in_)] + + for i in range(len(dims)): + if i == 0 or i == 1: + scalar_insn = dependency + else: + scalar_insn = insn.id + + if dims[i] == 0: + warn("found evidence of a scalar access in %s. Defaulting " + "to a conservative dependency relation between " + "%s and %s" % (scalar_insn, dependency, insn.id)) + + # clean up any deps that do not contain a variable name + happens_after = {insn_id: dep + for insn_id, dep in happens_after.items() + if dep.variable_name is not None} + + continue + + # non-scalar accesses + dep_map = after_map.apply_range(before_map.reverse()) + dep_map -= dep_map.identity(dep_map.get_space()) + + if insn.happens_after[dependency] is not None: + lex_map = insn.happens_after[dependency].instances_rel + else: + lex_map = dep_map.lex_lt_map(dep_map) + + assert lex_map is not None + if lex_map.space != dep_map.space: + lex_map = make_inames_unique(lex_map) + dep_map = make_inames_unique(dep_map) + + lex_map, dep_map = isl.align_two(lex_map, dep_map) + + dep_map = dep_map & lex_map + + happens_after = dict( + list(happens_after.items()) + + [HappensAfter(variable, dep_map)]) + + # clean up any deps that do not contain a variable name + happens_after = {insn_id: dep + for insn_id, dep in happens_after.items() + if dep.variable_name is not None} + + new_insns.append(insn.copy(happens_after=happens_after)) + + return knl.copy(instructions=new_insns) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index 281d65261..119f1e4f4 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -23,8 +23,8 @@ import sys import loopy as lp from loopy.kernel.dependency import add_lexicographic_happens_after, \ - narrow_dependencies - + find_data_dependencies +from loopy.transform.dependency import narrow_dependencies def test_lex_dependencies(): knl = lp.make_kernel( @@ -41,31 +41,42 @@ def test_lex_dependencies(): knl = add_lexicographic_happens_after(knl) -def test_data_dependencies(): - knl = lp.make_kernel( - [ - "{[a,b]: 0<=a,b<7}", - "{[i,j]: 0<=i,j rowstart = rowstarts[i] + <> rowend = rowstarts[i+1] + <> length = rowend - rowstart + y[i] = sum(j, values[rowstart+j] * x[colindices[rowstart + j]]) + end + """, name="spmv") - knl = add_lexicographic_happens_after(knl) - knl = narrow_dependencies(knl) + import numpy as np + k = lp.add_and_infer_dtypes(k, { + "values,x": np.float64, "rowstarts,colindices": k["spmv"].index_dtype + }) + + k = find_data_dependencies(k) + pu.db -def test_scalar_dependencies(): +def test_narrow_dependencies(): + pu.db knl = lp.make_kernel( - "{ [i]: i = 0 }", + "{ [i,j]: 0 <= i < n }", """ - a = 3 - b = a*2 + a = i + b = a + d = b + e = i - n + c = e + b """) knl = add_lexicographic_happens_after(knl) + knl = find_data_dependencies(knl) knl = narrow_dependencies(knl) From 8e0e384633d4c81011beec9800118b40c1005f80 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 8 May 2023 16:05:49 -0500 Subject: [PATCH 43/52] Use fine-grain dependency information that exists in the kernel when computing dependencies --- doc/ref_transform.rst | 4 + loopy/kernel/dependency.py | 58 -------- loopy/transform/dependency.py | 240 +++++++++++++++++++++++----------- test/test_dependencies.py | 65 ++++----- 4 files changed, 202 insertions(+), 165 deletions(-) diff --git a/doc/ref_transform.rst b/doc/ref_transform.rst index 9ef012d66..81d864a1f 100644 --- a/doc/ref_transform.rst +++ b/doc/ref_transform.rst @@ -13,6 +13,10 @@ Wrangling inames .. automodule:: loopy.transform.iname +Precise Dependency Finding +-------------------------- +.. automodule:: loopy.transform.dependency + Dealing with Substitution Rules ------------------------------- diff --git a/loopy/kernel/dependency.py b/loopy/kernel/dependency.py index 90e73bd17..5992a1cb1 100644 --- a/loopy/kernel/dependency.py +++ b/loopy/kernel/dependency.py @@ -1,6 +1,5 @@ """ .. autofunction:: add_lexicographic_happens_after -.. autofunction:: find_data_dependencies """ __copyright__ = "Copyright (C) 2023 Addison Alvey-Blanco" @@ -33,63 +32,6 @@ from loopy.translation_unit import for_each_kernel -@for_each_kernel -def find_data_dependencies(knl: LoopKernel) -> LoopKernel: - """Compute coarse-grained dependencies between statements in a Loopy program. - Finds data dependencies based on the Bernstein Condition, i.e., statement S2 - depends on statement S1 if the union of true, anti, and flow dependencies - between the two statements is non-empty and the instructions actually - execute. - """ - - reader_map = knl.reader_map() - writer_map = knl.writer_map() - - readers = {insn.id: insn.read_dependency_names() - insn.within_inames - for insn in knl.instructions} - writers = {insn.id: insn.write_dependency_names() - insn.within_inames - for insn in knl.instructions} - - ordered_insns = {insn.id: i for i, insn in enumerate(knl.instructions)} - - new_insns = [] - for insn in knl.instructions: - happens_after = insn.happens_after - - # read after write - for variable in readers[insn.id]: - for writer in writer_map.get(variable, set()) - {insn.id}: - if ordered_insns[writer] < ordered_insns[insn.id]: - happens_after = dict( - list(happens_after.items()) - + [(writer, HappensAfter(variable, None))]) - - for variable in writers[insn.id]: - # write after read - for reader in reader_map.get(variable, set()) - {insn.id}: - if ordered_insns[reader] < ordered_insns[insn.id]: - happens_after = dict( - list(happens_after.items()) - + [(reader, HappensAfter(variable, None))]) - - # write after write - for writer in writer_map.get(variable, set()) - {insn.id}: - if ordered_insns[writer] < ordered_insns[insn.id]: - happens_after = dict( - list(happens_after.items()) - + [(writer, HappensAfter(variable, None))]) - - # remove dependencies that do not specify a variable name - happens_after = { - insn_id: dependency - for insn_id, dependency in happens_after.items() - if dependency.variable_name is not None} - - new_insns.append(insn.copy(happens_after=happens_after)) - - return knl.copy(instructions=new_insns) - - @for_each_kernel def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel: """Construct a sequential dependency specification between each instruction diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index 0f9dd9e91..11cb71981 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -56,7 +56,7 @@ def __init__(self, knl: LoopKernel) -> None: super().__init__() - def get_map(self, insn_id: str, variable_name: str) -> isl.Map | None: + def get_map(self, insn_id: str, variable_name: str) -> isl.Map: """Retrieve an access map indexed by an instruction ID and variable name. """ @@ -123,20 +123,35 @@ def narrow_dependencies(knl: LoopKernel) -> LoopKernel: between statements by using the affine array accesses of each statement. The :attr:`loopy.Instruction.happens_after` of each instruction is updated accordingly. - - .. note:: Requires an existing :attr:`loopy.Instruction.happens_after` to be - defined. """ - def make_inames_unique(relation: isl.Map) -> isl.Map: - """Append a single quote to all inames in the output of a map to ensure - input/output inames do not match + from loopy.kernel.instruction import InstructionBase + from typing import Sequence + def propagate_dependencies(subset_insns: Sequence[InstructionBase], + dependencies: isl.Map) -> isl.Map: + """Use existing lexicographic happens after to adjust dependencies + between two statements based on the lexicographic order. """ - for idim in range(relation.dim(dim_type.out)): - iname = relation.get_dim_name(dim_type.out, idim) + "'" - relation = relation.set_dim_name(dim_type.out, iname) + insn = subset_insns[0] + id_before = subset_insns[1].id + assert isinstance(id_before, str) + + lex_map: isl.Map = insn.happens_after[id_before].instances_rel + + for i in range(1, len(subset_insns)-1): + insn = subset_insns[i] + id_before = subset_insns[i+1].id + assert isinstance(id_before, str) + + update = insn.happens_after[id_before].instances_rel + + lex_map = lex_map.apply_range(update) + + if dependencies is not None: + return dependencies & lex_map + else: + return lex_map - return relation # precompute access maps for each instruction amf = AccessMapFinder(knl) @@ -144,97 +159,170 @@ def make_inames_unique(relation: isl.Map) -> isl.Map: amf(insn.assignee, insn.id) amf(insn.expression, insn.id) - # determine transitive dependencies between statements - transitive_deps = {} + writer_map = knl.writer_map() + reader_map = knl.reader_map() - deps_dag = {insn.id: insn.depends_on for insn in knl.instructions} + writers = {insn.id: insn.write_dependency_names() - insn.within_inames + for insn in knl.instructions} + readers = {insn.id: insn.read_dependency_names() - insn.within_inames + for insn in knl.instructions} - from pytools.graph import compute_topological_order - t_sort = compute_topological_order(deps_dag) + # {{{ compute precise dependencies + # NOTE this is not permanent + enum_insns = {insn.id: n for n, insn in enumerate(knl.instructions)} - for insn_id in t_sort: - transitive_deps[insn_id] = reduce( - frozenset.union, - (transitive_deps.get(dep, frozenset([dep])) - for dep in - knl.id_to_insn[insn_id].depends_on), - frozenset()) - - # compute and store precise dependencies new_insns = [] for insn in knl.instructions: happens_after = insn.happens_after + assert isinstance(insn.id, str) + + # read-after-write + for variable in readers[insn.id]: + for writer in writer_map.get(variable, set()) - {insn.id}: + # NOTE not permanent + if enum_insns[insn.id] < enum_insns[writer]: + continue - # get access maps - for dependency, variable in happens_after: - assert isinstance(insn.id, str) # stop complaints + before_map = amf.get_map(writer, variable) + after_map = amf.get_map(insn.id, variable) - after_map = amf.get_map(insn.id, variable) - before_map = amf.get_map(dependency, variable) + # handle scalar / no map found cases + if after_map is None or before_map is None: + warn("found evidence of a scalar access. Defaulting to a " + f"conservative dependency relation between {insn.id} " + f"and {writer}") - # scalar case(s) - if before_map is None or after_map is None: - warn("unable to determine the access map for %s. Defaulting to " - "a conservative dependency relation between %s and %s" % - (dependency, insn.id, dependency)) + if writer in insn.happens_after: + dep_map = insn.happens_after[writer].instances_rel - # clean up any deps that do not contain a variable name - happens_after = {insn_id: dep - for insn_id, dep in happens_after.items() - if dep.variable_name is not None} + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] - continue + subset_insns = knl.instructions[ibefore:iafter+1][::-1] - dims = [before_map.dim(dim_type.out), - before_map.dim(dim_type.in_), - after_map.dim(dim_type.out), - after_map.dim(dim_type.in_)] + dep_map = propagate_dependencies(subset_insns, None) - for i in range(len(dims)): - if i == 0 or i == 1: - scalar_insn = dependency + # non-scalar dependencies else: - scalar_insn = insn.id + dep_map = after_map.apply_range(before_map.reverse()) + + # immediately preceding statement in program listing + if writer in happens_after: + lex_map = happens_after[writer].instances_rel + dep_map = lex_map & dep_map + + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] - if dims[i] == 0: - warn("found evidence of a scalar access in %s. Defaulting " - "to a conservative dependency relation between " - "%s and %s" % (scalar_insn, dependency, insn.id)) + subset_insns = knl.instructions[ibefore:iafter+1][::-1] - # clean up any deps that do not contain a variable name - happens_after = {insn_id: dep - for insn_id, dep in happens_after.items() - if dep.variable_name is not None} + dep_map = propagate_dependencies(subset_insns, dep_map) + happens_after = dict( + list(happens_after.items()) + + [(writer, HappensAfter(variable, dep_map))]) + + for variable in writers[insn.id]: + # write-after-read + for reader in reader_map.get(variable, set()) - {insn.id}: + # NOTE not permanent + if enum_insns[insn.id] < enum_insns[reader]: continue - # non-scalar accesses - dep_map = after_map.apply_range(before_map.reverse()) - dep_map -= dep_map.identity(dep_map.get_space()) + before_map = amf.get_map(reader, variable) + after_map = amf.get_map(insn.id, variable) + + # handle scalar / no map found cases + if after_map is None or before_map is None: + warn("found evidence of a scalar access. Defaulting to a " + f"conservative dependency relation between {insn.id} " + f"and {reader}") - if insn.happens_after[dependency] is not None: - lex_map = insn.happens_after[dependency].instances_rel - else: - lex_map = dep_map.lex_lt_map(dep_map) + if reader in insn.happens_after: + dep_map = insn.happens_after[reader].instances_rel - assert lex_map is not None - if lex_map.space != dep_map.space: - lex_map = make_inames_unique(lex_map) - dep_map = make_inames_unique(dep_map) + else: + ibefore = enum_insns[reader] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, None) + + # non-scalar dependencies + else: + dep_map = after_map.apply_range(before_map.reverse()) - lex_map, dep_map = isl.align_two(lex_map, dep_map) + # immediately preceding statement in program listing + if reader in happens_after: + lex_map = happens_after[reader].instances_rel + dep_map = lex_map & dep_map - dep_map = dep_map & lex_map + else: + ibefore = enum_insns[reader] + iafter = enum_insns[insn.id] - happens_after = dict( - list(happens_after.items()) - + [HappensAfter(variable, dep_map)]) + subset_insns = knl.instructions[ibefore:iafter+1][::-1] - # clean up any deps that do not contain a variable name - happens_after = {insn_id: dep - for insn_id, dep in happens_after.items() - if dep.variable_name is not None} + dep_map = propagate_dependencies(subset_insns, dep_map) + + happens_after = dict( + list(happens_after.items()) + + [(reader, HappensAfter(variable, dep_map))]) + + # write-after-read + for writer in writer_map.get(variable, set()) - {insn.id}: + # NOTE not permanent + if enum_insns[insn.id] < enum_insns[writer]: + continue + + before_map = amf.get_map(writer, variable) + after_map = amf.get_map(insn.id, variable) + + # handle scalar / no map found cases + if after_map is None or before_map is None: + warn("found evidence of a scalar access. Defaulting to a " + f"conservative dependency relation between {insn.id} " + f"and {writer}") + + if writer in insn.happens_after: + dep_map = insn.happens_after[writer].instances_rel + + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, None) + + # non-scalar dependencies + else: + dep_map = after_map.apply_range(before_map.reverse()) + + # immediately preceding statement in program listing + if writer in happens_after: + lex_map = happens_after[writer].instances_rel + dep_map = lex_map & dep_map + + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, dep_map) + + happens_after = dict( + list(happens_after.items()) + + [(writer, HappensAfter(variable, dep_map))]) new_insns.append(insn.copy(happens_after=happens_after)) + # }}} + + # {{{ handle transitive dependencies + # }}} return knl.copy(instructions=new_insns) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index 119f1e4f4..15213b14b 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -22,8 +22,7 @@ import sys import loopy as lp -from loopy.kernel.dependency import add_lexicographic_happens_after, \ - find_data_dependencies +from loopy.kernel.dependency import add_lexicographic_happens_after from loopy.transform.dependency import narrow_dependencies def test_lex_dependencies(): @@ -41,44 +40,48 @@ def test_lex_dependencies(): knl = add_lexicographic_happens_after(knl) -def test_find_dependencies(): - k = lp.make_kernel([ - "{ [i] : 0 <= i < m }", - "{ [j] : 0 <= j < length }"], - """ - for i - <> rowstart = rowstarts[i] - <> rowend = rowstarts[i+1] - <> length = rowend - rowstart - y[i] = sum(j, values[rowstart+j] * x[colindices[rowstart + j]]) - end - """, name="spmv") - - import numpy as np - k = lp.add_and_infer_dtypes(k, { - "values,x": np.float64, "rowstarts,colindices": k["spmv"].index_dtype - }) - - k = find_data_dependencies(k) - pu.db +def test_scalar_dependencies(): + knl = lp.make_kernel( + "{ [i]: 0 <= i < n }", + """ + <> a = 10*i + b = 9*n + i + c[0] = i + """) + knl = narrow_dependencies(knl) -def test_narrow_dependencies(): - pu.db +def test_narrow_simple(): knl = lp.make_kernel( - "{ [i,j]: 0 <= i < n }", + "{ [i,j,k]: 0 <= i,j,k < n }", """ - a = i - b = a - d = b - e = i - n - c = e + b + a[i,j] = 2*k + b[j,k] = i*j + k + c[k,j] = a[i,j] """) knl = add_lexicographic_happens_after(knl) - knl = find_data_dependencies(knl) knl = narrow_dependencies(knl) +def test_narrow_deps_spmv(): + knl = lp.make_kernel([ + "{ [i] : 0 <= i < m }", + "{ [j] : 0 <= j < length }"], + """ + for i + <> rowstart = rowstarts[i] + <> rowend = rowstarts[i+1] + <> length = rowend - rowstart + y[i] = sum(j, values[rowstart+j] * x[colindices[rowstart + j]]) + end + """, name="spmv") + + import numpy as np + knl = lp.add_and_infer_dtypes(knl, { + "values,x": np.float64, "rowstarts,colindices": knl["spmv"].index_dtype + }) + knl = add_lexicographic_happens_after(knl) + knl = narrow_dependencies(knl) if __name__ == "__main__": if len(sys.argv) > 1: From 69265b2e59abc659e3f84b0e5bb83298689b8b3c Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 8 May 2023 16:56:48 -0500 Subject: [PATCH 44/52] Fix failing pylint --- "\\" | 325 ++++++++++++++++++++++++++++++++++ loopy/check.py | 2 +- loopy/transform/dependency.py | 9 +- test/test_apps.py | 2 +- test/test_dependencies.py | 4 + 5 files changed, 333 insertions(+), 9 deletions(-) create mode 100644 "\\" diff --git "a/\\" "b/\\" new file mode 100644 index 000000000..f2e8f05dc --- /dev/null +++ "b/\\" @@ -0,0 +1,325 @@ +""" +.. autoclass:: AccessMapFinder +.. autofunction:: narrow_dependencies +""" +__copyright__ = "Copyright (C) 2022 Addison Alvey-Blanco" + +__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 islpy as isl +from islpy import dim_type + +from loopy.kernel.instruction import HappensAfter +from loopy.kernel import LoopKernel +from loopy.translation_unit import for_each_kernel +from loopy.symbolic import WalkMapper, get_access_map, \ + UnableToDetermineAccessRangeError +from loopy.typing import Expression + +import pymbolic.primitives as p +from typing import List, Dict +from pyrsistent import pmap, PMap +from warnings import warn +from functools import reduce + + +class AccessMapFinder(WalkMapper): + """Finds and stores relations representing the accesses to an array by + statement instances. Access maps can be found using an instruction's ID and + a variable's name. Essentially a specialized version of + BatchedAccessMapMapper. + """ + + def __init__(self, knl: LoopKernel) -> None: + self.kernel = knl + self._access_maps: PMap[str, PMap[str, isl.Map]] = pmap({}) + from collections import defaultdict # FIXME remove this + self.bad_subscripts: Dict[str, List[Expression]] = defaultdict(list) + + super().__init__() + + def get_map(self, insn_id: str, variable_name: str) -> isl.Map: + """Retrieve an access map indexed by an instruction ID and variable + name. + """ + try: + return self._access_maps[insn_id][variable_name] + except KeyError: + return None + + def map_subscript(self, expr, insn_id): + domain = self.kernel.get_inames_domain( + self.kernel.id_to_insn[insn_id].within_inames + ) + WalkMapper.map_subscript(self, expr, insn_id) + + assert isinstance(expr.aggregate, p.Variable) + + arg_name = expr.aggregate.name + subscript = expr.index_tuple + + try: + access_map = get_access_map( + domain, subscript, self.kernel.assumptions) + except UnableToDetermineAccessRangeError: + # may not have enough info to generate access map at current point + self.bad_subscripts[arg_name].append(expr) + return + + # analyze what we have in our access map dict before storing map + insn_to_args = self._access_maps.get(insn_id) + if insn_to_args is not None: + existing_relation = insn_to_args.get(arg_name) + + if existing_relation is not None: + access_map |= existing_relation + + self._access_maps = self._access_maps.set( + insn_id, self._access_maps[insn_id].set( + arg_name, access_map)) + + else: + self._access_maps = self._access_maps.set( + insn_id, pmap({arg_name: access_map})) + + def map_linear_subscript(self, expr, insn_id): + raise NotImplementedError("linear subscripts cannot be used with " + "precise dependency finding. Use " + "multidimensional accesses to take advantage " + "of this feature.") + + def map_reduction(self, expr, insn_id): + return WalkMapper.map_reduction(self, expr, insn_id) + + def map_type_cast(self, expr, insn_id): + return self.rec(expr.child, insn_id) + + def map_sub_array_ref(self, expr, insn_id): + raise NotImplementedError("Not yet implemented") + + +@for_each_kernel +def narrow_dependencies(knl: LoopKernel) -> LoopKernel: + """Attempt to relax the dependency requirements between instructions in + a loopy program. Computes the precise, statement-instance level dependencies + between statements by using the affine array accesses of each statement. The + :attr:`loopy.Instruction.happens_after` of each instruction is updated + accordingly. + """ + + def propagate_dependencies(subset_insns: Sequence[InstructionBase], + dependencies: isl.Map) -> isl.Map: + """Use existing lexicographic happens after to adjust dependencies + between two statements based on the lexicographic order. + """ + insn = subset_insns[0] + id_before = subset_insns[1].id + assert isinstance(id_before, str) + + lex_map: isl.Map = insn.happens_after[id_before].instances_rel + + for i in range(1, len(subset_insns)-1): + insn = subset_insns[i] + id_before = subset_insns[i+1].id + assert isinstance(id_before, str) + + update = insn.happens_after[id_before].instances_rel + + lex_map = lex_map.apply_range(update) + + if dependencies is not None: + return dependencies & lex_map + else: + return lex_map + + # precompute access maps for each instruction + amf = AccessMapFinder(knl) + for insn in knl.instructions: + amf(insn.assignee, insn.id) + amf(insn.expression, insn.id) + + writer_map = knl.writer_map() + reader_map = knl.reader_map() + + writers = {insn.id: insn.write_dependency_names() - insn.within_inames + for insn in knl.instructions} + readers = {insn.id: insn.read_dependency_names() - insn.within_inames + for insn in knl.instructions} + + # {{{ compute precise dependencies + # NOTE this is not permanent + enum_insns = {insn.id: n for n, insn in enumerate(knl.instructions)} + + new_insns = [] + for insn in knl.instructions: + happens_after = insn.happens_after + assert isinstance(insn.id, str) + + # read-after-write + for variable in readers[insn.id]: + for writer in writer_map.get(variable, set()) - {insn.id}: + # NOTE not permanent + if enum_insns[insn.id] < enum_insns[writer]: + continue + + before_map = amf.get_map(writer, variable) + after_map = amf.get_map(insn.id, variable) + + # handle scalar / no map found cases + if after_map is None or before_map is None: + warn("found evidence of a scalar access. Defaulting to a " + f"conservative dependency relation between {insn.id} " + f"and {writer}") + + if writer in insn.happens_after: + dep_map = insn.happens_after[writer].instances_rel + + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, None) + + # non-scalar dependencies + else: + dep_map = after_map.apply_range(before_map.reverse()) + + # immediately preceding statement in program listing + if writer in happens_after: + lex_map = happens_after[writer].instances_rel + dep_map = lex_map & dep_map + + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, dep_map) + + happens_after = dict( + list(happens_after.items()) + + [(writer, HappensAfter(variable, dep_map))]) + + for variable in writers[insn.id]: + # write-after-read + for reader in reader_map.get(variable, set()) - {insn.id}: + # NOTE not permanent + if enum_insns[insn.id] < enum_insns[reader]: + continue + + before_map = amf.get_map(reader, variable) + after_map = amf.get_map(insn.id, variable) + + # handle scalar / no map found cases + if after_map is None or before_map is None: + warn("found evidence of a scalar access. Defaulting to a " + f"conservative dependency relation between {insn.id} " + f"and {reader}") + + if reader in insn.happens_after: + dep_map = insn.happens_after[reader].instances_rel + + else: + ibefore = enum_insns[reader] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, None) + + # non-scalar dependencies + else: + dep_map = after_map.apply_range(before_map.reverse()) + + # immediately preceding statement in program listing + if reader in happens_after: + lex_map = happens_after[reader].instances_rel + dep_map = lex_map & dep_map + + else: + ibefore = enum_insns[reader] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, dep_map) + + happens_after = dict( + list(happens_after.items()) + + [(reader, HappensAfter(variable, dep_map))]) + + # write-after-read + for writer in writer_map.get(variable, set()) - {insn.id}: + # NOTE not permanent + if enum_insns[insn.id] < enum_insns[writer]: + continue + + before_map = amf.get_map(writer, variable) + after_map = amf.get_map(insn.id, variable) + + # handle scalar / no map found cases + if after_map is None or before_map is None: + warn("found evidence of a scalar access. Defaulting to a " + f"conservative dependency relation between {insn.id} " + f"and {writer}") + + if writer in insn.happens_after: + dep_map = insn.happens_after[writer].instances_rel + + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, None) + + # non-scalar dependencies + else: + dep_map = after_map.apply_range(before_map.reverse()) + + # immediately preceding statement in program listing + if writer in happens_after: + lex_map = happens_after[writer].instances_rel + dep_map = lex_map & dep_map + + else: + ibefore = enum_insns[writer] + iafter = enum_insns[insn.id] + + subset_insns = knl.instructions[ibefore:iafter+1][::-1] + + dep_map = propagate_dependencies(subset_insns, dep_map) + + happens_after = dict( + list(happens_after.items()) + + [(writer, HappensAfter(variable, dep_map))]) + + new_insns.append(insn.copy(happens_after=happens_after)) + # }}} + + # {{{ handle transitive dependencies + # }}} + + return knl.copy(instructions=new_insns) diff --git a/loopy/check.py b/loopy/check.py index d8c1656e1..3fdb99605 100644 --- a/loopy/check.py +++ b/loopy/check.py @@ -1057,7 +1057,7 @@ def _check_variable_access_ordered_inner(kernel): # depends_on: mapping from insn_ids to their dependencies depends_on = {insn.id: set() for insn in kernel.instructions} # rev_depends: mapping from insn_ids to their reverse deps. - rev_depends = {insn.id: set() for insn in kernel.instructions} + rev_depends = {insn.id: set() for insn in kernel.instructions} for insn in kernel.instructions: depends_on[insn.id].update(insn.depends_on) for dep in insn.depends_on: diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index 11cb71981..b95c53ed5 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -25,20 +25,18 @@ """ import islpy as isl -from islpy import dim_type from loopy.kernel.instruction import HappensAfter -from loopy.kernel import LoopKernel +from loopy.kernel import LoopKernel, InstructionBase from loopy.translation_unit import for_each_kernel from loopy.symbolic import WalkMapper, get_access_map, \ UnableToDetermineAccessRangeError from loopy.typing import Expression import pymbolic.primitives as p -from typing import List, Dict +from typing import List, Dict, Sequence from pyrsistent import pmap, PMap from warnings import warn -from functools import reduce class AccessMapFinder(WalkMapper): @@ -125,8 +123,6 @@ def narrow_dependencies(knl: LoopKernel) -> LoopKernel: accordingly. """ - from loopy.kernel.instruction import InstructionBase - from typing import Sequence def propagate_dependencies(subset_insns: Sequence[InstructionBase], dependencies: isl.Map) -> isl.Map: """Use existing lexicographic happens after to adjust dependencies @@ -152,7 +148,6 @@ def propagate_dependencies(subset_insns: Sequence[InstructionBase], else: return lex_map - # precompute access maps for each instruction amf = AccessMapFinder(knl) for insn in knl.instructions: diff --git a/test/test_apps.py b/test/test_apps.py index c4f3398b9..ad7879b1f 100644 --- a/test/test_apps.py +++ b/test/test_apps.py @@ -228,7 +228,7 @@ def test_rob_stroud_bernstein(): qpts=np.float32, coeffs=np.float32, tmp=np.float32, - )) + )) print(lp.generate_code_v2(knl)) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index 15213b14b..a925b6df2 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -25,6 +25,7 @@ from loopy.kernel.dependency import add_lexicographic_happens_after from loopy.transform.dependency import narrow_dependencies + def test_lex_dependencies(): knl = lp.make_kernel( [ @@ -51,6 +52,7 @@ def test_scalar_dependencies(): knl = narrow_dependencies(knl) + def test_narrow_simple(): knl = lp.make_kernel( "{ [i,j,k]: 0 <= i,j,k < n }", @@ -63,6 +65,7 @@ def test_narrow_simple(): knl = add_lexicographic_happens_after(knl) knl = narrow_dependencies(knl) + def test_narrow_deps_spmv(): knl = lp.make_kernel([ "{ [i] : 0 <= i < m }", @@ -83,6 +86,7 @@ def test_narrow_deps_spmv(): knl = add_lexicographic_happens_after(knl) knl = narrow_dependencies(knl) + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) From 8c42f693b878c4cc869bea40203753076f5f02b1 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 8 May 2023 16:57:43 -0500 Subject: [PATCH 45/52] Remove file that was accidentally created --- "\\" | 325 ----------------------------------------------------------- 1 file changed, 325 deletions(-) delete mode 100644 "\\" diff --git "a/\\" "b/\\" deleted file mode 100644 index f2e8f05dc..000000000 --- "a/\\" +++ /dev/null @@ -1,325 +0,0 @@ -""" -.. autoclass:: AccessMapFinder -.. autofunction:: narrow_dependencies -""" -__copyright__ = "Copyright (C) 2022 Addison Alvey-Blanco" - -__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 islpy as isl -from islpy import dim_type - -from loopy.kernel.instruction import HappensAfter -from loopy.kernel import LoopKernel -from loopy.translation_unit import for_each_kernel -from loopy.symbolic import WalkMapper, get_access_map, \ - UnableToDetermineAccessRangeError -from loopy.typing import Expression - -import pymbolic.primitives as p -from typing import List, Dict -from pyrsistent import pmap, PMap -from warnings import warn -from functools import reduce - - -class AccessMapFinder(WalkMapper): - """Finds and stores relations representing the accesses to an array by - statement instances. Access maps can be found using an instruction's ID and - a variable's name. Essentially a specialized version of - BatchedAccessMapMapper. - """ - - def __init__(self, knl: LoopKernel) -> None: - self.kernel = knl - self._access_maps: PMap[str, PMap[str, isl.Map]] = pmap({}) - from collections import defaultdict # FIXME remove this - self.bad_subscripts: Dict[str, List[Expression]] = defaultdict(list) - - super().__init__() - - def get_map(self, insn_id: str, variable_name: str) -> isl.Map: - """Retrieve an access map indexed by an instruction ID and variable - name. - """ - try: - return self._access_maps[insn_id][variable_name] - except KeyError: - return None - - def map_subscript(self, expr, insn_id): - domain = self.kernel.get_inames_domain( - self.kernel.id_to_insn[insn_id].within_inames - ) - WalkMapper.map_subscript(self, expr, insn_id) - - assert isinstance(expr.aggregate, p.Variable) - - arg_name = expr.aggregate.name - subscript = expr.index_tuple - - try: - access_map = get_access_map( - domain, subscript, self.kernel.assumptions) - except UnableToDetermineAccessRangeError: - # may not have enough info to generate access map at current point - self.bad_subscripts[arg_name].append(expr) - return - - # analyze what we have in our access map dict before storing map - insn_to_args = self._access_maps.get(insn_id) - if insn_to_args is not None: - existing_relation = insn_to_args.get(arg_name) - - if existing_relation is not None: - access_map |= existing_relation - - self._access_maps = self._access_maps.set( - insn_id, self._access_maps[insn_id].set( - arg_name, access_map)) - - else: - self._access_maps = self._access_maps.set( - insn_id, pmap({arg_name: access_map})) - - def map_linear_subscript(self, expr, insn_id): - raise NotImplementedError("linear subscripts cannot be used with " - "precise dependency finding. Use " - "multidimensional accesses to take advantage " - "of this feature.") - - def map_reduction(self, expr, insn_id): - return WalkMapper.map_reduction(self, expr, insn_id) - - def map_type_cast(self, expr, insn_id): - return self.rec(expr.child, insn_id) - - def map_sub_array_ref(self, expr, insn_id): - raise NotImplementedError("Not yet implemented") - - -@for_each_kernel -def narrow_dependencies(knl: LoopKernel) -> LoopKernel: - """Attempt to relax the dependency requirements between instructions in - a loopy program. Computes the precise, statement-instance level dependencies - between statements by using the affine array accesses of each statement. The - :attr:`loopy.Instruction.happens_after` of each instruction is updated - accordingly. - """ - - def propagate_dependencies(subset_insns: Sequence[InstructionBase], - dependencies: isl.Map) -> isl.Map: - """Use existing lexicographic happens after to adjust dependencies - between two statements based on the lexicographic order. - """ - insn = subset_insns[0] - id_before = subset_insns[1].id - assert isinstance(id_before, str) - - lex_map: isl.Map = insn.happens_after[id_before].instances_rel - - for i in range(1, len(subset_insns)-1): - insn = subset_insns[i] - id_before = subset_insns[i+1].id - assert isinstance(id_before, str) - - update = insn.happens_after[id_before].instances_rel - - lex_map = lex_map.apply_range(update) - - if dependencies is not None: - return dependencies & lex_map - else: - return lex_map - - # precompute access maps for each instruction - amf = AccessMapFinder(knl) - for insn in knl.instructions: - amf(insn.assignee, insn.id) - amf(insn.expression, insn.id) - - writer_map = knl.writer_map() - reader_map = knl.reader_map() - - writers = {insn.id: insn.write_dependency_names() - insn.within_inames - for insn in knl.instructions} - readers = {insn.id: insn.read_dependency_names() - insn.within_inames - for insn in knl.instructions} - - # {{{ compute precise dependencies - # NOTE this is not permanent - enum_insns = {insn.id: n for n, insn in enumerate(knl.instructions)} - - new_insns = [] - for insn in knl.instructions: - happens_after = insn.happens_after - assert isinstance(insn.id, str) - - # read-after-write - for variable in readers[insn.id]: - for writer in writer_map.get(variable, set()) - {insn.id}: - # NOTE not permanent - if enum_insns[insn.id] < enum_insns[writer]: - continue - - before_map = amf.get_map(writer, variable) - after_map = amf.get_map(insn.id, variable) - - # handle scalar / no map found cases - if after_map is None or before_map is None: - warn("found evidence of a scalar access. Defaulting to a " - f"conservative dependency relation between {insn.id} " - f"and {writer}") - - if writer in insn.happens_after: - dep_map = insn.happens_after[writer].instances_rel - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, None) - - # non-scalar dependencies - else: - dep_map = after_map.apply_range(before_map.reverse()) - - # immediately preceding statement in program listing - if writer in happens_after: - lex_map = happens_after[writer].instances_rel - dep_map = lex_map & dep_map - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, dep_map) - - happens_after = dict( - list(happens_after.items()) - + [(writer, HappensAfter(variable, dep_map))]) - - for variable in writers[insn.id]: - # write-after-read - for reader in reader_map.get(variable, set()) - {insn.id}: - # NOTE not permanent - if enum_insns[insn.id] < enum_insns[reader]: - continue - - before_map = amf.get_map(reader, variable) - after_map = amf.get_map(insn.id, variable) - - # handle scalar / no map found cases - if after_map is None or before_map is None: - warn("found evidence of a scalar access. Defaulting to a " - f"conservative dependency relation between {insn.id} " - f"and {reader}") - - if reader in insn.happens_after: - dep_map = insn.happens_after[reader].instances_rel - - else: - ibefore = enum_insns[reader] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, None) - - # non-scalar dependencies - else: - dep_map = after_map.apply_range(before_map.reverse()) - - # immediately preceding statement in program listing - if reader in happens_after: - lex_map = happens_after[reader].instances_rel - dep_map = lex_map & dep_map - - else: - ibefore = enum_insns[reader] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, dep_map) - - happens_after = dict( - list(happens_after.items()) - + [(reader, HappensAfter(variable, dep_map))]) - - # write-after-read - for writer in writer_map.get(variable, set()) - {insn.id}: - # NOTE not permanent - if enum_insns[insn.id] < enum_insns[writer]: - continue - - before_map = amf.get_map(writer, variable) - after_map = amf.get_map(insn.id, variable) - - # handle scalar / no map found cases - if after_map is None or before_map is None: - warn("found evidence of a scalar access. Defaulting to a " - f"conservative dependency relation between {insn.id} " - f"and {writer}") - - if writer in insn.happens_after: - dep_map = insn.happens_after[writer].instances_rel - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, None) - - # non-scalar dependencies - else: - dep_map = after_map.apply_range(before_map.reverse()) - - # immediately preceding statement in program listing - if writer in happens_after: - lex_map = happens_after[writer].instances_rel - dep_map = lex_map & dep_map - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, dep_map) - - happens_after = dict( - list(happens_after.items()) - + [(writer, HappensAfter(variable, dep_map))]) - - new_insns.append(insn.copy(happens_after=happens_after)) - # }}} - - # {{{ handle transitive dependencies - # }}} - - return knl.copy(instructions=new_insns) From 970feb05485edc6a71004cfe4a5330e0a3371bf0 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Thu, 8 Jun 2023 15:29:52 -0500 Subject: [PATCH 46/52] Some unimportant changes --- loopy/transform/dependency.py | 181 +--------------------------------- 1 file changed, 1 insertion(+), 180 deletions(-) diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index b95c53ed5..476a6961a 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -123,31 +123,6 @@ def narrow_dependencies(knl: LoopKernel) -> LoopKernel: accordingly. """ - def propagate_dependencies(subset_insns: Sequence[InstructionBase], - dependencies: isl.Map) -> isl.Map: - """Use existing lexicographic happens after to adjust dependencies - between two statements based on the lexicographic order. - """ - insn = subset_insns[0] - id_before = subset_insns[1].id - assert isinstance(id_before, str) - - lex_map: isl.Map = insn.happens_after[id_before].instances_rel - - for i in range(1, len(subset_insns)-1): - insn = subset_insns[i] - id_before = subset_insns[i+1].id - assert isinstance(id_before, str) - - update = insn.happens_after[id_before].instances_rel - - lex_map = lex_map.apply_range(update) - - if dependencies is not None: - return dependencies & lex_map - else: - return lex_map - # precompute access maps for each instruction amf = AccessMapFinder(knl) for insn in knl.instructions: @@ -162,162 +137,8 @@ def propagate_dependencies(subset_insns: Sequence[InstructionBase], readers = {insn.id: insn.read_dependency_names() - insn.within_inames for insn in knl.instructions} - # {{{ compute precise dependencies - # NOTE this is not permanent - enum_insns = {insn.id: n for n, insn in enumerate(knl.instructions)} + pu.db new_insns = [] - for insn in knl.instructions: - happens_after = insn.happens_after - assert isinstance(insn.id, str) - - # read-after-write - for variable in readers[insn.id]: - for writer in writer_map.get(variable, set()) - {insn.id}: - # NOTE not permanent - if enum_insns[insn.id] < enum_insns[writer]: - continue - - before_map = amf.get_map(writer, variable) - after_map = amf.get_map(insn.id, variable) - - # handle scalar / no map found cases - if after_map is None or before_map is None: - warn("found evidence of a scalar access. Defaulting to a " - f"conservative dependency relation between {insn.id} " - f"and {writer}") - - if writer in insn.happens_after: - dep_map = insn.happens_after[writer].instances_rel - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, None) - - # non-scalar dependencies - else: - dep_map = after_map.apply_range(before_map.reverse()) - - # immediately preceding statement in program listing - if writer in happens_after: - lex_map = happens_after[writer].instances_rel - dep_map = lex_map & dep_map - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, dep_map) - - happens_after = dict( - list(happens_after.items()) - + [(writer, HappensAfter(variable, dep_map))]) - - for variable in writers[insn.id]: - # write-after-read - for reader in reader_map.get(variable, set()) - {insn.id}: - # NOTE not permanent - if enum_insns[insn.id] < enum_insns[reader]: - continue - - before_map = amf.get_map(reader, variable) - after_map = amf.get_map(insn.id, variable) - - # handle scalar / no map found cases - if after_map is None or before_map is None: - warn("found evidence of a scalar access. Defaulting to a " - f"conservative dependency relation between {insn.id} " - f"and {reader}") - - if reader in insn.happens_after: - dep_map = insn.happens_after[reader].instances_rel - - else: - ibefore = enum_insns[reader] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, None) - - # non-scalar dependencies - else: - dep_map = after_map.apply_range(before_map.reverse()) - - # immediately preceding statement in program listing - if reader in happens_after: - lex_map = happens_after[reader].instances_rel - dep_map = lex_map & dep_map - - else: - ibefore = enum_insns[reader] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, dep_map) - - happens_after = dict( - list(happens_after.items()) - + [(reader, HappensAfter(variable, dep_map))]) - - # write-after-read - for writer in writer_map.get(variable, set()) - {insn.id}: - # NOTE not permanent - if enum_insns[insn.id] < enum_insns[writer]: - continue - - before_map = amf.get_map(writer, variable) - after_map = amf.get_map(insn.id, variable) - - # handle scalar / no map found cases - if after_map is None or before_map is None: - warn("found evidence of a scalar access. Defaulting to a " - f"conservative dependency relation between {insn.id} " - f"and {writer}") - - if writer in insn.happens_after: - dep_map = insn.happens_after[writer].instances_rel - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, None) - - # non-scalar dependencies - else: - dep_map = after_map.apply_range(before_map.reverse()) - - # immediately preceding statement in program listing - if writer in happens_after: - lex_map = happens_after[writer].instances_rel - dep_map = lex_map & dep_map - - else: - ibefore = enum_insns[writer] - iafter = enum_insns[insn.id] - - subset_insns = knl.instructions[ibefore:iafter+1][::-1] - - dep_map = propagate_dependencies(subset_insns, dep_map) - - happens_after = dict( - list(happens_after.items()) - + [(writer, HappensAfter(variable, dep_map))]) - - new_insns.append(insn.copy(happens_after=happens_after)) - # }}} - - # {{{ handle transitive dependencies - # }}} return knl.copy(instructions=new_insns) From 5b98c841bd2b7a9cb315149dfa7ee98df3597391 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Fri, 9 Jun 2023 13:33:49 -0500 Subject: [PATCH 47/52] Remove unnecessary tests --- test/test_dependencies.py | 47 --------------------------------------- 1 file changed, 47 deletions(-) diff --git a/test/test_dependencies.py b/test/test_dependencies.py index a925b6df2..3860d38fd 100644 --- a/test/test_dependencies.py +++ b/test/test_dependencies.py @@ -23,7 +23,6 @@ import sys import loopy as lp from loopy.kernel.dependency import add_lexicographic_happens_after -from loopy.transform.dependency import narrow_dependencies def test_lex_dependencies(): @@ -41,52 +40,6 @@ def test_lex_dependencies(): knl = add_lexicographic_happens_after(knl) -def test_scalar_dependencies(): - knl = lp.make_kernel( - "{ [i]: 0 <= i < n }", - """ - <> a = 10*i - b = 9*n + i - c[0] = i - """) - - knl = narrow_dependencies(knl) - - -def test_narrow_simple(): - knl = lp.make_kernel( - "{ [i,j,k]: 0 <= i,j,k < n }", - """ - a[i,j] = 2*k - b[j,k] = i*j + k - c[k,j] = a[i,j] - """) - - knl = add_lexicographic_happens_after(knl) - knl = narrow_dependencies(knl) - - -def test_narrow_deps_spmv(): - knl = lp.make_kernel([ - "{ [i] : 0 <= i < m }", - "{ [j] : 0 <= j < length }"], - """ - for i - <> rowstart = rowstarts[i] - <> rowend = rowstarts[i+1] - <> length = rowend - rowstart - y[i] = sum(j, values[rowstart+j] * x[colindices[rowstart + j]]) - end - """, name="spmv") - - import numpy as np - knl = lp.add_and_infer_dtypes(knl, { - "values,x": np.float64, "rowstarts,colindices": knl["spmv"].index_dtype - }) - knl = add_lexicographic_happens_after(knl) - knl = narrow_dependencies(knl) - - if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) From 20432332445862072ed558e4ca0d3e352d786f80 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 12 Jun 2023 21:19:21 -0500 Subject: [PATCH 48/52] Fix failing doctests --- loopy/kernel/instruction.py | 3 +-- loopy/transform/dependency.py | 3 +-- pytest.xml | 1 + test/pytest.xml | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 pytest.xml create mode 100644 test/pytest.xml diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index a3c788217..723ce5e27 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -91,8 +91,7 @@ class HappensAfter: .. attribute:: variable_name - If this is a precise dependency, the name of the variable name in - :attr:`after_id` that mediates the dependency. + The name of the variable responsible for the dependency. .. attribute:: instances_rel diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index 476a6961a..8e24e750c 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -119,8 +119,7 @@ def narrow_dependencies(knl: LoopKernel) -> LoopKernel: """Attempt to relax the dependency requirements between instructions in a loopy program. Computes the precise, statement-instance level dependencies between statements by using the affine array accesses of each statement. The - :attr:`loopy.Instruction.happens_after` of each instruction is updated - accordingly. + happens_after of each instruction is updated accordingly. """ # precompute access maps for each instruction diff --git a/pytest.xml b/pytest.xml new file mode 100644 index 000000000..fcbff7557 --- /dev/null +++ b/pytest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/pytest.xml b/test/pytest.xml new file mode 100644 index 000000000..fc6dd2f20 --- /dev/null +++ b/test/pytest.xml @@ -0,0 +1 @@ +/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_apps.py:390: takes very long to compile on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_linalg.py:205: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_fortran.py:377: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_linalg.py:476: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_linalg.py:528: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_loopy.py:2641: Not investing time in passing test depends on feature which was deprecated in 2016worker 'gw0' crashed while running "test/test_target.py::test_passing_bajillions_of_svm_args[<context factory for <pyopencl.Device 'pthread-Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz' on 'Portable Computing Language'>>-False]"/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_numa_diff.py:57: takes a very long time to compile on poclworker 'gw3' crashed while running "test/test_target.py::test_passing_bajillions_of_svm_args[<context factory for <pyopencl.Device 'pthread-Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz' on 'Portable Computing Language'>>-True]" \ No newline at end of file From 1cea1b3d33ded40944c59216696f5be7b793b129 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 12 Jun 2023 21:21:34 -0500 Subject: [PATCH 49/52] Remove some (currently) unnecessary changes --- loopy/transform/dependency.py | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index 8e24e750c..106892359 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -26,17 +26,15 @@ import islpy as isl -from loopy.kernel.instruction import HappensAfter -from loopy.kernel import LoopKernel, InstructionBase +from loopy.kernel import LoopKernel from loopy.translation_unit import for_each_kernel from loopy.symbolic import WalkMapper, get_access_map, \ UnableToDetermineAccessRangeError from loopy.typing import Expression import pymbolic.primitives as p -from typing import List, Dict, Sequence +from typing import List, Dict from pyrsistent import pmap, PMap -from warnings import warn class AccessMapFinder(WalkMapper): @@ -115,29 +113,10 @@ def map_sub_array_ref(self, expr, insn_id): @for_each_kernel -def narrow_dependencies(knl: LoopKernel) -> LoopKernel: +def narrow_dependencies(knl: LoopKernel): """Attempt to relax the dependency requirements between instructions in a loopy program. Computes the precise, statement-instance level dependencies between statements by using the affine array accesses of each statement. The happens_after of each instruction is updated accordingly. """ - - # precompute access maps for each instruction - amf = AccessMapFinder(knl) - for insn in knl.instructions: - amf(insn.assignee, insn.id) - amf(insn.expression, insn.id) - - writer_map = knl.writer_map() - reader_map = knl.reader_map() - - writers = {insn.id: insn.write_dependency_names() - insn.within_inames - for insn in knl.instructions} - readers = {insn.id: insn.read_dependency_names() - insn.within_inames - for insn in knl.instructions} - - pu.db - - new_insns = [] - - return knl.copy(instructions=new_insns) + pass From 9131461f27ff4d3f317e8e678e12f10e0ee6dca9 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 12 Jun 2023 21:22:54 -0500 Subject: [PATCH 50/52] Remove accidentally added files --- pytest.xml | 1 - test/pytest.xml | 1 - 2 files changed, 2 deletions(-) delete mode 100644 pytest.xml delete mode 100644 test/pytest.xml diff --git a/pytest.xml b/pytest.xml deleted file mode 100644 index fcbff7557..000000000 --- a/pytest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/pytest.xml b/test/pytest.xml deleted file mode 100644 index fc6dd2f20..000000000 --- a/test/pytest.xml +++ /dev/null @@ -1 +0,0 @@ -/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_apps.py:390: takes very long to compile on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_linalg.py:205: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_fortran.py:377: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_linalg.py:476: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_linalg.py:528: crashes on pocl/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_loopy.py:2641: Not investing time in passing test depends on feature which was deprecated in 2016worker 'gw0' crashed while running "test/test_target.py::test_passing_bajillions_of_svm_args[<context factory for <pyopencl.Device 'pthread-Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz' on 'Portable Computing Language'>>-False]"/home/aj/Dropbox/grad_school/research/cs597/loopy/test/test_numa_diff.py:57: takes a very long time to compile on poclworker 'gw3' crashed while running "test/test_target.py::test_passing_bajillions_of_svm_args[<context factory for <pyopencl.Device 'pthread-Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz' on 'Portable Computing Language'>>-True]" \ No newline at end of file From 290c91eae7baecacad3a7f51f39749042aa5f600 Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 12 Jun 2023 21:31:14 -0500 Subject: [PATCH 51/52] Remove more (temporarily) unnecessary code to prep for compatibility branch --- loopy/transform/dependency.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index 106892359..259d63ec7 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -110,13 +110,3 @@ def map_type_cast(self, expr, insn_id): def map_sub_array_ref(self, expr, insn_id): raise NotImplementedError("Not yet implemented") - - -@for_each_kernel -def narrow_dependencies(knl: LoopKernel): - """Attempt to relax the dependency requirements between instructions in - a loopy program. Computes the precise, statement-instance level dependencies - between statements by using the affine array accesses of each statement. The - happens_after of each instruction is updated accordingly. - """ - pass From 7cd7ea00d0f7e0f3916e8dafea52a8983f20407c Mon Sep 17 00:00:00 2001 From: Addison Alvey-Blanco Date: Mon, 12 Jun 2023 21:32:13 -0500 Subject: [PATCH 52/52] Fix failing doctest --- loopy/transform/dependency.py | 1 - 1 file changed, 1 deletion(-) diff --git a/loopy/transform/dependency.py b/loopy/transform/dependency.py index 259d63ec7..588dc08cb 100644 --- a/loopy/transform/dependency.py +++ b/loopy/transform/dependency.py @@ -1,6 +1,5 @@ """ .. autoclass:: AccessMapFinder -.. autofunction:: narrow_dependencies """ __copyright__ = "Copyright (C) 2022 Addison Alvey-Blanco"