Reference Guide  2.5.0
parallel_loop_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 # A. B. G. Chalk STFC Daresbury Lab
36 # J. Henrichs, Bureau of Meteorology
37 # Modified I. Kavcic, Met Office
38 
39 ''' This module provides the ParallelLoopTrans transformation.'''
40 
41 import abc
42 
43 from psyclone import psyGen
44 from psyclone.domain.common.psylayer import PSyLoop
45 from psyclone.psyir import nodes
46 from psyclone.psyir.nodes import Loop
47 from psyclone.psyir.tools import DependencyTools, DTCode
50  TransformationError
51 
52 
53 class ParallelLoopTrans(LoopTrans, metaclass=abc.ABCMeta):
54  '''
55  Adds an abstract directive (it needs to be specified by sub-classing this
56  transformation) to a loop indicating that it should be parallelised. It
57  performs some data dependency checks to guarantee that the loop can be
58  parallelised without changing the semantics of it.
59 
60  '''
61  # The types of node that must be excluded from the section of PSyIR
62  # being transformed.
63  excluded_node_types = (nodes.Return, psyGen.HaloExchange, nodes.CodeBlock)
64 
65  @abc.abstractmethod
66  def _directive(self, children, collapse=None):
67  '''
68  Returns the directive object to insert into the Schedule.
69  Must be implemented by sub-class.
70 
71  :param children: list of nodes that will be children of this Directive.
72  :type children: list of :py:class:`psyclone.psyir.nodes.Node`
73  :param int collapse: the number of tightly-nested loops to which \
74  this directive applies or None.
75 
76  :returns: the new Directive node.
77  :rtype: sub-class of :py:class:`psyclone.psyir.nodes.Directive`.
78  '''
79 
80  def validate(self, node, options=None):
81  '''
82  Perform validation checks before applying the transformation
83 
84  :param node: the node we are checking.
85  :type node: :py:class:`psyclone.psyir.nodes.Node`
86  :param options: a dictionary with options for transformations.\
87  This transform supports "collapse", which is the\
88  number of nested loops to collapse.
89  :type options: Optional[Dict[str, Any]]
90  :param int options["collapse"]: number of nested loops to collapse
91  or None.
92  :param bool options["force"]: whether to force parallelisation of the
93  target loop (i.e. ignore any dependence analysis).
94  :param bool options["sequential"]: whether this is a sequential loop.
95 
96  :raises TransformationError: if the \
97  :py:class:`psyclone.psyir.nodes.Loop` loop iterates over \
98  colours.
99  :raises TransformationError: if 'collapse' is supplied with an \
100  invalid number of loops.
101  :raises TransformationError: if there is a data dependency that \
102  prevents the parallelisation of the loop unless \
103  `options["force"]` is True.
104 
105  '''
106  # Check that the supplied node is a Loop and does not contain any
107  # unsupported nodes.
108  super().validate(node, options=options)
109 
110  if not options:
111  options = {}
112  collapse = options.get("collapse", None)
113  ignore_dep_analysis = options.get("force", False)
114  sequential = options.get("sequential", False)
115 
116  # Check we are not a sequential loop
117  if (not sequential and isinstance(node, PSyLoop) and
118  node.loop_type == 'colours'):
119  raise TransformationError(f"Error in {self.name} transformation. "
120  f"The target loop is over colours and "
121  f"must be computed serially.")
122 
123  # If 'collapse' is specified, check that it is an int and that the
124  # loop nest has at least that number of loops in it
125  if collapse:
126  if not isinstance(collapse, int):
127  raise TransformationError(
128  f"The 'collapse' argument must be an integer but got an "
129  f"object of type {type(collapse)}")
130  if collapse < 2:
131  raise TransformationError(
132  f"It only makes sense to collapse 2 or more loops "
133  f"but got a value of {collapse}")
134  # Count the number of loops in the loop nest
135  loop_count = 0
136  cnode = node
137  while isinstance(cnode, Loop):
138  loop_count += 1
139  # Loops must be tightly nested (no intervening statements)
140  cnode = cnode.loop_body[0]
141  if collapse > loop_count:
142  raise TransformationError(
143  f"Cannot apply COLLAPSE({collapse}) clause to a loop nest "
144  f"containing only {loop_count} loops")
145 
146  # Check that there are no loop-carried dependencies
147  if sequential or ignore_dep_analysis:
148  return
149 
150  dep_tools = DependencyTools()
151 
152  if not node.independent_iterations(dep_tools=dep_tools,
153  test_all_variables=True):
154  # The DependencyTools also returns False for things that are
155  # not an issue, so we ignore specific messages.
156  for message in dep_tools.get_all_messages():
157  if message.code == DTCode.WARN_SCALAR_WRITTEN_ONCE:
158  continue
159  all_msg_str = [str(message) for message in
160  dep_tools.get_all_messages()]
161  messages = "\n".join(all_msg_str)
162  raise TransformationError(
163  f"Dependency analysis failed with the following "
164  f"messages:\n{messages}")
165 
166  def apply(self, node, options=None):
167  '''
168  Apply the Loop transformation to the specified node in a
169  Schedule. This node must be a Loop since this transformation
170  corresponds to wrapping the generated code with directives,
171  e.g. for OpenMP:
172 
173  .. code-block:: fortran
174 
175  !$OMP DO
176  do ...
177  ...
178  end do
179  !$OMP END DO
180 
181  At code-generation time (when gen_code()` is called), this node must be
182  within (i.e. a child of) a PARALLEL region.
183 
184  :param node: the supplied node to which we will apply the \
185  Loop transformation.
186  :type node: :py:class:`psyclone.psyir.nodes.Node`
187  :param options: a dictionary with options for transformations. \
188  :type options: Optional[Dict[str, Any]]
189  :param int options["collapse"]: the number of loops to collapse into \
190  single iteration space or None.
191 
192  '''
193  if not options:
194  options = {}
195  self.validatevalidatevalidatevalidate(node, options=options)
196 
197  collapse = options.get("collapse", None)
198 
199  # keep a reference to the node's original parent and its index as these
200  # are required and will change when we change the node's location
201  node_parent = node.parent
202  node_position = node.position
203 
204  # Add our orphan loop directive setting its parent to the node's
205  # parent and its children to the node. This calls down to the sub-class
206  # to get the type of directive we require.
207  directive = self._directive_directive([node.detach()], collapse)
208 
209  # Add the loop directive as a child of the node's parent
210  node_parent.addchild(directive, index=node_position)
def validate(self, node, options=None)
Definition: psyGen.py:2783