# -----------------------------------------------------------------------------
# BSD 3-Clause License
#
# Copyright (c) 2020-2024, Science and Technology Facilities Council.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
# Author R. W. Ford, N. Nobre and S. Siso, STFC Daresbury Lab
# Modified by J. Henrichs, Bureau of Meteorology
# Modified by A. B. G. Chalk, STFC Daresbury Lab
'''Module providing a transformation from a PSyIR Array Range to a
PSyIR Loop. This could be useful for e.g. performance reasons, to
allow further transformations e.g. loop fusion or if the back-end does
not support array ranges.
By default the transformation will reject character arrays,
though this can be overriden by setting the
allow_string option to True. Note that PSyclone expresses syntax such
as `character(LEN=100)` as UnsupportedFortranType, and this
transformation will convert unknown or unsupported types to loops.
'''
from psyclone.psyGen import Transformation
from psyclone.psyir.nodes import ArrayReference, Assignment, Call, \
IntrinsicCall, Loop, Literal, Range, Reference
from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE, ScalarType, \
UnresolvedType, UnsupportedType, ArrayType, NoType
from psyclone.psyir.transformations.transformation_error \
import TransformationError
[docs]class ArrayRange2LoopTrans(Transformation):
'''Provides a transformation from a PSyIR Array Range to a PSyIR
Loop. For example:
>>> from psyclone.parse.algorithm import parse
>>> from psyclone.psyGen import PSyFactory
>>> api = "nemo"
>>> filename = "tra_adv_compute.F90"
>>> ast, invoke_info = parse(filename, api=api)
>>> psy = PSyFactory(api).create(invoke_info)
>>> schedule = psy.invokes.invoke_list[0].schedule
>>>
>>> from psyclone.psyir.nodes import Assignment
>>> from psyclone.psyir.transformations import ArrayRange2LoopTrans, \
>>> TransformationError
>>>
>>> print(schedule.view())
>>> trans = ArrayRange2LoopTrans()
>>> for assignment in schedule.walk(Assignment):
>>> while True:
>>> try:
>>> trans.apply(assignment)
>>> except TransformationError:
>>> break
>>> print(schedule.view())
'''
[docs] def apply(self, node, options=None):
'''Apply the ArrayRange2Loop transformation to the specified node. The
node must be an assignment. The rightmost range node in each array
within the assignment is replaced with a loop index and the
assignment is placed within a loop iterating over that
index. The bounds of the loop are determined from the bounds
of the array range on the left hand side of the assignment.
:param node: an Assignment node.
:type node: :py:class:`psyclone.psyir.nodes.Assignment`
:type options: Optional[Dict[str, Any]]
:param bool options["allow_string"]: whether to allow the
transformation on a character type array range. Defaults to False.
'''
self.validate(node, options)
parent = node.parent
symbol_table = node.scope.symbol_table
loop_variable = symbol_table.new_symbol("idx", symbol_type=DataSymbol,
datatype=INTEGER_TYPE)
# Replace the rightmost range found in all arrays with the
# iterator and use the range from the LHS range for the loop
# iteration space.
for array in node.walk(ArrayReference):
for idx, child in reversed(list(enumerate(array.children))):
if isinstance(child, Range):
if array is node.lhs:
# Save this range to determine indexing
lhs_range = child
array.children[idx] = Reference(
loop_variable, parent=array)
break
position = node.position
# Issue #806: If Loop bounds were a Range we would just
# need to provide the range node which would be simpler.
start, stop, step = lhs_range.pop_all_children()
loop = Loop.create(loop_variable, start, stop, step, [node.detach()])
parent.children.insert(position, loop)
def __str__(self):
return ("Convert a PSyIR assignment to an array Range into a "
"PSyIR Loop.")
@property
def name(self):
'''
:returns: the name of the transformation as a string.
:rtype: str
'''
return type(self).__name__
def validate(self, node, options=None):
'''Perform various checks to ensure that it is valid to apply the
ArrayRange2LoopTrans transformation to the supplied PSyIR Node.
By default the validate function will throw an TransofmrationError
on character arrays, though this can be overriden by setting the
allow_string option to True. Note that PSyclone expresses syntax such
as `character(LEN=100)` as UnsupportedFortranType, and this
transformation will convert unknown or unsupported types to loops.
:param node: the node that is being checked.
:type node: :py:class:`psyclone.psyir.nodes.Assignment`
:param options: a dictionary with options for transformations
:type options: Optional[Dict[str, Any]]
:param bool options["allow_string"]: whether to allow the
transformation on a character type array range. Defaults to False.
:raises TransformationError: if the node argument is not an
Assignment.
:raises TransformationError: if the node argument is an
Assignment whose left hand side is not an ArrayReference.
:raises TransformationError: if the node argument is an
Assignment whose left hand side is an ArrayReference that does
not have Range specifying the access to at least one of its
dimensions.
:raises TransformationError: if two or more of the loop ranges
in the assignment are different or are not known to be the
same.
:raises TransformationError: if node contains a character type
child and the allow_strings option is
not set.
'''
if not isinstance(node, Assignment):
raise TransformationError(
f"Error in {self.name} transformation. The supplied node "
f"argument should be a PSyIR Assignment, but found "
f"'{type(node).__name__}'.")
if not isinstance(node.lhs, ArrayReference):
raise TransformationError(
f"Error in {self.name} transformation. The lhs of the "
f"supplied Assignment node should be a PSyIR ArrayReference, "
f"but found '{type(node.lhs).__name__}'.")
if not [dim for dim in node.lhs.children if isinstance(dim, Range)]:
raise TransformationError(
f"Error in {self.name} transformation. The lhs of the supplied"
f" Assignment node should be a PSyIR ArrayReference with at "
f"least one of its dimensions being a Range, but found None "
f"in '{node.lhs}'.")
# TODO #2004: Note that the NEMOArrayRange2Loop transforamtion has
# a different implementation that accepts many more statemetns (e.g.
# elemental function calls) but lacks in the use of symbolics. Both
# implementation should be merged (as well as their tests) to one
# combining the advantages of both.
# Currently we don't accept calls (with the exception of L/UBOUND)
for call in node.rhs.walk(Call):
if isinstance(call, IntrinsicCall) and call.intrinsic in \
(IntrinsicCall.Intrinsic.LBOUND,
IntrinsicCall.Intrinsic.UBOUND):
continue
raise TransformationError(
f"Error in {self.name} transformation. The rhs of the supplied"
f" Assignment contains a call '{call.debug_string()}'.")
# Find the outermost range for the array on the lhs of the
# assignment and save its index.
for idx, child in reversed(list(enumerate(node.lhs.children))):
if isinstance(child, Range):
lhs_index = idx
break
# For each array on the rhs of the assignment find the
# outermost range if there is one, then compare this range
# with the one on the lhs.
for array in node.walk(ArrayReference):
for idx, child in reversed(list(enumerate(array.children))):
if isinstance(child, Range):
# Issue #814 We should add support for adding
# loop variables where the ranges are
# different, or occur in different index
# locations.
if not node.lhs.same_range(lhs_index, array, idx):
# Ranges are, or may be, different so we
# can't safely replace this range with a
# loop iterator.
raise TransformationError(
f"The ArrayRange2LoopTrans transformation only "
f"supports ranges that are known to be the "
f"same as each other but array access "
f"'{node.lhs.name}' dimension {lhs_index} and "
f"'{array.name}' dimension {idx} are either "
f"different or can't be determined in the "
f"assignment '{node}'.")
break
if not options:
options = {}
allow_string_array = options.get("allow_string", False)
# If we allow string arrays then we can skip the check.
if not allow_string_array:
# ArrayMixin datatype lookup can fail if the indices contain a
# Call or Intrinsic Call. We catch this exception and continue
# for now - TODO #1799
for child in node.walk((Literal, Reference)):
try:
# Skip unresolved types
if (isinstance(child.datatype,
(UnresolvedType, UnsupportedType, NoType))
or (isinstance(child.datatype, ArrayType) and
isinstance(child.datatype.datatype,
(UnresolvedType, UnsupportedType)))):
continue
if (child.datatype.intrinsic ==
ScalarType.Intrinsic.CHARACTER):
raise TransformationError(
"The ArrayRange2LoopTrans transformation doesn't "
"allow character arrays by default. This can be "
"enabled by passing the allow_string option to "
"the transformation."
)
except NotImplementedError:
pass
__all__ = [
'ArrayRange2LoopTrans']