Reference Guide  2.5.0
region_trans.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2017-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 R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
35 # J. Henrichs, Bureau of Meteorology
36 # Modified I. Kavcic, Met Office
37 
38 '''This module contains the base class RegionTrans. All transformations which
39  act on a region of code (list of PSyIR nodes) sub-class this one.
40 '''
41 
42 import abc
43 
44 from psyclone.psyGen import Kern, Transformation
46  import TransformationError
47 from psyclone.psyir.nodes import Schedule, Node
48 
49 
50 class RegionTrans(Transformation, metaclass=abc.ABCMeta):
51  # Avoid pylint warning about abstract functions (apply, name) not
52  # overwritten:
53  # pylint: disable=abstract-method,arguments-differ
54  '''
55  This abstract class is a base class for all transformations that act
56  on a list of nodes. It gives access to a validate function that
57  makes sure that the nodes in the list are in the same order as in
58  the original AST, no node is duplicated, and that all nodes have
59  the same parent. We also check that all nodes to be enclosed are
60  valid for this transformation - this requires that the sub-class
61  populate the `excluded_node_types` tuple.
62 
63  '''
64  # The types of Node that are excluded from within this region. Must be
65  # populated by sub-class.
66  excluded_node_types = ()
67 
68  def get_node_list(self, nodes):
69  '''This is a helper function for region based transformations.
70  The parameter for any of those transformations is either a single
71  node, a schedule, or a list of nodes. This function converts this
72  into a list of nodes according to the parameter type. This function
73  will always return a copy, to avoid issues e.g. if a child list
74  of a node should be provided, and a transformation changes the order
75  in this list (which would then also change the order of the
76  nodes in the tree).
77 
78  :param nodes: can be a single node, a schedule or a list of nodes.
79  :type nodes: Union[:py:obj:`psyclone.psyir.nodes.Node`,
80  :py:obj:`psyclone.psyir.nodes.Schedule`,
81  List[:py:obj:`psyclone.psyir.nodes.Node`]
82  :param options: a dictionary with options for transformations.
83  :type options: Optional[Dict[str,Any]]
84 
85  :returns: a list of nodes.
86  :rtype: List[:py:class:`psyclone.psyir.nodes.Node`]
87 
88  :raises TransformationError: if the supplied parameter is neither a \
89  single Node, nor a Schedule, nor a list of Nodes.
90 
91  '''
92  if isinstance(nodes, list) and \
93  all(isinstance(node, Node) for node in nodes):
94  # We still need to return a copy, since the user might have
95  # provided Node.children as parameter.
96  return nodes[:]
97  if isinstance(nodes, Schedule):
98  # We've been passed a Schedule so default to enclosing its
99  # children.
100  return nodes.children[:]
101  if isinstance(nodes, Node):
102  # Single node that's not a Schedule
103  return [nodes]
104 
105  arg_type = str(type(nodes))
106  raise TransformationError(f"Error in {self.name}: "
107  f"Argument must be a single Node in a "
108  f"Schedule, a Schedule or a list of Nodes "
109  f"in a Schedule but have been passed an "
110  f"object of type: {arg_type}")
111 
112  def validate(self, nodes, options=None):
113  '''Checks that the nodes in node_list are valid for a region
114  transformation.
115 
116  :param nodes: can be a single node, a schedule or a list of nodes.
117  :type nodes: Union[:py:obj:`psyclone.psyir.nodes.Node`,
118  :py:obj:`psyclone.psyir.nodes.Schedule`,
119  List[:py:obj:`psyclone.psyir.nodes.Node`]
120  :param options: a dictionary with options for transformations.
121  :type options: Optional[Dict[str,Any]]
122  :param bool options["node-type-check"]: this flag controls if the \
123  type of the nodes enclosed in the region should be tested \
124  to avoid using unsupported nodes inside a region.
125 
126  :raises TransformationError: if the nodes in the list are not \
127  in the original order in which they are in the AST, \
128  a node is duplicated or the nodes have different parents.
129  :raises TransformationError: if any of the nodes to be enclosed in \
130  the region are of an unsupported type.
131  :raises TransformationError: if the parent of the supplied Nodes is \
132  not a Schedule or a Directive.
133  :raises TransformationError: if the nodes are in a NEMO \
134  Schedule and the transformation acts on the child of a \
135  single-line If or Where statment.
136  :raises TransformationError: if the supplied options are not a \
137  dictionary.
138 
139  '''
140  # pylint: disable=too-many-branches
141 
142  # Ensure we are always validating a list of nodes, even if only
143  # one was supplied via the `node_list` argument.
144  node_list = self.get_node_listget_node_list(nodes)
145 
146  if not options:
147  options = {}
148  if not isinstance(options, dict):
149  raise TransformationError(
150  f"Transformation apply method options argument must be a "
151  f"dictionary but found '{type(options).__name__}'.")
152  node_parent = node_list[0].parent
153  prev_position = -1
154  for child in node_list:
155  if child.parent is not node_parent:
156  raise TransformationError(
157  f"Error in {self.name} transformation: supplied nodes "
158  f"are not children of the same parent.")
159  if prev_position >= 0 and prev_position+1 != child.position:
160  raise TransformationError(
161  f"Children are not consecutive children of one parent: "
162  f"child '{child}' has position {child.position}, but "
163  f"previous child had position {prev_position}.")
164  prev_position = child.position
165 
166  # Check that the proposed region contains only supported node types
167  if options.get("node-type-check", True):
168  for child in node_list:
169  # Stop at any instance of Kern to avoid going into the
170  # actual kernels, e.g. in Nemo inlined kernels
171  flat_list = [item for item in child.walk(object, Kern)
172  if not isinstance(item, Schedule)]
173  for item in flat_list:
174  if isinstance(item, self.excluded_node_typesexcluded_node_types):
175  raise TransformationError(
176  f"Nodes of type '{type(item).__name__}' cannot be "
177  f"enclosed by a {self.name} transformation")
178 
179  # If we've been passed a list that contains one or more Schedules
180  # then something is wrong. e.g. two Schedules that are both children
181  # of an IfBlock would imply that the transformation is being applied
182  # around both the if-body and the else-body and that doesn't make
183  # sense.
184  if (len(node_list) > 1 and
185  any([isinstance(node, Schedule) for node in node_list])):
186  raise TransformationError(
187  "Cannot apply a transformation to multiple nodes when one "
188  "or more is a Schedule. Either target a single Schedule "
189  "or the children of a Schedule.")
190 
191  # Sanity check that we've not been passed the condition part of
192  # an If statement or the bounds of a Loop. If the parent node is
193  # a Loop or IfBlock then we can only accept a single Schedule.
194  if not isinstance(node_parent, Schedule) and \
195  not isinstance(node_list[0], Schedule):
196  # We've already checked for lists with len > 1 that contain a
197  # Schedule above so if the first item is a Schedule then that's
198  # all the list contains.
199  raise TransformationError(
200  "Cannot apply transformation to the immediate children of a "
201  "Loop/IfBlock unless it is to a single Schedule representing"
202  " the Loop/If/Else body.")