Source code for psyclone.psyir.transformations.reference2arrayrange_trans

# -----------------------------------------------------------------------------
# BSD 3-Clause License
#
# Copyright (c) 2022-2026, 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, STFC Daresbury Lab
# Modified: S. Siso, STFC Daresbury Lab

'''Module providing a transformation from a reference to an Array (a = ...)
   to an ArrayReference with one or more array ranges (a(:) = ...). This can
   be useful to determine when we have array accesses (as it is not clear when
   there is a reference to an Array) and can allow further optimisations such
   as transforming to explicit loops.

'''
from psyclone.errors import LazyString, InternalError
from psyclone.psyGen import Transformation
from psyclone.psyir.nodes import (
    ArrayReference, Call, Reference, Member, StructureReference,
    ArrayOfStructuresMember, ArrayOfStructuresReference, ArrayMember,
    StructureMember, Assignment, Range)
from psyclone.psyir.nodes.structure_accessor_mixin import (
    StructureAccessorMixin)
from psyclone.psyir.nodes.array_mixin import ArrayMixin
from psyclone.psyir.symbols import (
    DataSymbol, UnresolvedType, UnsupportedType, DataTypeSymbol,
    ArrayType, StructureType)
from psyclone.psyir.transformations.transformation_error import (
    TransformationError)
from psyclone.utils import transformation_documentation_wrapper


[docs] @transformation_documentation_wrapper class Reference2ArrayRangeTrans(Transformation): ''' Transformation to convert plain References of array symbols to ArrayReferences with full-extent ranges if it is semantically equivalent to do so (e.g. it won't convert call arguments because it would change the bounds values). Note that if the provided node does not need to be modified ( e.g. a Reference to a scalar or an ArrayReference to an array), the transformation will succeed. However, if we cannot guarantee the type of the symbol, or the validity of the transformations (e.g. it is in a call that we don't know if it is elemental or not), the transformation will fail. >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.nodes import Reference >>> from psyclone.psyir.transformations import TransformationError >>> CODE = ("program example\\n" ... "real :: a(:)\\n" ... "a = 0.0\\n" ... "end program\\n") >>> trans = Reference2ArrayRangeTrans() >>> psyir = FortranReader().psyir_from_source(CODE) >>> for reference in psyir.walk(Reference): ... try: ... trans.apply(reference) ... except TransformationError: ... pass >>> print(FortranWriter()(psyir)) program example real, dimension(:) :: a <BLANKLINE> a(:) = 0.0 <BLANKLINE> end program example <BLANKLINE> '''
[docs] def validate(self, node, options=None, **kwargs): '''Check that the node is a Reference node and that we have all information necessary to decide if it can be expanded. :param node: a Reference node. :type node: :py:class:`psyclone.psyir.nodes.Reference` :raises TransformationError: if the node is not a Reference node or the Reference node not does not reference an array symbol. :raises TransformationError: if the Reference node is (or may be) passed as an argument to a call that is not elemental. :raises TransformationError: if provided a reference inside a pointer assignment. ''' super().validate(node, **kwargs) self.validate_options(**kwargs) if node and isinstance(node.parent, Call): if node is node.parent.routine: return if node.parent.is_elemental is None: raise TransformationError(LazyString( lambda: f"The supplied node is passed as an argument to a " f"Call that may or may not be elemental: " f"'{node.parent.debug_string().strip()}'. Consider " f"adding the function's filename to RESOLVE_IMPORTS.")) if not node.parent.is_elemental: return if node and node.parent and isinstance(node.parent, Range): # If it is directly inside a Range, we know it is a scalar and we # don't need further validation return assignment = node.ancestor(Assignment) if node else None if assignment and assignment.is_pointer: raise TransformationError( f"{type(self).__name__} cannot be applied to references" f" inside pointer assignments, but found '{node.name}' in" f" {assignment.debug_string()}") if not isinstance(node, Reference): raise TransformationError( f"The supplied node should be a Reference but found " f"'{type(node).__name__}'.") if not isinstance(node.symbol, DataSymbol): raise TransformationError( f"The supplied node should be a Reference to a DataSymbol " f"but found '{node.symbol}'. Consider adding the name of the " f"file containing the declaration of this quantity to " f"RESOLVE_IMPORTS.") cursor = node cursor_datatype = cursor.symbol.datatype while cursor: if ( isinstance(cursor_datatype, UnsupportedType) and cursor_datatype.partial_datatype ): cursor_datatype = cursor_datatype.partial_datatype if isinstance(cursor_datatype, StructureType.ComponentType): # If it is a ComponentType, follow its declaration cursor_datatype = cursor_datatype.datatype if isinstance(cursor_datatype, DataTypeSymbol): # If it is a DataTypeSymbol, follow its declaration cursor_datatype = cursor_datatype.datatype # If we don't know if it is an array access (is not ArrayMixin) # or we recurse down (its a StructureAccessorMixin)s, we need to # know the exact type. if ( not isinstance(cursor, ArrayMixin) or isinstance(cursor, StructureAccessorMixin) ): if isinstance(cursor_datatype, (UnresolvedType, UnsupportedType)): # In case of a structure access, we can still guarantee # it is fine without knowing the types if all the members # accessors recursing down are all array accesses while cursor: if not isinstance(cursor, ArrayMixin): break if isinstance(cursor, StructureAccessorMixin): cursor = cursor.member else: cursor = False else: # An 'else' in a while means that it has left # without a break, in this case without finding # a non-array, so the validation succeeds return raise TransformationError( f"The supplied node should be a Reference to a symbol " f"of known type, but '{node.debug_string()}' is " f"'{cursor_datatype}'. Consider adding the name of the" f" file containing the declaration of this quantity to" f" RESOLVE_IMPORTS.") # Continue recursing if it is some kind of structure accessor if isinstance(cursor, StructureAccessorMixin): if isinstance(cursor_datatype, ArrayType): cursor_datatype = cursor_datatype.intrinsic.datatype try: cursor_datatype = cursor_datatype.components[ cursor.member.name.lower() ] except (AttributeError, KeyError): # pylint: disable=raise-missing-from raise TransformationError( f"{self.name} cannot validate '{node.debug_string()}'" f" because it could not resolve the " f"'{cursor.member.name}' accessor") cursor = cursor.member else: break
[docs] def apply(self, node, options=None, **kwargs): '''Apply the Reference2ArrayRangeTrans transformation to the specified node. The node must be a Reference to an array. The Reference is replaced by an ArrayReference with appropriate explicit range nodes (termed colon notation in Fortran). :param node: a Reference node. :type node: :py:class:`psyclone.psyir.nodes.Reference` ''' self.validate(node, **kwargs) # The following cases do not need expansions if node.parent and isinstance(node.parent, Call): if node is node.parent.routine: return if not node.parent.is_elemental: return if node and node.parent and isinstance(node.parent, Range): return # Recurse down the node converting each plain Reference and Member # to ArrayReferences or ArrayMembers when they are associated to an # array datatype. cursor = node cursor_datatype = cursor.symbol.datatype while cursor: if ( isinstance(cursor_datatype, UnsupportedType) and cursor_datatype.partial_datatype ): cursor_datatype = cursor_datatype.partial_datatype if isinstance(cursor_datatype, StructureType.ComponentType): cursor_datatype = cursor_datatype.datatype # If we know it's an array but it's not an array accessor, we need # to update the node if not isinstance(cursor, ArrayMixin): if isinstance(cursor_datatype, ArrayType): # Select the appropriate conversion target # pylint: disable=unidiomatic-typecheck if type(cursor) is Reference: array = ArrayReference(cursor.symbol) elif type(cursor) is StructureReference: array = ArrayOfStructuresReference(cursor.symbol) array.addchild(cursor.member.copy()) elif type(cursor) is Member: array = ArrayMember(cursor.name) elif type(cursor) is StructureMember: array = ArrayOfStructuresMember(cursor.name) array.addchild(cursor.member.copy()) else: raise InternalError( f"{type(cursor).__name__} needs to be converted " f"to an Array, but {self.name} does not know how." ) # Replace the node with the new one if cursor.parent: cursor.replace_with(array) # Add full-extent ranges for each dimension for idx, _ in enumerate(cursor_datatype.shape): array.addchild(array.get_full_range(idx)) # Continue recursion with the updated node cursor = array # Keep recursing down if there are more structure accessors if isinstance(cursor, StructureAccessorMixin): if isinstance(cursor_datatype, DataTypeSymbol): cursor_datatype = cursor_datatype.datatype if isinstance(cursor_datatype, ArrayType): cursor_datatype = cursor_datatype.intrinsic.datatype try: cursor_datatype = cursor_datatype.components[ cursor.member.name.lower() ] except (AttributeError, KeyError): # This condition was already validated, if it happens here # is because we guaranteed its correctness (e.g. it is # ArrayMixins all the way down), so we can finish break cursor = cursor.member else: break