Reference Guide  2.5.0
gocean_const_loop_bounds_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 R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
35 # J. Henrichs, Bureau of Meteorology
36 # I. Kavcic, Met Office
37 
38 '''This module contains the GOConstLoopBoundsTrans.'''
39 
40 from psyclone.errors import InternalError
41 from psyclone.gocean1p0 import GOInvokeSchedule, GOLoop
42 from psyclone.psyGen import Transformation
43 from psyclone.psyir.frontend.fortran import FortranReader
44 from psyclone.psyir.nodes import Assignment, Reference, StructureReference
45 from psyclone.psyir.symbols import INTEGER_TYPE, DataSymbol, DataTypeSymbol
46 from psyclone.psyir.transformations import TransformationError
47 from psyclone.configuration import Config
48 
49 
51  ''' Use of a common constant variable for each loop bound within
52  a GOInvokeSchedule. By deafault, PSyclone generates loops where
53  the bounds are obtained by de-referencing a field object, e.g.:
54 
55  .. code-block:: fortran
56 
57  DO j = my_field%grid%internal%ystart, my_field%grid%internal%ystop
58 
59  Some compilers are able to produce more efficient code if they are
60  provided with information on the relative trip-counts of the loops
61  within an Invoke. With constant loop bounds, PSyclone generates code
62  like:
63 
64  .. code-block:: fortran
65 
66  ny = my_field%grid%subdomain%internal%ystop
67  ...
68  DO j = 1, ny-1
69 
70  In practice, the application of the constant loop bounds transformation
71  looks something like, e.g.:
72 
73  >>> from psyclone.parse.algorithm import parse
74  >>> from psyclone.psyGen import PSyFactory
75  >>> import os
76  >>> TEST_API = "gocean1.0"
77  >>> _, info = parse(os.path.join("tests", "test_files", "gocean1p0",
78  ... "single_invoke.f90"),
79  ... api=TEST_API)
80  >>> psy = PSyFactory(TEST_API).create(info)
81  >>> invoke = psy.invokes.get('invoke_0_compute_cu')
82  >>> schedule = invoke.schedule
83  >>>
84  >>> from psyclone.transformations import GOConstLoopBoundsTrans
85  >>> clbtrans = GOConstLoopBoundsTrans()
86  >>>
87  >>> clbtrans.apply(schedule)
88  >>> print(schedule.view())
89 
90  '''
91  def __str__(self):
92  return "Use constant loop bounds for all loops in a GOInvokeSchedule"
93 
94  @property
95  def name(self):
96  '''
97  :returns: the name of the Transformation as a string.
98  :rtype: str
99 
100  '''
101  return "GOConstLoopBoundsTrans"
102 
103  def validate(self, node, options=None):
104  '''Checks if it is valid to apply the GOConstLoopBoundsTrans
105  transform.
106 
107  :param node: the GOInvokeSchedule to transform.
108  :type node: :py:class:`psyclone.gocean1p0.GOInvokeSchedule`
109  :param options: a dictionary with options for transformations.
110  :type options: Optional[Dict[str, Any]]
111 
112  :raises TransformationError: if the supplied node is not a \
113  GOInvokeSchedule.
114  :raises TransformationError: if the supplied schedule has loops with \
115  a loop with loop_type different than 'inner' or 'outer'.
116  :raises TransformationError: if the supplied schedule has loops with \
117  attributes for index_offsets, field_space, iteration_space and \
118  loop_type that don't appear in the GOLoop.bounds_lookup table.
119  :raises TransformationError: if the supplied schedule doesn't have a \
120  field argument.
121 
122  '''
123  if not isinstance(node, GOInvokeSchedule):
124  raise TransformationError(
125  f"GOConstLoopBoundsTrans can only be applied to "
126  f"'GOInvokeSchedule' but found '{type(node).__name__}'.")
127 
128  for loop in node.walk(GOLoop):
129  if loop.loop_type not in ["inner", "outer"]:
130  raise TransformationError(
131  f"GOConstLoopBoundsTrans can not transform a loop with "
132  f"loop_type '{loop.loop_type}', only 'inner' or 'outer' "
133  f"loop_type values are expected.")
134 
135  if loop.index_offset not in loop.bounds_lookup:
136  raise TransformationError(
137  f"GOConstLoopBoundsTrans can not transform a loop with "
138  f"index_offset '{loop.index_offset}' because it is not in "
139  f"the bounds lookup table, the available index_offset "
140  f"values are {list(loop.bounds_lookup.keys())}.")
141 
142  table = loop.bounds_lookup[loop.index_offset]
143  if loop.field_space not in table:
144  raise TransformationError(
145  f"GOConstLoopBoundsTrans can not transform a loop with "
146  f"field_space '{loop.field_space}' because it is not in "
147  f"the bounds lookup table, the available field_space "
148  f"values are {list(table.keys())}.")
149 
150  table = table[loop.field_space]
151  if loop.iteration_space not in table:
152  raise TransformationError(
153  f"GOConstLoopBoundsTrans can not transform a loop with "
154  f"iteration_space '{loop.iteration_space}' because it is "
155  f"not in the bounds lookup table, the available "
156  f"iteration_space values are {list(table.keys())}.")
157 
158  table = table[loop.iteration_space]
159  if loop.loop_type not in table:
160  raise TransformationError(
161  f"GOConstLoopBoundsTrans can not transform a loop with "
162  f"loop_type '{loop.loop_type}' because it is not in the "
163  f"bounds lookup table, the available loop_type values are "
164  f"{list(table.keys())}.")
165 
166  # Make sure the Invoke has at least one field argument
167  for arg in node.symbol_table.argument_list:
168  if isinstance(arg.datatype, DataTypeSymbol):
169  if arg.datatype.name == "r2d_field":
170  break
171  else:
172  raise TransformationError(
173  f"GOConstLoopBoundsTrans can not transform invoke "
174  f"'{node.name}' because it does not have any field arguments.")
175 
176  def apply(self, node, options=None):
177  ''' Modify the GOcean kernel loops in a GOInvokeSchedule to use
178  common constant loop bound variables.
179 
180  :param node: the GOInvokeSchedule of which all loops will get the \
181  constant loop bounds.
182  :type node: :py:class:`psyclone.gocean1p0.GOInvokeSchedule`
183  :param options: a dictionary with options for transformations.
184  :type options: Optional[Dict[str, Any]]
185 
186  '''
187  self.validatevalidatevalidate(node, options=options)
188 
189  i_stop = node.symbol_table.new_symbol(
190  "istop", symbol_type=DataSymbol, datatype=INTEGER_TYPE)
191  j_stop = node.symbol_table.new_symbol(
192  "jstop", symbol_type=DataSymbol, datatype=INTEGER_TYPE)
193 
194  # Get a field argument from the argument list (we checked there
195  # is at least one on the validation method)
196  for arg in node.symbol_table.argument_list:
197  if isinstance(arg.datatype, DataTypeSymbol):
198  if arg.datatype.name == "r2d_field":
199  field = arg
200  break
201 
202  # Look-up the loop bounds using the first field object in the
203  # list
204  api_config = Config.get().api_conf("gocean1.0")
205  xstop = api_config.grid_properties["go_grid_xstop"].fortran \
206  .format(field)
207  ystop = api_config.grid_properties["go_grid_ystop"].fortran \
208  .format(field)
209 
210  # Add the assignments of the bounds to their variables at the
211  # beginning of the invoke.
212  assign1 = Assignment.create(
213  Reference(i_stop),
214  StructureReference.create(
215  field, xstop.split('%')[1:]))
216  assign1.preceding_comment = "Look-up loop bounds"
217  assign2 = Assignment.create(
218  Reference(j_stop),
219  StructureReference.create(
220  field, ystop.split('%')[1:]))
221  node.children.insert(0, assign1)
222  node.children.insert(1, assign2)
223 
224  # Fortran reader needed to parse constructed bound expressions
225  fortran_reader = FortranReader()
226 
227  # Update all GOLoop bounds with the new variables
228  for loop in node.walk(GOLoop):
229 
230  # Chose the variable depending if it is an inner or outer loop
231  if loop.loop_type == "inner":
232  stop = i_stop.name
233  elif loop.loop_type == "outer":
234  stop = j_stop.name
235  else:
236  raise InternalError(
237  f"Found a loop with loop_type '{loop.loop_type}' but the "
238  f"only expected values are 'inner' or 'outer'.")
239 
240  # Get the bounds map
241  bounds = loop.bounds_lookup[loop.index_offset][loop.field_space][
242  loop.iteration_space][loop.loop_type]
243 
244  # Set the lower bound
245  start_expr = bounds["start"].format(start='2', stop=stop)
246  start_expr = "".join(start_expr.split()) # Remove white spaces
247  # This common case is a bit of compile-time computation
248  # but it helps with fixing all of the test cases.
249  if start_expr == "2-1":
250  start_expr = "1"
251  psyir = fortran_reader.psyir_from_expression(
252  start_expr, node.symbol_table)
253  loop.children[0].replace_with(psyir)
254 
255  # Set the upper bound
256  stop_expr = bounds["stop"].format(start='2', stop=stop)
257  stop_expr = "".join(stop_expr.split()) # Remove white spaces
258  if stop_expr == "2-1":
259  stop_expr = "1"
260  psyir = fortran_reader.psyir_from_expression(
261  stop_expr, node.symbol_table)
262  loop.children[1].replace_with(psyir)
def validate(self, node, options=None)
Definition: psyGen.py:2799