Reference Guide  2.5.0
arrayrange2loop_trans.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2020-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 # Author R. W. Ford, N. Nobre and S. Siso, STFC Daresbury Lab
35 # Modified by J. Henrichs, Bureau of Meteorology
36 # Modified by A. B. G. Chalk, STFC Daresbury Lab
37 
38 '''Module providing a transformation from a PSyIR Array Range to a
39 PSyIR Loop. This could be useful for e.g. performance reasons, to
40 allow further transformations e.g. loop fusion or if the back-end does
41 not support array ranges.
42 
43 By default the transformation will reject character arrays,
44 though this can be overriden by setting the
45 allow_string option to True. Note that PSyclone expresses syntax such
46 as `character(LEN=100)` as UnsupportedFortranType, and this
47 transformation will convert unknown or unsupported types to loops.
48 
49 '''
50 
51 from psyclone.psyGen import Transformation
52 from psyclone.psyir.nodes import ArrayReference, Assignment, Call, \
53  IntrinsicCall, Loop, Literal, Range, Reference
54 from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE, ScalarType, \
55  UnresolvedType, UnsupportedType, ArrayType, NoType
57  import TransformationError
58 
59 
61  '''Provides a transformation from a PSyIR Array Range to a PSyIR
62  Loop. For example:
63 
64  >>> from psyclone.parse.algorithm import parse
65  >>> from psyclone.psyGen import PSyFactory
66  >>> api = "nemo"
67  >>> filename = "tra_adv_compute.F90"
68  >>> ast, invoke_info = parse(filename, api=api)
69  >>> psy = PSyFactory(api).create(invoke_info)
70  >>> schedule = psy.invokes.invoke_list[0].schedule
71  >>>
72  >>> from psyclone.psyir.nodes import Assignment
73  >>> from psyclone.psyir.transformations import ArrayRange2LoopTrans, \
74  >>> TransformationError
75  >>>
76  >>> print(schedule.view())
77  >>> trans = ArrayRange2LoopTrans()
78  >>> for assignment in schedule.walk(Assignment):
79  >>> while True:
80  >>> try:
81  >>> trans.apply(assignment)
82  >>> except TransformationError:
83  >>> break
84  >>> print(schedule.view())
85 
86  '''
87 
88  def apply(self, node, options=None):
89  '''Apply the ArrayRange2Loop transformation to the specified node. The
90  node must be an assignment. The rightmost range node in each array
91  within the assignment is replaced with a loop index and the
92  assignment is placed within a loop iterating over that
93  index. The bounds of the loop are determined from the bounds
94  of the array range on the left hand side of the assignment.
95 
96  :param node: an Assignment node.
97  :type node: :py:class:`psyclone.psyir.nodes.Assignment`
98  :type options: Optional[Dict[str, Any]]
99  :param bool options["allow_string"]: whether to allow the
100  transformation on a character type array range. Defaults to False.
101 
102  '''
103  self.validatevalidatevalidate(node, options)
104 
105  parent = node.parent
106  symbol_table = node.scope.symbol_table
107  loop_variable = symbol_table.new_symbol("idx", symbol_type=DataSymbol,
108  datatype=INTEGER_TYPE)
109 
110  # Replace the rightmost range found in all arrays with the
111  # iterator and use the range from the LHS range for the loop
112  # iteration space.
113  for array in node.walk(ArrayReference):
114  for idx, child in reversed(list(enumerate(array.children))):
115  if isinstance(child, Range):
116  if array is node.lhs:
117  # Save this range to determine indexing
118  lhs_range = child
119  array.children[idx] = Reference(
120  loop_variable, parent=array)
121  break
122  position = node.position
123  # Issue #806: If Loop bounds were a Range we would just
124  # need to provide the range node which would be simpler.
125  start, stop, step = lhs_range.pop_all_children()
126  loop = Loop.create(loop_variable, start, stop, step, [node.detach()])
127  parent.children.insert(position, loop)
128 
129  def __str__(self):
130  return ("Convert a PSyIR assignment to an array Range into a "
131  "PSyIR Loop.")
132 
133  @property
134  def name(self):
135  '''
136  :returns: the name of the transformation as a string.
137  :rtype: str
138 
139  '''
140  return type(self).__name__
141 
142  def validate(self, node, options=None):
143  '''Perform various checks to ensure that it is valid to apply the
144  ArrayRange2LoopTrans transformation to the supplied PSyIR Node.
145 
146  By default the validate function will throw an TransofmrationError
147  on character arrays, though this can be overriden by setting the
148  allow_string option to True. Note that PSyclone expresses syntax such
149  as `character(LEN=100)` as UnsupportedFortranType, and this
150  transformation will convert unknown or unsupported types to loops.
151 
152  :param node: the node that is being checked.
153  :type node: :py:class:`psyclone.psyir.nodes.Assignment`
154  :param options: a dictionary with options for transformations
155  :type options: Optional[Dict[str, Any]]
156  :param bool options["allow_string"]: whether to allow the
157  transformation on a character type array range. Defaults to False.
158 
159  :raises TransformationError: if the node argument is not an
160  Assignment.
161  :raises TransformationError: if the node argument is an
162  Assignment whose left hand side is not an ArrayReference.
163  :raises TransformationError: if the node argument is an
164  Assignment whose left hand side is an ArrayReference that does
165  not have Range specifying the access to at least one of its
166  dimensions.
167  :raises TransformationError: if two or more of the loop ranges
168  in the assignment are different or are not known to be the
169  same.
170  :raises TransformationError: if node contains a character type
171  child and the allow_strings option is
172  not set.
173 
174  '''
175  if not isinstance(node, Assignment):
176  raise TransformationError(
177  f"Error in {self.name} transformation. The supplied node "
178  f"argument should be a PSyIR Assignment, but found "
179  f"'{type(node).__name__}'.")
180 
181  if not isinstance(node.lhs, ArrayReference):
182  raise TransformationError(
183  f"Error in {self.name} transformation. The lhs of the "
184  f"supplied Assignment node should be a PSyIR ArrayReference, "
185  f"but found '{type(node.lhs).__name__}'.")
186 
187  if not [dim for dim in node.lhs.children if isinstance(dim, Range)]:
188  raise TransformationError(
189  f"Error in {self.name} transformation. The lhs of the supplied"
190  f" Assignment node should be a PSyIR ArrayReference with at "
191  f"least one of its dimensions being a Range, but found None "
192  f"in '{node.lhs}'.")
193 
194  # TODO #2004: Note that the NEMOArrayRange2Loop transforamtion has
195  # a different implementation that accepts many more statemetns (e.g.
196  # elemental function calls) but lacks in the use of symbolics. Both
197  # implementation should be merged (as well as their tests) to one
198  # combining the advantages of both.
199 
200  # Currently we don't accept calls (with the exception of L/UBOUND)
201  for call in node.rhs.walk(Call):
202  if isinstance(call, IntrinsicCall) and call.intrinsic in \
203  (IntrinsicCall.Intrinsic.LBOUND,
204  IntrinsicCall.Intrinsic.UBOUND):
205  continue
206  raise TransformationError(
207  f"Error in {self.name} transformation. The rhs of the supplied"
208  f" Assignment contains a call '{call.debug_string()}'.")
209 
210  # Find the outermost range for the array on the lhs of the
211  # assignment and save its index.
212  for idx, child in reversed(list(enumerate(node.lhs.children))):
213  if isinstance(child, Range):
214  lhs_index = idx
215  break
216 
217  # For each array on the rhs of the assignment find the
218  # outermost range if there is one, then compare this range
219  # with the one on the lhs.
220  for array in node.walk(ArrayReference):
221  for idx, child in reversed(list(enumerate(array.children))):
222  if isinstance(child, Range):
223  # Issue #814 We should add support for adding
224  # loop variables where the ranges are
225  # different, or occur in different index
226  # locations.
227  if not node.lhs.same_range(lhs_index, array, idx):
228  # Ranges are, or may be, different so we
229  # can't safely replace this range with a
230  # loop iterator.
231  raise TransformationError(
232  f"The ArrayRange2LoopTrans transformation only "
233  f"supports ranges that are known to be the "
234  f"same as each other but array access "
235  f"'{node.lhs.name}' dimension {lhs_index} and "
236  f"'{array.name}' dimension {idx} are either "
237  f"different or can't be determined in the "
238  f"assignment '{node}'.")
239  break
240 
241  if not options:
242  options = {}
243  allow_string_array = options.get("allow_string", False)
244  # If we allow string arrays then we can skip the check.
245  if not allow_string_array:
246  # ArrayMixin datatype lookup can fail if the indices contain a
247  # Call or Intrinsic Call. We catch this exception and continue
248  # for now - TODO #1799
249  for child in node.walk((Literal, Reference)):
250  try:
251  # Skip unresolved types
252  if (isinstance(child.datatype,
253  (UnresolvedType, UnsupportedType, NoType))
254  or (isinstance(child.datatype, ArrayType) and
255  isinstance(child.datatype.datatype,
256  (UnresolvedType, UnsupportedType)))):
257  continue
258  if (child.datatype.intrinsic ==
259  ScalarType.Intrinsic.CHARACTER):
260  raise TransformationError(
261  "The ArrayRange2LoopTrans transformation doesn't "
262  "allow character arrays by default. This can be "
263  "enabled by passing the allow_string option to "
264  "the transformation."
265  )
266  except NotImplementedError:
267  pass
268 
269 
270 __all__ = [
271  'ArrayRange2LoopTrans']
def validate(self, node, options=None)
Definition: psyGen.py:2799