Reference Guide  2.5.0
psyloop.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2022-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, A. B. G. Chalk and N. Nobre,
35 # STFC Daresbury Lab
36 # I. Kavcic and L. Turner, Met Office
37 # J. Henrichs, Bureau of Meteorology
38 # -----------------------------------------------------------------------------
39 
40 ''' This module contains the PSyLoop node implementation.'''
41 
42 from psyclone.core import AccessType
43 from psyclone.psyir.nodes import Routine, Loop
44 
45 
46 class PSyLoop(Loop):
47  # pylint: disable=too-many-instance-attributes
48  '''Node representing a psylayer loop within the PSyIR. It extends the PSyIR
49  loop construct with information about the domain-specific iteration space
50  that the loop is traversing and utility methods to interact with other
51  psylayer nodes.
52 
53  :param List[str] valid_loop_types: a list of loop types that are specific \
54  to a particular API.
55  :param kwargs: additional keyword arguments provided to the PSyIR node.
56  :type kwargs: unwrapped dict.
57 
58  '''
59  # Textual description of the node.
60  _text_name = "Loop"
61  _colour = "red"
62 
63  def __init__(self, valid_loop_types=None, **kwargs):
64  super().__init__(**kwargs)
65 
66  if valid_loop_types is None:
67  self._valid_loop_types_valid_loop_types = []
68  else:
69  self._valid_loop_types_valid_loop_types = valid_loop_types
70  self._loop_type_loop_type = None # inner, outer, colour, colours, ...
71  self._field_field = None
72  self._field_name_field_name = None # name of the field
73  self._field_space_field_space = None # v0, v1, ..., cu, cv, ...
74  self._iteration_space_iteration_space = None # cells, ..., cu, cv, ...
75  self._kern_kern = None # Kernel associated with this loop
76  # TODO 1731: _kern expects one kernel. What happens when we doo loop
77  # fusion?
78 
79  # TODO 1731: replace iterates_over with iteration_space
80  self._iterates_over_iterates_over = "unknown"
81 
82  def __eq__(self, other):
83  '''
84  Checks whether two nodes are equal. Two PSyLoop nodes are equal
85  if they have equal loop_type, field, field_name, field_space
86  iteraction_space and kernel.
87 
88  :param object other: the object to check equality to.
89 
90  :returns: whether other is equal to self.
91  :rtype: bool
92  '''
93  is_eq = super().__eq__(other)
94  is_eq = is_eq and self.loop_typeloop_typeloop_typeloop_type == other.loop_type
95  is_eq = is_eq and self.fieldfieldfield == other.field
96  is_eq = is_eq and self.field_namefield_namefield_namefield_name == other.field_name
97  is_eq = is_eq and self.field_spacefield_spacefield_spacefield_space == other.field_space
98  is_eq = is_eq and self.iteration_spaceiteration_spaceiteration_spaceiteration_space == other.iteration_space
99  is_eq = is_eq and self.kernelkernelkernelkernel == other.kernel
100  # pylint: disable=protected-access
101  is_eq = is_eq and self._iterates_over_iterates_over == other._iterates_over
102 
103  return is_eq
104 
105  @property
106  def dag_name(self):
107  '''
108  :returns: the dag name to use for this loop.
109  :rtype: str
110 
111  '''
112  if self.loop_typeloop_typeloop_typeloop_type:
113  _, position = self._find_position(self.ancestor(Routine))
114  name = f"loop_[{self.loop_type}]_{position}"
115  else:
116  name = super().dag_name
117  return name
118 
119  @property
120  def valid_loop_types(self):
121  '''
122  :returns: the (domain-specific) loop types allowed by this instance.
123  :rtype: list of str
124  '''
125  return self._valid_loop_types_valid_loop_types
126 
127  @property
128  def loop_type(self):
129  '''
130  :returns: the (domain-specific) type of this loop.
131  :rtype: str
132  '''
133  return self._loop_type_loop_type
134 
135  @loop_type.setter
136  def loop_type(self, value):
137  '''
138  Set the type of this Loop.
139 
140  :param str value: the type of this loop.
141 
142  :raises TypeError: if the specified value is not a recognised \
143  loop type.
144  '''
145  if value not in self._valid_loop_types_valid_loop_types:
146  raise TypeError(
147  f"Error, loop_type value ({value}) is invalid. Must be one of "
148  f"{self._valid_loop_types}.")
149  self._loop_type_loop_type = value
150 
151  def node_str(self, colour=True):
152  '''
153  Returns the name of this node with (optional) control codes
154  to generate coloured output in a terminal that supports it.
155 
156  :param bool colour: whether or not to include colour control codes.
157 
158  :returns: description of this node, possibly coloured.
159  :rtype: str
160 
161  '''
162  return (f"{self.coloured_name(colour)}[type='{self._loop_type}', "
163  f"field_space='{self._field_space}', "
164  f"it_space='{self.iteration_space}']")
165 
166  # TODO 1731: The properties below should be dynamically computed instead
167  # of storing an attribute that can become inconsistent with the kernel or
168  # loop bounds.
169 
170  @property
171  def field_space(self):
172  '''
173  :returns: the field_space associated this loop.
174  :rtype: str
175  '''
176  return self._field_space_field_space
177 
178  @field_space.setter
179  def field_space(self, my_field_space):
180  ''' Set a new field_space for this loop.
181 
182  :param my_field_space: the new field_space value.
183  :rtype my_field_space: str
184  '''
185  self._field_space_field_space = my_field_space
186 
187  @property
188  def field_name(self):
189  '''
190  :returns: the field name associated to this loop.
191  :rtype: str
192  '''
193  return self._field_name_field_name
194 
195  @property
196  def field(self):
197  '''
198  :returns: the field associated to this loop.
199  :rtype: :py:class:`psyclone.psyGen.Argument`
200  '''
201  return self._field_field
202 
203  @field_name.setter
204  def field_name(self, my_field_name):
205  ''' Set a new field_name for the field associated to this loop.
206 
207  :param str my_field_name: the new field name.
208  '''
209  self._field_name_field_name = my_field_name
210 
211  @property
212  def iteration_space(self):
213  '''
214  :returns: the iteration_space of this loop.
215  :rtype: str
216  '''
217  return self._iteration_space_iteration_space
218 
219  @iteration_space.setter
220  def iteration_space(self, it_space):
221  ''' Set a new iteration space for this loop.
222 
223  :param str it_space: the new iteration_space fore this loop.
224  '''
225  self._iteration_space_iteration_space = it_space
226 
227  @property
228  def kernel(self):
229  '''
230  :returns: the kernel object associated with this PSyLoop (if any).
231  :rtype: Optional[:py:class:`psyclone.psyGen.Kern`]
232  '''
233  return self._kern_kern
234 
235  @kernel.setter
236  def kernel(self, kern):
237  '''
238  Setter for kernel object associated with this PSyLoop.
239 
240  :param kern: a kernel object.
241  :type kern: :py:class:`psyclone.psyGen.Kern`
242  '''
243  self._kern_kern = kern
244 
245  def __str__(self):
246  # Give Loop sub-classes a specialised name
247  name = self.__class__.__name__
248  result = name + "["
249  result += "variable:'" + self.variable.name
250  if self.loop_typeloop_typeloop_typeloop_type:
251  result += "', loop_type:'" + self._loop_type_loop_type
252  result += "']\n"
253  for entity in self._children:
254  result += str(entity) + "\n"
255  result += "End " + name
256  return result
257 
258  def has_inc_arg(self):
259  '''
260  :returns: True if any of the Kernels called within this loop have an \
261  argument with INC access, False otherwise.
262  :rtype: bool
263  '''
264  for kern_call in self.coded_kernels():
265  for arg in kern_call.arguments.args:
266  if arg.access == AccessType.INC:
267  return True
268  return False
269 
270  def unique_modified_args(self, arg_type):
271  '''Return all unique arguments of the given type from kernels inside
272  this loop that are modified.
273 
274  :param str arg_type: the type of kernel argument (e.g. field, \
275  operator) to search for.
276  :returns: all unique arguments of the given type from kernels inside \
277  this loop that are modified.
278  :rtype: List[:py:class:`psyclone.psyGen.DynKernelArgument`]
279  '''
280  arg_names = []
281  args = []
282  for call in self.kernels():
283  for arg in call.arguments.args:
284  if arg.argument_type.lower() == arg_type:
285  if arg.access != AccessType.READ:
286  if arg.name not in arg_names:
287  arg_names.append(arg.name)
288  args.append(arg)
289  return args
290 
292  '''
293  :returns: fields in this loop that require at least some of their \
294  halo to be clean to work correctly.
295  :rtype: List[:py:class:`psyclone.psyGen.Argument`]
296  '''
297 
298  unique_fields = []
299  unique_field_names = []
300 
301  for call in self.kernels():
302  for arg in call.arguments.args:
303  if self._halo_read_access_halo_read_access(arg):
304  if arg.name not in unique_field_names:
305  unique_field_names.append(arg.name)
306  unique_fields.append(arg)
307  return unique_fields
308 
309  def args_filter(self, arg_types=None, arg_accesses=None, unique=False):
310  '''
311  :returns: all arguments of type arg_types and arg_accesses. If these \
312  are not set then return all arguments. If unique is set to \
313  True then only return uniquely named arguments.
314  :rtype: List[:py:class:`psyclone.psyGen.Argument`]
315  '''
316  # Avoid circular import
317  # pylint: disable=import-outside-toplevel
318  from psyclone.psyGen import args_filter
319  all_args = []
320  all_arg_names = []
321  for call in self.kernels():
322  call_args = args_filter(call.arguments.args, arg_types,
323  arg_accesses)
324  if unique:
325  for arg in call_args:
326  if arg.name not in all_arg_names:
327  all_args.append(arg)
328  all_arg_names.append(arg.name)
329  else:
330  all_args.extend(call_args)
331  return all_args
332 
333  def gen_mark_halos_clean_dirty(self, parent):
334  '''
335  Generates the necessary code to mark halo regions as clean or dirty
336  following execution of this loop. This default implementation does
337  nothing.
338 
339  TODO #1648 - this method should be removed when the corresponding
340  one in LFRicLoop is removed.
341 
342  :param parent: the node in the f2pygen AST to which to add content.
343  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
344 
345  '''
346 
347  def _halo_read_access(self, arg):
348  '''Determines whether the supplied argument has (or might have) its
349  halo data read within this loop. Returns True if it does, or if
350  it might and False if it definitely does not.
351 
352  :param arg: an argument contained within this loop.
353  :type arg: :py:class:`psyclone.psyGen.KernelArgument`
354 
355  :return: True if the argument reads, or might read from the \
356  halo and False otherwise.
357  :rtype: bool
358 
359  :raises NotImplementedError: This is an abstract method.
360 
361  '''
362  raise NotImplementedError("This method needs to be implemented by the "
363  "APIs that support distributed memory.")
def args_filter(self, arg_types=None, arg_accesses=None, unique=False)
Definition: psyloop.py:309
def field_space(self, my_field_space)
Definition: psyloop.py:179