Reference Guide  2.5.0
gocean_move_iteration_boundaries_inside_kernel_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: S. Siso and N. Nobre, STFC Daresbury Lab
35 
36 '''This module contains the GOMoveIterationBoundariesInsideKernelTrans.'''
37 
38 from psyclone.psyir.transformations import TransformationError
39 from psyclone.psyGen import Transformation, InvokeSchedule
40 from psyclone.gocean1p0 import GOKern
41 from psyclone.psyir.nodes import (BinaryOperation, Reference, Loop,
42  Assignment, IfBlock, Return)
43 from psyclone.psyir.symbols import (INTEGER_TYPE, ArgumentInterface,
44  DataSymbol)
45 
46 
48  ''' Provides a transformation that moves iteration boundaries that are
49  encoded in the Loops lower_bound() and upper_bound() methods to a mask
50  inside the kernel with the boundaries passed as kernel arguments.
51 
52  For example the following kernel call:
53 
54  .. code-block:: fortran
55 
56  do i = 2, N - 1
57  do j = 2, N - 1
58  kernel(i, j, field)
59  end do
60  end do
61 
62  will be transformed to:
63 
64  .. code-block:: fortran
65 
66  startx = 2
67  stopx = N - 1
68  starty = 2
69  stopy = N - 1
70  do i = 1, size(field, 1)
71  do j = 1, size(field, 2)
72  kernel(i, j, field, startx, stopx, starty, stopy)
73  end do
74  end do
75 
76  additionally a mask like the following one will be introduced in the
77  kernel code:
78 
79  .. code-block:: fortran
80 
81  if (i < startx .or. i > stopx .or. j < starty .or. j > stopy) then
82  return
83  end if
84 
85  '''
86  def __str__(self):
87  return "Move kernel iteration boundaries inside the kernel code."
88 
89  @property
90  def name(self):
91  '''Returns the name of this transformation as a string.'''
92  return "GOMoveIterationBoundariesInsideKernelTrans"
93 
94  def validate(self, node, options=None):
95  '''Ensure that it is valid to apply this transformation to the
96  supplied node.
97 
98  :param node: the node to validate.
99  :type node: :py:class:`psyclone.gocean1p0.GOKern`
100  :param options: a dictionary with options for transformations.
101  :type options: Optional[Dict[str, Any]]
102 
103  :raises TransformationError: if the node is not a GOKern.
104 
105  '''
106  if not isinstance(node, GOKern):
107  raise TransformationError(
108  f"Error in {self.name} transformation. This transformation "
109  f"can only be applied to 'GOKern' nodes, but found "
110  f"'{type(node).__name__}'.")
111 
112  def apply(self, node, options=None):
113  '''Apply this transformation to the supplied node.
114 
115  :param node: the node to transform.
116  :type node: :py:class:`psyclone.gocean1p0.GOKern`
117  :param options: a dictionary with options for transformations.
118  :type options: Optional[Dict[str, Any]]
119 
120  '''
121  self.validatevalidatevalidate(node, options)
122 
123  # Get useful references
124  invoke_st = node.ancestor(InvokeSchedule).symbol_table
125  inner_loop = node.ancestor(Loop)
126  outer_loop = inner_loop.ancestor(Loop)
127  cursor = outer_loop.position
128 
129  # Make sure the boundary symbols in the PSylayer exist
130  inv_xstart = invoke_st.find_or_create_tag(
131  "xstart_" + node.name, root_name="xstart", symbol_type=DataSymbol,
132  datatype=INTEGER_TYPE)
133  inv_xstop = invoke_st.find_or_create_tag(
134  "xstop_" + node.name, root_name="xstop", symbol_type=DataSymbol,
135  datatype=INTEGER_TYPE)
136  inv_ystart = invoke_st.find_or_create_tag(
137  "ystart_" + node.name, root_name="ystart", symbol_type=DataSymbol,
138  datatype=INTEGER_TYPE)
139  inv_ystop = invoke_st.find_or_create_tag(
140  "ystop_" + node.name, root_name="ystop", symbol_type=DataSymbol,
141  datatype=INTEGER_TYPE)
142 
143  # If the kernel acts on the whole iteration space, the boundary values
144  # are not needed. This also avoids adding duplicated arguments if this
145  # transformation is applied more than once to the same kernel. But the
146  # declaration and initialisation above still needs to exist because the
147  # boundary variables are expected to exist by the generation code.
148  if (inner_loop.field_space == "go_every" and
149  outer_loop.field_space == "go_every" and
150  inner_loop.iteration_space == "go_all_pts" and
151  outer_loop.iteration_space == "go_all_pts"):
152  return node.root, None
153 
154  # Initialise the boundary values provided by the Loop construct
155  assign1 = Assignment.create(Reference(inv_xstart),
156  inner_loop.start_expr.copy())
157  outer_loop.parent.children.insert(cursor, assign1)
158  cursor = cursor + 1
159  assign2 = Assignment.create(Reference(inv_xstop),
160  inner_loop.stop_expr.copy())
161  outer_loop.parent.children.insert(cursor, assign2)
162  cursor = cursor + 1
163  assign3 = Assignment.create(Reference(inv_ystart),
164  outer_loop.start_expr.copy())
165  outer_loop.parent.children.insert(cursor, assign3)
166  cursor = cursor + 1
167  assign4 = Assignment.create(Reference(inv_ystop),
168  outer_loop.stop_expr.copy())
169  outer_loop.parent.children.insert(cursor, assign4)
170 
171  # Update Kernel Call argument list
172  for symbol in [inv_xstart, inv_xstop, inv_ystart, inv_ystop]:
173  node.arguments.append(symbol.name, "go_i_scalar")
174 
175  # Now that the boundaries are inside the kernel, the looping should go
176  # through all the field points
177  inner_loop.field_space = "go_every"
178  outer_loop.field_space = "go_every"
179  inner_loop.iteration_space = "go_all_pts"
180  outer_loop.iteration_space = "go_all_pts"
181 
182  # Update Kernel
183  kschedule = node.get_kernel_schedule()
184  kernel_st = kschedule.symbol_table
185  iteration_indices = kernel_st.iteration_indices
186  data_arguments = kernel_st.data_arguments
187 
188  # Create new symbols and insert them as kernel arguments at the end of
189  # the kernel argument list
190  xstart_symbol = kernel_st.new_symbol(
191  "xstart", symbol_type=DataSymbol, datatype=INTEGER_TYPE,
192  interface=ArgumentInterface(ArgumentInterface.Access.READ))
193  xstop_symbol = kernel_st.new_symbol(
194  "xstop", symbol_type=DataSymbol, datatype=INTEGER_TYPE,
195  interface=ArgumentInterface(ArgumentInterface.Access.READ))
196  ystart_symbol = kernel_st.new_symbol(
197  "ystart", symbol_type=DataSymbol, datatype=INTEGER_TYPE,
198  interface=ArgumentInterface(ArgumentInterface.Access.READ))
199  ystop_symbol = kernel_st.new_symbol(
200  "ystop", symbol_type=DataSymbol, datatype=INTEGER_TYPE,
201  interface=ArgumentInterface(ArgumentInterface.Access.READ))
202  kernel_st.specify_argument_list(
203  iteration_indices + data_arguments +
204  [xstart_symbol, xstop_symbol, ystart_symbol, ystop_symbol])
205 
206  # Create boundary masking conditions
207  condition1 = BinaryOperation.create(
208  BinaryOperation.Operator.LT,
209  Reference(iteration_indices[0]),
210  Reference(xstart_symbol))
211  condition2 = BinaryOperation.create(
212  BinaryOperation.Operator.GT,
213  Reference(iteration_indices[0]),
214  Reference(xstop_symbol))
215  condition3 = BinaryOperation.create(
216  BinaryOperation.Operator.LT,
217  Reference(iteration_indices[1]),
218  Reference(ystart_symbol))
219  condition4 = BinaryOperation.create(
220  BinaryOperation.Operator.GT,
221  Reference(iteration_indices[1]),
222  Reference(ystop_symbol))
223 
224  condition = BinaryOperation.create(
225  BinaryOperation.Operator.OR,
226  BinaryOperation.create(
227  BinaryOperation.Operator.OR,
228  condition1,
229  condition2),
230  BinaryOperation.create(
231  BinaryOperation.Operator.OR,
232  condition3,
233  condition4)
234  )
235 
236  # Insert the conditional mask as the first statement of the kernel
237  if_statement = IfBlock.create(condition, [Return()])
238  kschedule.children.insert(0, if_statement)
239 
240 
241 # For Sphinx AutoAPI documentation generation
242 __all__ = ['GOMoveIterationBoundariesInsideKernelTrans']
def validate(self, node, options=None)
Definition: psyGen.py:2799