Reference Guide  2.5.0
loop_trans.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2021-2024, Science and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Authors: A. R. Porter, N. Nobre and S. Siso, STFC Daresbury Lab
35 
36 '''This module contains the base class LoopTrans. All transformations which
37  act on a loop sub-class this one.
38 '''
39 
40 import abc
41 
42 from psyclone.psyGen import Kern, Transformation
44  import TransformationError
45 from psyclone.psyir.nodes import Schedule, Loop
46 
47 
48 class LoopTrans(Transformation, metaclass=abc.ABCMeta):
49  # Avoid pylint warning about abstract method (apply) not overwritten:
50  # pylint: disable=abstract-method
51  '''
52  This abstract class is a base class for all transformations that act
53  on a Loop node. It gives access to a validate function that
54  makes sure that the supplied node is a Loop.
55  We also check that all nodes to be enclosed are valid for this
56  transformation - this requires that the sub-class populate the
57  `excluded_node_types` tuple.
58 
59  '''
60  # The types of Node that are excluded from within the target loop. Must be
61  # populated by sub-class.
62  excluded_node_types = ()
63 
64  def validate(self, node, options=None):
65  '''Checks that the supplied node is a valid target for a loop
66  transformation.
67 
68  :param node: target PSyIR node.
69  :type node: subclass of :py:class:`psyclone.psyir.nodes.Node`
70  :param options: a dictionary with options for transformations.
71  :type options: Optional[Dict[str, Any]]
72  :param bool options["node-type-check"]: this flag controls if the \
73  type of the nodes enclosed in the loop should be tested to \
74  avoid including unsupported nodes in a transformation.
75 
76  :raises TransformationError: if the supplied node is not a (fully- \
77  formed) Loop.
78  :raises TransformationError: if any of the nodes within the loop are \
79  of an unsupported type.
80  :raises TransformationError: if the loop is of 'null' type.
81  :raises TransformationError: if the supplied options are not a \
82  dictionary.
83 
84  '''
85  # pylint: disable=too-many-branches
86  if not isinstance(node, Loop):
87  raise TransformationError(
88  f"Target of {self.name} transformation must be a sub-class of "
89  f"Loop but got '{type(node).__name__}'")
90 
91  # The loop must be fully-formed.
92  if len(node.children) != 4:
93  raise TransformationError(
94  f"Error in {self.name} transformation. The target loop "
95  f"must have four children but found: "
96  f"{[type(child).__name__ for child in node.children]}.")
97 
98  if not options:
99  options = {}
100  if not isinstance(options, dict):
101  raise TransformationError(
102  f"Transformation validate method 'options' argument must be a "
103  f"dictionary but found '{type(options).__name__}'.")
104 
105  # Check that the proposed region contains only supported node types
106  if options.get("node-type-check", True):
107  # Stop at any instance of Kern to avoid going into the
108  # actual kernels, e.g. in Nemo inlined kernels
109  flat_list = [item for item in node.walk(object, stop_type=Kern)
110  if not isinstance(item, Schedule)]
111  for item in flat_list:
112  if isinstance(item, self.excluded_node_typesexcluded_node_types):
113  raise TransformationError(
114  f"Nodes of type '{type(item).__name__}' cannot be "
115  f"enclosed by a {self.name} transformation")
116 
117  # Disable warning to avoid circular dependency
118  # pylint: disable=import-outside-toplevel
119  from psyclone.domain.common.psylayer import PSyLoop
120 
121  # A 'null' loop is one which exists in the PSyIR hierarchy (mainly for
122  # halo-exchange logic) but does *not* correspond to an actual loop
123  # in the code that is generated for the PSy layer.
124 
125  # TODO 1756: PSyLoop is a PSy-layer concept and 'null' is only defined
126  # in LFRic. Maybe a generic transformation validation is not the best
127  # place for this check.
128  if isinstance(node, PSyLoop) and node.loop_type == 'null':
129  raise TransformationError(
130  f"Cannot apply a {self.name} transformation to a 'null' loop.")
131 
132  @property
133  def name(self):
134  '''
135  :returns: the name of this class.
136  :rtype: str
137  '''
138  return self.__class__.__name__
139 
140 
141 # For Sphinx AutoAPI documentation generation
142 __all__ = ["LoopTrans"]