From deb21cfcb70af7a0655da5f70d05336f1a49d76a Mon Sep 17 00:00:00 2001 From: rupertford Date: Tue, 2 May 2023 14:40:45 +0100 Subject: [PATCH 01/11] issue #2105. First working version for maxval. --- .../psyir/transformations/__init__.py | 2 + .../intrinsics/maxval2code_trans.py | 192 ++++++++ .../intrinsics/mms_base_trans.py | 430 ++++++++++++++++++ .../intrinsics/maxval2code_trans_test.py | 430 ++++++++++++++++++ 4 files changed, 1054 insertions(+) create mode 100644 src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py create mode 100644 src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py create mode 100644 src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py diff --git a/src/psyclone/psyir/transformations/__init__.py b/src/psyclone/psyir/transformations/__init__.py index 232f5b227e..46c5b8a771 100644 --- a/src/psyclone/psyir/transformations/__init__.py +++ b/src/psyclone/psyir/transformations/__init__.py @@ -66,6 +66,8 @@ Matmul2CodeTrans from psyclone.psyir.transformations.intrinsics.max2code_trans import \ Max2CodeTrans +from psyclone.psyir.transformations.intrinsics.maxval2code_trans import \ + Maxval2CodeTrans from psyclone.psyir.transformations.intrinsics.min2code_trans import \ Min2CodeTrans from psyclone.psyir.transformations.intrinsics.sign2code_trans import \ diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py new file mode 100644 index 0000000000..4c5452ff68 --- /dev/null +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py @@ -0,0 +1,192 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, 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 + +'''Module providing a transformation from a PSyIR MAXVAL intrinsic to +PSyIR code. This could be useful if the MAXVAL operator is not +supported by the back-end, the required parallelisation approach, or +if the performance in the inline code is better than the intrinsic. + +''' +from psyclone.psyir.transformations.intrinsics.mms_base_trans import ( + MMSBaseTrans) + + +class Maxval2CodeTrans(MMSBaseTrans): + '''Provides a transformation from a PSyIR MAXVAL Operator node to + equivalent code in a PSyIR tree. Validity checks are also + performed. + + If MAXVAL contains a single positional argument which is an array, + the maximum value of all of the elements in the array is returned + in the the scalar R. + + .. code-block:: python + + R = MAXVAL(ARRAY) + + For example, if the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R = ARRAY(LBOUND(ARRAY,1),LBOUND(ARRAY,2)) + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + IF R < ARRAY(I,J) THEN + R = ARRAY(I,J) + + If the dimension argument is provided then the maximum value is + returned along the row for each entry in that dimension: + + .. code-block:: python + + R = MAXVAL(ARRAY, dimension=2) + + If the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + R(I) = ARRAY(I,1) + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + IF R(I) < ARRAY(I,J) THEN + R(I) = ARRAY(I,J) + + If the mask argument is provided then the mask is used to + determine whether the maxval is applied: + + .. code-block:: python + + R = MAXVAL(ARRAY, mask=MOD(ARRAY, 2.0)==1) + + If the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R = ARRAY(LBOUND(ARRAY,1),UBOUND(ARRAY,2)) + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + if (MOD(ARRAY(I,J), 2.0)==1): + R = R + ARRAY(I,J) + + For example: + + >>> from psyclone.psyir.backend.fortran import FortranWriter + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.transformations import Maxval2CodeTrans + >>> code = ("subroutine maxval_test(array,n,m)\\n" + ... " real :: array(10,10)\\n" + ... " real :: result\\n" + ... " result = maxval(array)\\n" + ... "end subroutine\\n") + >>> psyir = FortranReader().psyir_from_source(code) + >>> sum_node = psyir.children[0].children[0].children[1] + >>> Maxval2CodeTrans().apply(sum_node) + >>> print(FortranWriter()(psyir)) + subroutine maxval_test(array, n, m) + real, dimension(10,10) :: array + real :: result + real :: maxval_var + integer :: i_0 + integer :: i_1 + + maxval_var = array(1,1) + do i_1 = 1, 10, 1 + do i_0 = 1, 10, 1 + maxval_var = maxval_var + array(i_0,i_1) + enddo + enddo + result = maxval_var + + end subroutine maxval_test + + + ''' + _INTRINSIC_NAME = "MAXVAL" + + def _loop_body(self, array_reduction, array_iterators, symbol_maxval_var, array_ref): + ''' xxx ''' + from psyclone.psyir.nodes import Reference, Assignment, IfBlock, BinaryOperation, ArrayReference + if array_reduction: + array_indices = [Reference(iterator) + for iterator in array_iterators] + lhs = ArrayReference.create(symbol_maxval_var, array_indices) + else: + lhs = Reference(symbol_maxval_var) + + # if maxval_var < array(i...) then + # maxval_var = array(i...) + # end if + + rhs = array_ref + assignment = Assignment.create(lhs, rhs) + lhs = lhs.copy() + rhs = array_ref.copy() + if_condition = BinaryOperation.create(BinaryOperation.Operator.LT, + lhs, rhs) + return IfBlock.create(if_condition, [assignment]) + + def _init_var(self, symbol_maxval_var, _, array_ref, loop_bounds, loop_iterators, array_iterators, array_reduction, dimension_literal): + ''' xxx ''' + from psyclone.psyir.nodes import Reference, Assignment, ArrayReference, Loop, Literal + from psyclone.psyir.symbols import INTEGER_TYPE + if array_reduction: + array_indices = [Reference(iterator) + for iterator in array_iterators] + lhs = ArrayReference.create(symbol_maxval_var, array_indices) + array_indices = [] + for idx in range(len(loop_bounds)): + if idx == int(dimension_literal.value)-1: + array_indices.append(loop_bounds[idx][0].copy()) + else: + array_indices.append(Reference(loop_iterators[idx])) + rhs = ArrayReference.create(array_ref.symbol, array_indices) + statement = Assignment.create(lhs, rhs) + for idx in range(len(loop_bounds)): + if idx != int(dimension_literal.value)-1: + statement = Loop.create( + loop_iterators[idx], loop_bounds[idx][0], loop_bounds[idx][1], + Literal("1", INTEGER_TYPE), [statement]) + return statement + else: + lhs = Reference(symbol_maxval_var) + array_indices = [] + for idx in range(len(loop_bounds)): + array_indices.append(loop_bounds[idx][0].copy()) + rhs = ArrayReference.create(array_ref.symbol, array_indices) + return Assignment.create(lhs, rhs) diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py new file mode 100644 index 0000000000..48119c6050 --- /dev/null +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -0,0 +1,430 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, 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 PSyIR SUM intrinsic to +PSyIR code. This could be useful if the SUM operator is not supported +by the back-end or if the performance in the inline code is better +than the intrinsic. + +''' + +from psyclone.psyir.nodes import ( + BinaryOperation, Assignment, Reference, + Literal, Loop, ArrayReference, IfBlock, Range, IntrinsicCall) +from psyclone.psyir.symbols import ( + DataSymbol, INTEGER_TYPE, ScalarType, ArrayType) +from psyclone.psyGen import Transformation +from psyclone.psyir.transformations.transformation_error import \ + TransformationError + + +class MMSBaseTrans(Transformation): + '''Provides a transformation from a PSyIR SUM Operator node to + equivalent code in a PSyIR tree. Validity checks are also + performed. + + If SUM contains a single positional argument which is an array, + all element on that array are summed and the result returned in + the scalar R. + + .. code-block:: python + + R = SUM(ARRAY) + + For example, if the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R = 0.0 + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + R = R + ARRAY(I,J) + + If the dimension argument is provided then only that dimension is + summed: + + .. code-block:: python + + R = SUM(ARRAY, dimension=2) + + If the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R(:) = 0.0 + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + R(I) = R(I) + ARRAY(I,J) + + If the mask argument is provided then the mask is used to + determine whether the sum is applied: + + .. code-block:: python + + R = SUM(ARRAY, mask=MOD(ARRAY, 2.0)==1) + + If the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R = 0.0 + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + if (MOD(ARRAY(I,J), 2.0)==1): + R = R + ARRAY(I,J) + + For example: + + >>> from psyclone.psyir.backend.fortran import FortranWriter + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.transformations import Sum2CodeTrans + >>> code = ("subroutine sum_test(array,n,m)\\n" + ... " integer :: n, m\\n" + ... " real :: array(10,10)\\n" + ... " real :: result\\n" + ... " result = sum(array)\\n" + ... "end subroutine\\n") + >>> psyir = FortranReader().psyir_from_source(code) + >>> sum_node = psyir.children[0].children[0].children[1] + >>> Sum2CodeTrans().apply(sum_node) + >>> print(FortranWriter()(psyir)) + subroutine sum_test(array, n, m) + integer :: n + integer :: m + real, dimension(10,10) :: array + real :: result + real :: sum_var + integer :: i_0 + integer :: i_1 + + sum_var = 0.0 + do i_1 = 1, 10, 1 + do i_0 = 1, 10, 1 + sum_var = sum_var + array(i_0,i_1) + enddo + enddo + result = sum_var + + end subroutine sum_test + + + ''' + _INTRINSIC_NAME = None + + @staticmethod + def _get_args(node): + '''Utility method that returns the sum arguments, (array reference, + dimension and mask). + + :param node: a Sum intrinsic. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + + returns: a tuple containing the 3 sum arguments. + rtype: Tuple[py:class:`psyclone.psyir.nodes.reference.Reference`, \ + py:class:`psyclone.psyir.nodes.Literal` | \ + :py:class:`psyclone.psyir.nodes.Reference`, \ + Optional[:py:class:`psyclone.psyir.nodes.node`]] + + ''' + # Determine the arguments to sum + args = [None, None, None] + arg_names_map = {"array": 0, "dim": 1, "mask": 2} + for idx, child in enumerate(node.children): + if not node.argument_names[idx]: + # positional arg + args[idx] = child + else: + # named arg + name = node.argument_names[idx].lower() + args[arg_names_map[name]] = child + array_ref = args[0] + dimension_ref = args[1] + mask_ref = args[2] + return (array_ref, dimension_ref, mask_ref) + + def __str__(self): + return f"Convert the PSyIR {self._INTRINSIC_NAME} intrinsic to equivalent PSyIR code." + + def validate(self, node, options=None): + '''Check that the input node is valid before applying the + transformation. + + :param node: a Sum intrinsic. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + :param options: options for the transformation. + :type options: Optional[Dict[str, Any]] + + :raises TransformationError: if the supplied node is not an \ + intrinsic. + :raises TransformationError: if the supplied node is not a sum \ + intrinsic. + :raises TransformationError: if a valid value for the \ + dimension argument can't be determined. + :raises TransformationError: if the array argument is not an array. + :raises TransformationError: if the shape of the array is not \ + supported. + :raises TransformationError: if the array datatype is not \ + supported. + + ''' + if not isinstance(node, IntrinsicCall): + raise TransformationError( + f"Error in {self.name} transformation. The supplied node " + f"argument is not an intrinsic, found " + f"'{type(node).__name__}'.") + + if node.routine.name.upper() != self._INTRINSIC_NAME: + raise TransformationError( + f"Error in {self.name} transformation. The supplied node " + f"argument is not a {self._INTRINSIC_NAME.lower()} intrinsic, found " + f"'{node.routine.name}'.") + + array_ref, dim_ref, _ = self._get_args(node) + if dim_ref and not isinstance(dim_ref, (Literal, Reference)): + raise TransformationError( + f"Can't find the value of the dimension argument. Expected " + f"it to be a literal or a reference but found " + f"'{dim_ref.debug_string()}' which is a " + f"'{type(dim_ref).__name__}'.") + + # pylint: disable=unidiomatic-typecheck + if not (isinstance(array_ref, ArrayReference) or + type(array_ref) == Reference): + raise TransformationError( + f"{self.name} only support arrays for the first argument, " + f"but found '{type(array_ref).__name__}'.") + + if len(array_ref.children) == 0: + if not array_ref.symbol.is_array: + raise TransformationError( + f"Expected '{array_ref.name}' to be an array.") + + for shape in array_ref.children: + if not isinstance(shape, Range): + raise TransformationError( + f"{self.name} only supports arrays with array ranges, " + f"but found a fixed dimension in " + f"'{array_ref.debug_string()}'.") + + for shape in array_ref.symbol.shape: + if not (shape in [ + ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE] + or isinstance(shape, ArrayType.ArrayBounds)): + raise TransformationError( + f"Unexpected shape for array. Expecting one of Deferred, " + f"Attribute or Bounds but found '{shape}'.") + + array_intrinsic = array_ref.symbol.datatype.intrinsic + if array_intrinsic not in [ScalarType.Intrinsic.REAL, + ScalarType.Intrinsic.INTEGER]: + raise TransformationError( + f"Only real and integer types supported for array " + f"'{array_ref.name}', but found '{array_intrinsic.name}'.") + + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + def apply(self, node, options=None): + '''Apply the SUM intrinsic conversion transformation to the specified + node. This node must be a SUM Operation which is converted to + equivalent inline code. + + :param node: a Sum intrinsic. + :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + :param options: options for the transformation. + :type options: Optional[Dict[str, Any]] + + ''' + self.validate(node) + + array_ref, dimension_ref, mask_ref = self._get_args(node) + + # Determine the literal value of the dimension argument + dimension_literal = None + if not dimension_ref: + # there is no dimension argument + pass + elif isinstance(dimension_ref, Literal): + dimension_literal = dimension_ref + elif (isinstance(dimension_ref, Reference) and + dimension_ref.symbol.is_constant): + dimension_literal = dimension_ref.symbol.constant_value + # else exception is handled by the validate method. + + # Determine the dimension and extent of the array + ndims = None + if len(array_ref.children) == 0: + # Note, the potential 'if not array_ref.symbol.is_array:' + # exception is already handled by the validate method. + ndims = len(array_ref.symbol.shape) + + loop_bounds = [] + for idx, shape in enumerate(array_ref.symbol.shape): + if shape in [ArrayType.Extent.DEFERRED, + ArrayType.Extent.ATTRIBUTE]: + # runtime extent using LBOUND and UBOUND required + lbound = BinaryOperation.create( + BinaryOperation.Operator.LBOUND, + Reference(array_ref.symbol), + Literal(str(idx+1), INTEGER_TYPE)) + ubound = BinaryOperation.create( + BinaryOperation.Operator.UBOUND, + Reference(array_ref.symbol), + Literal(str(idx+1), INTEGER_TYPE)) + loop_bounds.append((lbound, ubound)) + elif isinstance(shape, ArrayType.ArrayBounds): + # array extent is defined in the array declaration + loop_bounds.append(shape) + # Note, the validate method guarantees that an else + # clause is not required. + else: + # The validate method guarantees that this is an array + # reference. + loop_bounds = [] + ndims = len(array_ref.children) + for shape in array_ref.children: + loop_bounds.append((shape.start.copy(), shape.stop.copy())) + + # Determine the datatype of the array's values and create a + # scalar of that type + array_intrinsic = array_ref.symbol.datatype.intrinsic + array_precision = array_ref.symbol.datatype.precision + scalar_type = ScalarType(array_intrinsic, array_precision) + + symbol_table = node.scope.symbol_table + assignment = node.ancestor(Assignment) + + datatype = scalar_type + array_reduction = False + if dimension_ref and ndims > 1: + array_reduction = True + # We are reducing from one array to another + shape = [] + for idx, bounds in enumerate(loop_bounds): + if int(dimension_literal.value)-1 == idx: + pass + else: + shape.append(bounds) + datatype = ArrayType(scalar_type, shape) + + array_ref = node.children[0].detach() + + # Create temporary sum variable (sum_var) + symbol_sum_var = symbol_table.new_symbol( + f"{self._INTRINSIC_NAME.lower()}_var", symbol_type=DataSymbol, datatype=datatype) + # Replace operation with a temporary variable (sum_var). + if array_reduction: + array_indices = [] + for idx in range(ndims-1): + array_indices.append(":") + reference = ArrayReference.create(symbol_sum_var, array_indices) + else: + reference = Reference(symbol_sum_var) + node.replace_with(reference) + + # Create the loop iterators + loop_iterators = [] + array_iterators = [] + for idx in range(ndims): + loop_iterator = symbol_table.new_symbol( + f"i_{idx}", symbol_type=DataSymbol, datatype=INTEGER_TYPE) + loop_iterators.append(loop_iterator) + if array_reduction and idx != int(dimension_literal.value)-1: + array_iterators.append(loop_iterator) + + # Initialise the temporary variable. + new_assignment = self._init_var(symbol_sum_var, scalar_type, array_ref, loop_bounds, loop_iterators, array_iterators, array_reduction, dimension_literal) + assignment.parent.children.insert(assignment.position, new_assignment) + + array_indices = [] + for idx in range(ndims): + array_indices.append(Reference(loop_iterators[idx])) + array_ref = ArrayReference.create(array_ref.symbol, array_indices) + + statement = self._loop_body( + array_reduction, array_iterators, symbol_sum_var, array_ref) + + if mask_ref: + # A mask argument has been provided + for ref in mask_ref.walk(Reference): + if ref.name == array_ref.name: + # The array needs indexing + shape = [Reference(obj) for obj in loop_iterators] + reference = ArrayReference.create(ref.symbol, shape) + ref.replace_with(reference) + statement = IfBlock.create(mask_ref.detach(), [statement]) + + for idx in range(ndims): + statement = Loop.create( + loop_iterators[idx].copy(), loop_bounds[idx][0].copy(), loop_bounds[idx][1].copy(), + Literal("1", INTEGER_TYPE), [statement]) + + assignment.parent.children.insert(assignment.position, statement) + + def _loop_body(self, array_reduction, array_iterators, symbol_sum_var, array_ref): + ''' xxx ''' + if array_reduction: + # sum_var(i,...) = sum_var(i,...) + array(i,...) + array_indices = [Reference(iterator) + for iterator in array_iterators] + lhs = ArrayReference.create(symbol_sum_var, array_indices) + array_indices = [Reference(iterator) + for iterator in array_iterators] + rhs_child1 = ArrayReference.create(symbol_sum_var, array_indices) + else: + # sum_var = sum_var + array(i,...) + lhs = Reference(symbol_sum_var) + rhs_child1 = Reference(symbol_sum_var) + + rhs_child2 = array_ref + rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, rhs_child1, + rhs_child2) + return Assignment.create(lhs, rhs) + + def _init_var(self, symbol_sum_var, scalar_type, _1, _2, _3, _4, _5, _6): + ''' xxx ''' + # sum_var=0.0 or 0 + lhs = Reference(symbol_sum_var) + if scalar_type.intrinsic == scalar_type.Intrinsic.REAL: + rhs = Literal("0.0", scalar_type) + elif scalar_type.intrinsic == scalar_type.Intrinsic.INTEGER: + rhs = Literal("0", scalar_type) + # Note, the validate method guarantees that an else branch is + # not required. + return Assignment.create(lhs, rhs) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py new file mode 100644 index 0000000000..9dadf4740b --- /dev/null +++ b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py @@ -0,0 +1,430 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, 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 Laboratory + +'''Module containing tests for the sum2code transformation.''' + +import pytest + +from psyclone.psyir.frontend.fortran import FortranReader +from psyclone.psyir.backend.fortran import FortranWriter +from psyclone.psyir.nodes import IntrinsicCall, Reference +from psyclone.psyir.symbols import REAL_TYPE, DataSymbol +from psyclone.psyir.transformations import Maxval2CodeTrans, TransformationError + + +def test_initialise(): + ''' Test that we can create an instance of the transformation ''' + trans = Maxval2CodeTrans() + assert isinstance(trans, Maxval2CodeTrans) + assert (str(trans) == "Convert the PSyIR MAXVAL intrinsic to equivalent " + "PSyIR code.") + assert trans.name == "Maxval2CodeTrans" + + +def test_validate_node(): + '''Check that an incorrect node raises the expected exception.''' + trans = Maxval2CodeTrans() + with pytest.raises(TransformationError) as info: + trans.validate(None) + assert ("Error in Maxval2CodeTrans transformation. The supplied node " + "argument is not an intrinsic, found 'NoneType'." + in str(info.value)) + + +# apply tests + +@pytest.mark.parametrize("idim1,idim2,rdim11,rdim12,rdim21,rdim22", + [("10", "20", "1", "10", "1", "20"), + ("n", "m", "1", "n", "1", "m"), + ("0:n", "2:m", "0", "n", "2", "m"), + (":", ":", "LBOUND(array, 1)", "UBOUND(array, 1)", + "LBOUND(array, 2)", "UBOUND(array, 2)")]) +def test_apply_maxval(idim1, idim2, rdim11, rdim12, rdim21, rdim22, + fortran_reader, fortran_writer): + '''Test that a sum intrinsic as the only term on the rhs of an + assignment with a single array argument gets transformed as + expected. Test with known and unknown array sizes. + + ''' + code = ( + f"subroutine maxval_test(array,n,m)\n" + f" integer :: n, m\n" + f" real :: array({idim1},{idim2})\n" + f" real :: result\n" + f" result = maxval(array)\n" + f"end subroutine\n") + expected = ( + f"subroutine maxval_test(array, n, m)\n" + f" integer :: n\n integer :: m\n" + f" real, dimension({idim1},{idim2}) :: array\n" + f" real :: result\n real :: maxval_var\n" + f" integer :: i_0\n integer :: i_1\n\n" + f" maxval_var = array({rdim11},{rdim21})\n" + f" do i_1 = {rdim21}, {rdim22}, 1\n" + f" do i_0 = {rdim11}, {rdim12}, 1\n" + f" if (maxval_var < array(i_0,i_1)) then\n" + f" maxval_var = array(i_0,i_1)\n" + f" end if\n" + f" enddo\n" + f" enddo\n" + f" result = maxval_var\n\n" + f"end subroutine maxval_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + result = fortran_writer(psyir) + assert result == expected + + +@pytest.mark.parametrize("datatype,zero", [("real", "0.0"), ("integer", "0"), + ("real(kind=r_def)", "0.0_r_def")]) +def test_apply_sum_multi(fortran_reader, fortran_writer, datatype, zero): + '''Test that a sum intrinsic as part of multiple term on the rhs of an + assignment with a single array argument gets transformed as + expected. Test with real, integer and with a specified precision. + + ''' + code = ( + f"subroutine maxval_test(array,n,m,value1,value2)\n" + f" use precision\n" + f" integer :: n, m\n" + f" {datatype} :: array(n,m)\n" + f" real :: value1, value2\n" + f" real :: result\n" + f" result = value1 + maxval(array) * value2\n" + f"end subroutine\n") + expected = ( + f"subroutine maxval_test(array, n, m, value1, value2)\n" + f" use precision\n" + f" integer :: n\n integer :: m\n" + f" {datatype}, dimension(n,m) :: array\n" + f" real :: value1\n real :: value2\n" + f" real :: result\n {datatype} :: maxval_var\n" + f" integer :: i_0\n integer :: i_1\n\n" + f" maxval_var = array(1,1)\n" + f" do i_1 = 1, m, 1\n" + f" do i_0 = 1, n, 1\n" + f" if (maxval_var < array(i_0,i_1)) then\n" + f" maxval_var = array(i_0,i_1)\n" + f" end if\n" + f" enddo\n" + f" enddo\n" + f" result = value1 + maxval_var * value2\n\n" + f"end subroutine maxval_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + sum_node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + result = fortran_writer(psyir) + assert result == expected + + +def test_apply_dimension_1d(fortran_reader, fortran_writer): + '''Test that the apply method works as expected when a dimension + argument is specified and the array is one dimensional. This + should be the same as if dimension were not specified at all. + + ''' + code = ( + "subroutine maxval_test(array,value1,value2)\n" + " real :: array(:)\n" + " real :: value1, value2\n" + " real :: result\n" + " result = value1 + maxval(array,dim=1) * value2\n" + "end subroutine\n") + expected = ( + "subroutine maxval_test(array, value1, value2)\n" + " real, dimension(:) :: array\n" + " real :: value1\n real :: value2\n" + " real :: result\n real :: maxval_var\n" + " integer :: i_0\n\n" + " maxval_var = array(LBOUND(array, 1))\n" + " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" + " if (maxval_var < array(i_0)) then\n" + " maxval_var = array(i_0)\n" + " end if\n" + " enddo\n" + " result = value1 + maxval_var * value2\n\n" + "end subroutine maxval_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + sum_node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + result = fortran_writer(psyir) + assert result == expected + + +def test_apply_dimension_multid(fortran_reader, fortran_writer): + '''Test that the apply method works as expected when a dimension + argument is specified and the array is multi-dimensional. Only the + specified dimension should be summed. + + ''' + code = ( + "subroutine maxval_test(array,value1,value2,n,m,p)\n" + " integer :: n,m,p\n" + " real :: array(n,m,p)\n" + " real :: value1, value2\n" + " real :: result(n,p)\n" + " result(:,:) = value1 + maxval(array,dim=2) * value2\n" + "end subroutine\n") + expected = ( + "subroutine maxval_test(array, value1, value2, n, m, p)\n" + " integer :: n\n integer :: m\n integer :: p\n" + " real, dimension(n,m,p) :: array\n" + " real :: value1\n real :: value2\n" + " real, dimension(n,p) :: result\n" + " real, dimension(n,p) :: maxval_var\n" + " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" + " do i_2 = 1, p, 1\n" + " do i_0 = 1, n, 1\n" + " maxval_var(i_0,i_2) = array(i_0,1,i_2)\n" + " enddo\n" + " enddo\n" + " do i_2 = 1, p, 1\n" + " do i_1 = 1, m, 1\n" + " do i_0 = 1, n, 1\n" + " if (maxval_var(i_0,i_2) < array(i_0,i_1,i_2)) then\n" + " maxval_var(i_0,i_2) = array(i_0,i_1,i_2)\n" + " end if\n" + " enddo\n" + " enddo\n" + " enddo\n" + " result(:,:) = value1 + maxval_var(:,:) * value2\n\n" + "end subroutine maxval_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + sum_node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + result = fortran_writer(psyir) + assert result == expected + + +def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): + '''Test that lbound and ubound are used if the bounds of the array are + not known. + + ''' + code = ( + "subroutine maxval_test(array,value1,value2,result)\n" + " real :: array(:,:,:)\n" + " real :: value1, value2\n" + " real :: result(:,:)\n" + " result(:,:) = value1 + maxval(array,dim=2) * value2\n" + "end subroutine\n") + expected = ( + "subroutine maxval_test(array, value1, value2, result)\n" + " real, dimension(:,:,:) :: array\n" + " real :: value1\n" + " real :: value2\n" + " real, dimension(:,:) :: result\n" + " real, dimension(LBOUND(array, 1):UBOUND(array, 1),LBOUND(array, 3):" + "UBOUND(array, 3)) :: maxval_var\n" + " integer :: i_0\n" + " integer :: i_1\n" + " integer :: i_2\n\n" + " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" + " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" + " maxval_var(i_0,i_2) = array(i_0,LBOUND(array, 2),i_2)\n" + " enddo\n" + " enddo\n" + " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" + " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" + " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" + " if (maxval_var(i_0,i_2) < array(i_0,i_1,i_2)) then\n" + " maxval_var(i_0,i_2) = array(i_0,i_1,i_2)\n" + " end if\n" + " enddo\n" + " enddo\n" + " enddo\n" + " result(:,:) = value1 + maxval_var(:,:) * value2\n\n" + "end subroutine maxval_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + sum_node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + result = fortran_writer(psyir) + assert result == expected + + +# specified array range +def test_apply_dimension_multid_range(fortran_reader, fortran_writer): + '''Test that the apply method works as expected when an array range is + specified and the array is multi-dimensional. Only the specified + dimension should be summed. + + ''' + code = ( + "subroutine maxval_test(array,value1,value2,n,m,p)\n" + " integer :: n,m,p\n" + " real :: array(:,:,:)\n" + " real :: value1, value2\n" + " real :: result(n,p)\n" + " result(:,:) = value1 + maxval(array(1:n,m-1:m,1:p),dim=2) * " + "value2\n" + "end subroutine\n") + expected = ( + "subroutine maxval_test(array, value1, value2, n, m, p)\n" + " integer :: n\n integer :: m\n integer :: p\n" + " real, dimension(:,:,:) :: array\n" + " real :: value1\n real :: value2\n" + " real, dimension(n,p) :: result\n" + " real, dimension(n,p) :: maxval_var\n" + " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" + " do i_2 = 1, p, 1\n" + " do i_0 = 1, n, 1\n" + " maxval_var(i_0,i_2) = array(i_0,m - 1,i_2)\n" + " enddo\n" + " enddo\n" + " do i_2 = 1, p, 1\n" + " do i_1 = m - 1, m, 1\n" + " do i_0 = 1, n, 1\n" + " if (maxval_var(i_0,i_2) < array(i_0,i_1,i_2)) then\n" + " maxval_var(i_0,i_2) = array(i_0,i_1,i_2)\n" + " end if\n" + " enddo\n" + " enddo\n" + " enddo\n" + " result(:,:) = value1 + maxval_var(:,:) * value2\n\n" + "end subroutine maxval_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + sum_node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + result = fortran_writer(psyir) + assert result == expected + + +def test_mask(): + '''Test that the sum transformation works when there is a mask + specified. + + ''' + code = ( + "program maxval_test\n" + " real :: array(10,10)\n" + " real :: result\n" + " result = maxval(array, mask=MOD(array, 2.0)==1)\n" + "end program\n") + expected = ( + "program maxval_test\n" + " real, dimension(10,10) :: array\n" + " real :: result\n" + " real :: maxval_var\n" + " integer :: i_0\n" + " integer :: i_1\n\n" + " maxval_var = array(1,1)\n" + " do i_1 = 1, 10, 1\n" + " do i_0 = 1, 10, 1\n" + " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" + " if (maxval_var < array(i_0,i_1)) then\n" + " maxval_var = array(i_0,i_1)\n" + " end if\n" + " end if\n" + " enddo\n" + " enddo\n" + " result = maxval_var\n\n" + "end program maxval_test") + reader = FortranReader() + psyir = reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + writer = FortranWriter() + result = writer(psyir) + assert expected in result + + +def test_mask_dimension(): + '''Test that the sum transformation works when there is a mask and a + dimension specified. + + ''' + code = ( + "program maxval_test\n" + " real :: array(10,10)\n" + " real :: result(10)\n" + " integer, parameter :: dimension=2\n" + " result(:) = maxval(array, dimension, mask=MOD(array, 2.0)==1)\n" + "end program\n") + expected = ( + "program maxval_test\n" + " integer, parameter :: dimension = 2\n" + " real, dimension(10,10) :: array\n" + " real, dimension(10) :: result\n" + " real, dimension(10) :: maxval_var\n" + " integer :: i_0\n" + " integer :: i_1\n\n" + " do i_0 = 1, 10, 1\n" + " maxval_var(i_0) = array(i_0,1)\n" + " enddo\n" + " do i_1 = 1, 10, 1\n" + " do i_0 = 1, 10, 1\n" + " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" + " if (maxval_var(i_0) < array(i_0,i_1)) then\n" + " maxval_var(i_0) = array(i_0,i_1)\n" + " end if\n" + " end if\n" + " enddo\n" + " enddo\n" + " result(:) = maxval_var(:)\n\n" + "end program maxval_test") + reader = FortranReader() + psyir = reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + trans = Maxval2CodeTrans() + trans.apply(sum_node) + writer = FortranWriter() + result = writer(psyir) + assert expected in result From d5c105c797738555c1a6b800f1ec7f81fc6627be Mon Sep 17 00:00:00 2001 From: rupertford Date: Fri, 19 May 2023 15:07:31 +0100 Subject: [PATCH 02/11] pr #2148. Completed implementation of code. --- .../intrinsics/maxval2code_trans.py | 112 ++++--- .../intrinsics/minval2code_trans.py | 204 ++++++++++++ .../intrinsics/mms_base_trans.py | 46 +-- .../intrinsics/sum2code_trans.py | 313 ++++-------------- .../intrinsics/maxval2code_trans_test.py | 30 +- .../intrinsics/sum2code_trans_test.py | 22 +- 6 files changed, 356 insertions(+), 371 deletions(-) create mode 100644 src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py index 4c5452ff68..05e0e6872a 100644 --- a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py @@ -39,6 +39,9 @@ if the performance in the inline code is better than the intrinsic. ''' +from psyclone.psyir.nodes import ( + Reference, Assignment, IfBlock, BinaryOperation, ArrayReference, + IntrinsicCall) from psyclone.psyir.transformations.intrinsics.mms_base_trans import ( MMSBaseTrans) @@ -61,7 +64,7 @@ class Maxval2CodeTrans(MMSBaseTrans): .. code-block:: python - R = ARRAY(LBOUND(ARRAY,1),LBOUND(ARRAY,2)) + R = TINY(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) IF R < ARRAY(I,J) THEN @@ -79,8 +82,7 @@ class Maxval2CodeTrans(MMSBaseTrans): .. code-block:: python - DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - R(I) = ARRAY(I,1) + R(:) = TINY(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) IF R(I) < ARRAY(I,J) THEN @@ -98,11 +100,12 @@ class Maxval2CodeTrans(MMSBaseTrans): .. code-block:: python - R = ARRAY(LBOUND(ARRAY,1),UBOUND(ARRAY,2)) + R = TINY(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - if (MOD(ARRAY(I,J), 2.0)==1): - R = R + ARRAY(I,J) + IF MOD(ARRAY(I,J), 2.0)==1 THEN + IF R < ARRAY(I,J) THEN + R = ARRAY(I,J) For example: @@ -125,10 +128,12 @@ class Maxval2CodeTrans(MMSBaseTrans): integer :: i_0 integer :: i_1 - maxval_var = array(1,1) + maxval_var = TINY(maxval_var) do i_1 = 1, 10, 1 do i_0 = 1, 10, 1 - maxval_var = maxval_var + array(i_0,i_1) + if (maxval_var < array(i_0,i_1)) then + maxval_var = array(i_0,i_1) + end if enddo enddo result = maxval_var @@ -139,54 +144,61 @@ class Maxval2CodeTrans(MMSBaseTrans): ''' _INTRINSIC_NAME = "MAXVAL" - def _loop_body(self, array_reduction, array_iterators, symbol_maxval_var, array_ref): - ''' xxx ''' - from psyclone.psyir.nodes import Reference, Assignment, IfBlock, BinaryOperation, ArrayReference + def _loop_body(self, array_reduction, array_iterators, symbol_var, + array_ref): + '''Provide the body of the nested loop that computes the maximum value + of an array. + + :param bool array_reduction: True if the implementation should \ + provide a maximum over a particular array dimension and False \ + if the maximum is for all elements the array. + :param array_iterators: a list of datasymbols containing the \ + loop iterators ordered from outermost loop symbol to innermost \ + loop symbol. + :type array_iterators: \ + List[:py:class:`psyclone.psyir.symbols.DataSymbol`] + :param symbol_var: the symbol used to store the final result. + :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param array_ref: a reference to the array from which the + maximum is being determined. + :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` + + :returns: PSyIR for the body of the nested loop. + :rtype: :py:class:`psyclone.psyir.nodes.IfBlock` + + ''' + # maxval_var() = array(i...) if array_reduction: array_indices = [Reference(iterator) for iterator in array_iterators] - lhs = ArrayReference.create(symbol_maxval_var, array_indices) + lhs = ArrayReference.create(symbol_var, array_indices) else: - lhs = Reference(symbol_maxval_var) - - # if maxval_var < array(i...) then - # maxval_var = array(i...) - # end if - + lhs = Reference(symbol_var) rhs = array_ref assignment = Assignment.create(lhs, rhs) + + # maxval_var() < array(i...) lhs = lhs.copy() - rhs = array_ref.copy() - if_condition = BinaryOperation.create(BinaryOperation.Operator.LT, - lhs, rhs) + rhs = rhs.copy() + if_condition = BinaryOperation.create( + BinaryOperation.Operator.LT, lhs, rhs) + + # if maxval_var() < array(i...) then + # maxval_var() = array(i...) + # end if return IfBlock.create(if_condition, [assignment]) - def _init_var(self, symbol_maxval_var, _, array_ref, loop_bounds, loop_iterators, array_iterators, array_reduction, dimension_literal): - ''' xxx ''' - from psyclone.psyir.nodes import Reference, Assignment, ArrayReference, Loop, Literal - from psyclone.psyir.symbols import INTEGER_TYPE - if array_reduction: - array_indices = [Reference(iterator) - for iterator in array_iterators] - lhs = ArrayReference.create(symbol_maxval_var, array_indices) - array_indices = [] - for idx in range(len(loop_bounds)): - if idx == int(dimension_literal.value)-1: - array_indices.append(loop_bounds[idx][0].copy()) - else: - array_indices.append(Reference(loop_iterators[idx])) - rhs = ArrayReference.create(array_ref.symbol, array_indices) - statement = Assignment.create(lhs, rhs) - for idx in range(len(loop_bounds)): - if idx != int(dimension_literal.value)-1: - statement = Loop.create( - loop_iterators[idx], loop_bounds[idx][0], loop_bounds[idx][1], - Literal("1", INTEGER_TYPE), [statement]) - return statement - else: - lhs = Reference(symbol_maxval_var) - array_indices = [] - for idx in range(len(loop_bounds)): - array_indices.append(loop_bounds[idx][0].copy()) - rhs = ArrayReference.create(array_ref.symbol, array_indices) - return Assignment.create(lhs, rhs) + def _init_var(self, symbol_var): + '''The initial value for the variable that computes the maximum value + of an array. + + :param symbol_var: the symbol used to store the final result. + :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + + :returns: PSyIR for the value to initialise the variable that \ + computes the maximum value. + :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + + ''' + return IntrinsicCall.create( + IntrinsicCall.Intrinsic.TINY, [Reference(symbol_var)]) diff --git a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py new file mode 100644 index 0000000000..2c4d380103 --- /dev/null +++ b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py @@ -0,0 +1,204 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, 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 + +'''Module providing a transformation from a PSyIR MINVAL intrinsic to +PSyIR code. This could be useful if the MINVAL operator is not +supported by the back-end, the required parallelisation approach, or +if the performance in the inline code is better than the intrinsic. + +''' +from psyclone.psyir.nodes import ( + Reference, Assignment, IfBlock, BinaryOperation, ArrayReference, + IntrinsicCall) +from psyclone.psyir.transformations.intrinsics.mms_base_trans import ( + MMSBaseTrans) + + +class Minval2CodeTrans(MMSBaseTrans): + '''Provides a transformation from a PSyIR MINVAL Operator node to + equivalent code in a PSyIR tree. Validity checks are also + performed. + + If MINVAL contains a single positional argument which is an array, + the minimum value of all of the elements in the array is returned + in the the scalar R. + + .. code-block:: python + + R = MINVAL(ARRAY) + + For example, if the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R = HUGE(R) + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + IF R > ARRAY(I,J) THEN + R = ARRAY(I,J) + + If the dimension argument is provided then the minimum value is + returned along the row for each entry in that dimension: + + .. code-block:: python + + R = MINVAL(ARRAY, dimension=2) + + If the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R(:) = HUGE(R) + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + IF R(I) > ARRAY(I,J) THEN + R(I) = ARRAY(I,J) + + If the mask argument is provided then the mask is used to + determine whether the minval is applied: + + .. code-block:: python + + R = MINVAL(ARRAY, mask=MOD(ARRAY, 2.0)==1) + + If the array is two dimensional, the equivalent code + for real data is: + + .. code-block:: python + + R = HUGE(R) + DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) + DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) + IF MOD(ARRAY(I,J), 2.0)==1 THEN + IF R > ARRAY(I,J) THEN + R = ARRAY(I,J) + + For example: + + >>> from psyclone.psyir.backend.fortran import FortranWriter + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.transformations import Minval2CodeTrans + >>> code = ("subroutine minval_test(array,n,m)\\n" + ... " real :: array(10,10)\\n" + ... " real :: result\\n" + ... " result = minval(array)\\n" + ... "end subroutine\\n") + >>> psyir = FortranReader().psyir_from_source(code) + >>> sum_node = psyir.children[0].children[0].children[1] + >>> Minval2CodeTrans().apply(sum_node) + >>> print(FortranWriter()(psyir)) + subroutine minval_test(array, n, m) + real, dimension(10,10) :: array + real :: result + real :: minval_var + integer :: i_0 + integer :: i_1 + + minval_var = HUGE(minval_var) + do i_1 = 1, 10, 1 + do i_0 = 1, 10, 1 + if (minval_var > array(i_0,i_1)) then + minval_var = array(i_0,i_1) + end if + enddo + enddo + result = minval_var + + end subroutine minval_test + + + ''' + _INTRINSIC_NAME = "MINVAL" + + def _loop_body(self, array_reduction, array_iterators, symbol_var, + array_ref): + '''Provide the body of the nested loop that computes the minimum value + of an array. + + :param bool array_reduction: True if the implementation should \ + provide a minimum over a particular array dimension and False \ + if the minimum is for all elements the array. + :param array_iterators: a list of datasymbols containing the \ + loop iterators ordered from outermost loop symbol to innermost \ + loop symbol. + :type array_iterators: \ + List[:py:class:`psyclone.psyir.symbols.DataSymbol`] + :param symbol_var: the symbol used to store the final result. + :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param array_ref: a reference to the array from which the + minimum is being determined. + :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` + + :returns: PSyIR for the body of the nested loop. + :rtype: :py:class:`psyclone.psyir.nodes.IfBlock` + + ''' + # minval_var() = array(i...) + if array_reduction: + array_indices = [Reference(iterator) + for iterator in array_iterators] + lhs = ArrayReference.create(symbol_var, array_indices) + else: + lhs = Reference(symbol_var) + rhs = array_ref + assignment = Assignment.create(lhs, rhs) + + # minval_var() > array(i...) + lhs = lhs.copy() + rhs = rhs.copy() + if_condition = BinaryOperation.create( + BinaryOperation.Operator.GT, lhs, rhs) + + # if minval_var() > array(i...) then + # minval_var() = array(i...) + # end if + return IfBlock.create(if_condition, [assignment]) + + def _init_var(self, symbol_var): + '''The initial value for the variable that computes the minimum value + of an array. + + :param symbol_var: the symbol used to store the final result. + :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + + :returns: PSyIR for the value to initialise the variable that \ + computes the minimum value. + :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` + + ''' + return IntrinsicCall.create( + IntrinsicCall.Intrinsic.HUGE, [Reference(symbol_var)]) diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index 48119c6050..f6a6dcdbd1 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -40,6 +40,7 @@ than the intrinsic. ''' +from abc import ABC, abstractmethod from psyclone.psyir.nodes import ( BinaryOperation, Assignment, Reference, @@ -51,7 +52,7 @@ TransformationError -class MMSBaseTrans(Transformation): +class MMSBaseTrans(Transformation, ABC): '''Provides a transformation from a PSyIR SUM Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -369,7 +370,10 @@ def apply(self, node, options=None): array_iterators.append(loop_iterator) # Initialise the temporary variable. - new_assignment = self._init_var(symbol_sum_var, scalar_type, array_ref, loop_bounds, loop_iterators, array_iterators, array_reduction, dimension_literal) + # new_assignment = self._init_var(symbol_sum_var, scalar_type, array_ref, loop_bounds, loop_iterators, array_iterators, array_reduction, dimension_literal) + rhs = self._init_var(symbol_sum_var) + lhs = reference.copy() + new_assignment = Assignment.create(lhs, rhs) assignment.parent.children.insert(assignment.position, new_assignment) array_indices = [] @@ -397,34 +401,10 @@ def apply(self, node, options=None): assignment.parent.children.insert(assignment.position, statement) - def _loop_body(self, array_reduction, array_iterators, symbol_sum_var, array_ref): - ''' xxx ''' - if array_reduction: - # sum_var(i,...) = sum_var(i,...) + array(i,...) - array_indices = [Reference(iterator) - for iterator in array_iterators] - lhs = ArrayReference.create(symbol_sum_var, array_indices) - array_indices = [Reference(iterator) - for iterator in array_iterators] - rhs_child1 = ArrayReference.create(symbol_sum_var, array_indices) - else: - # sum_var = sum_var + array(i,...) - lhs = Reference(symbol_sum_var) - rhs_child1 = Reference(symbol_sum_var) - - rhs_child2 = array_ref - rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, rhs_child1, - rhs_child2) - return Assignment.create(lhs, rhs) - - def _init_var(self, symbol_sum_var, scalar_type, _1, _2, _3, _4, _5, _6): - ''' xxx ''' - # sum_var=0.0 or 0 - lhs = Reference(symbol_sum_var) - if scalar_type.intrinsic == scalar_type.Intrinsic.REAL: - rhs = Literal("0.0", scalar_type) - elif scalar_type.intrinsic == scalar_type.Intrinsic.INTEGER: - rhs = Literal("0", scalar_type) - # Note, the validate method guarantees that an else branch is - # not required. - return Assignment.create(lhs, rhs) + @abstractmethod + def _loop_body(self, array_reduction, array_iterators, symbol_var, array_ref): + pass + + @abstractmethod + def _init_var(self, symbol_var): + pass diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index a7b60183bd..e545088623 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -41,16 +41,13 @@ ''' from psyclone.psyir.nodes import ( - BinaryOperation, Assignment, Reference, - Literal, Loop, ArrayReference, IfBlock, Range, IntrinsicCall) -from psyclone.psyir.symbols import ( - DataSymbol, INTEGER_TYPE, ScalarType, ArrayType) -from psyclone.psyGen import Transformation -from psyclone.psyir.transformations.transformation_error import \ - TransformationError + BinaryOperation, Assignment, Reference, Literal, ArrayReference) +from psyclone.psyir.symbols import ScalarType +from psyclone.psyir.transformations.intrinsics.mms_base_trans import ( + MMSBaseTrans) -class Sum2CodeTrans(Transformation): +class Sum2CodeTrans(MMSBaseTrans): '''Provides a transformation from a PSyIR SUM Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -144,264 +141,68 @@ class Sum2CodeTrans(Transformation): ''' - @staticmethod - def _get_args(node): - '''Utility method that returns the sum arguments, (array reference, - dimension and mask). - - :param node: a Sum intrinsic. - :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - - returns: a tuple containing the 3 sum arguments. - rtype: Tuple[py:class:`psyclone.psyir.nodes.reference.Reference`, \ - py:class:`psyclone.psyir.nodes.Literal` | \ - :py:class:`psyclone.psyir.nodes.Reference`, \ - Optional[:py:class:`psyclone.psyir.nodes.node`]] - - ''' - # Determine the arguments to sum - args = [None, None, None] - arg_names_map = {"array": 0, "dim": 1, "mask": 2} - for idx, child in enumerate(node.children): - if not node.argument_names[idx]: - # positional arg - args[idx] = child - else: - # named arg - name = node.argument_names[idx].lower() - args[arg_names_map[name]] = child - array_ref = args[0] - dimension_ref = args[1] - mask_ref = args[2] - return (array_ref, dimension_ref, mask_ref) - - def __str__(self): - return "Convert the PSyIR SUM intrinsic to equivalent PSyIR code." - - def validate(self, node, options=None): - '''Check that the input node is valid before applying the - transformation. - - :param node: a Sum intrinsic. - :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - :param options: options for the transformation. - :type options: Optional[Dict[str, Any]] - - :raises TransformationError: if the supplied node is not an \ - intrinsic. - :raises TransformationError: if the supplied node is not a sum \ - intrinsic. - :raises TransformationError: if a valid value for the \ - dimension argument can't be determined. - :raises TransformationError: if the array argument is not an array. - :raises TransformationError: if the shape of the array is not \ - supported. - :raises TransformationError: if the array datatype is not \ - supported. - - ''' - if not isinstance(node, IntrinsicCall): - raise TransformationError( - f"Error in {self.name} transformation. The supplied node " - f"argument is not an intrinsic, found " - f"'{type(node).__name__}'.") - - if node.routine.name.lower() != "sum": - raise TransformationError( - f"Error in {self.name} transformation. The supplied node " - f"argument is not a sum intrinsic, found " - f"'{node.routine.name}'.") - - array_ref, dim_ref, _ = self._get_args(node) - if dim_ref and not isinstance(dim_ref, (Literal, Reference)): - raise TransformationError( - f"Can't find the value of the dimension argument. Expected " - f"it to be a literal or a reference but found " - f"'{dim_ref.debug_string()}' which is a " - f"'{type(dim_ref).__name__}'.") - - # pylint: disable=unidiomatic-typecheck - if not (isinstance(array_ref, ArrayReference) or - type(array_ref) == Reference): - raise TransformationError( - f"Sum2CodeTrans only support arrays for the first argument, " - f"but found '{type(array_ref).__name__}'.") - - if len(array_ref.children) == 0: - if not array_ref.symbol.is_array: - raise TransformationError( - f"Expected '{array_ref.name}' to be an array.") - - for shape in array_ref.children: - if not isinstance(shape, Range): - raise TransformationError( - f"Sum2CodeTrans only supports arrays with array ranges, " - f"but found a fixed dimension in " - f"'{array_ref.debug_string()}'.") - - try: - _ = array_ref.symbol.shape - except TypeError as err: - raise TransformationError( - f"Unexpected shape for array '{array_ref.symbol.name}': " - f"{err}") from err - - array_intrinsic = array_ref.symbol.datatype.intrinsic - if array_intrinsic not in [ScalarType.Intrinsic.REAL, - ScalarType.Intrinsic.INTEGER]: - raise TransformationError( - f"Only real and integer types supported for array " - f"'{array_ref.name}', but found '{array_intrinsic.name}'.") - - # pylint: disable=too-many-locals - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - def apply(self, node, options=None): - '''Apply the SUM intrinsic conversion transformation to the specified - node. This node must be a SUM Operation which is converted to - equivalent inline code. - - :param node: a Sum intrinsic. - :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - :param options: options for the transformation. - :type options: Optional[Dict[str, Any]] + _INTRINSIC_NAME = "SUM" + + def _loop_body(self, array_reduction, array_iterators, symbol_var, + array_ref): + '''Provide the body of the nested loop that computes the sum of an + array. + + :param bool array_reduction: True if the implementation should \ + provide a sum over a particular array dimension and False \ + if the sum is for all elements the array. + :param array_iterators: a list of datasymbols containing the \ + loop iterators ordered from outermost loop symbol to innermost \ + loop symbol. + :type array_iterators: \ + List[:py:class:`psyclone.psyir.symbols.DataSymbol`] + :param symbol_var: the symbol used to store the final result. + :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param array_ref: a reference to the array from which the + sum is being determined. + :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` + + :returns: PSyIR for the body of the nested loop. + :rtype: :py:class:`psyclone.psyir.nodes.Assignment` ''' - self.validate(node) - - array_ref, dimension_ref, mask_ref = self._get_args(node) - - # Determine the literal value of the dimension argument - dimension_literal = None - if not dimension_ref: - # there is no dimension argument - pass - elif isinstance(dimension_ref, Literal): - dimension_literal = dimension_ref - elif (isinstance(dimension_ref, Reference) and - dimension_ref.symbol.is_constant): - dimension_literal = dimension_ref.symbol.constant_value - # else exception is handled by the validate method. - - # Determine the dimension and extent of the array - ndims = None - if len(array_ref.children) == 0: - # Note, the potential 'if not array_ref.symbol.is_array:' - # exception is already handled by the validate method. - ndims = len(array_ref.symbol.shape) - - loop_bounds = [] - for idx, shape in enumerate(array_ref.symbol.shape): - if shape in [ArrayType.Extent.DEFERRED, - ArrayType.Extent.ATTRIBUTE]: - # runtime extent using LBOUND and UBOUND required - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(array_ref.symbol), - Literal(str(idx+1), INTEGER_TYPE)) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(array_ref.symbol), - Literal(str(idx+1), INTEGER_TYPE)) - loop_bounds.append((lbound, ubound)) - elif isinstance(shape, ArrayType.ArrayBounds): - # array extent is defined in the array declaration - loop_bounds.append(shape) - # Note, the validate method guarantees that an else - # clause is not required. - else: - # The validate method guarantees that this is an array - # reference. - loop_bounds = [] - ndims = len(array_ref.children) - for shape in array_ref.children: - loop_bounds.append((shape.start.copy(), shape.stop.copy())) - - # Determine the datatype of the array's values and create a - # scalar of that type - array_intrinsic = array_ref.symbol.datatype.intrinsic - array_precision = array_ref.symbol.datatype.precision - scalar_type = ScalarType(array_intrinsic, array_precision) - - symbol_table = node.scope.symbol_table - assignment = node.ancestor(Assignment) - - datatype = scalar_type - array_reduction = False - if dimension_ref and ndims > 1: - array_reduction = True - # We are reducing from one array to another - shape = [] - for idx, bounds in enumerate(loop_bounds): - if int(dimension_literal.value)-1 == idx: - pass - else: - shape.append(bounds) - datatype = ArrayType(scalar_type, shape) - - # Create temporary sum variable (sum_var) - symbol_sum_var = symbol_table.new_symbol( - "sum_var", symbol_type=DataSymbol, datatype=datatype) - - # Replace operation with a temporary variable (sum_var). - node.replace_with(Reference(symbol_sum_var)) - - # sum_var=0.0 or 0 - lhs = Reference(symbol_sum_var) - if scalar_type.intrinsic == scalar_type.Intrinsic.REAL: - rhs = Literal("0.0", scalar_type) - elif scalar_type.intrinsic == scalar_type.Intrinsic.INTEGER: - rhs = Literal("0", scalar_type) - # Note, the validate method guarantees that an else branch is - # not required. - - new_assignment = Assignment.create(lhs, rhs) - assignment.parent.children.insert(assignment.position, new_assignment) - - # Create the loop iterators - loop_iterators = [] - array_iterators = [] - for idx in range(ndims): - loop_iterator = symbol_table.new_symbol( - f"i_{idx}", symbol_type=DataSymbol, datatype=INTEGER_TYPE) - loop_iterators.append(loop_iterator) - if array_reduction and idx != int(dimension_literal.value)-1: - array_iterators.append(loop_iterator) - if array_reduction: # sum_var(i,...) = sum_var(i,...) + array(i,...) array_indices = [Reference(iterator) for iterator in array_iterators] - lhs = ArrayReference.create(symbol_sum_var, array_indices) + lhs = ArrayReference.create(symbol_var, array_indices) array_indices = [Reference(iterator) for iterator in array_iterators] - rhs_child1 = ArrayReference.create(symbol_sum_var, array_indices) + rhs_child1 = ArrayReference.create(symbol_var, array_indices) else: # sum_var = sum_var + array(i,...) - lhs = Reference(symbol_sum_var) - rhs_child1 = Reference(symbol_sum_var) + lhs = Reference(symbol_var) + rhs_child1 = Reference(symbol_var) - rhs_child2 = node.children[0].detach() + rhs_child2 = array_ref rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, rhs_child1, rhs_child2) - statement = Assignment.create(lhs, rhs) - if mask_ref: - # A mask argument has been provided - for ref in mask_ref.walk(Reference): - if ref.name == array_ref.name: - # The array needs indexing - shape = [Reference(obj) for obj in loop_iterators] - reference = ArrayReference.create(ref.symbol, shape) - ref.replace_with(reference) - statement = IfBlock.create(mask_ref.detach(), [statement]) - - array_indices = [] - for idx in range(ndims): - statement = Loop.create( - loop_iterators[idx], loop_bounds[idx][0], loop_bounds[idx][1], - Literal("1", INTEGER_TYPE), [statement]) - array_indices.append(Reference(loop_iterators[idx])) - - assignment.parent.children.insert(assignment.position, statement) - rhs_child2.replace_with( - ArrayReference.create(rhs_child2.symbol, array_indices)) + return Assignment.create(lhs, rhs) + + def _init_var(self, symbol_var): + '''The initial value for the variable that computes the sum + of an array. + + :param symbol_var: the symbol used to store the final result. + :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + + :returns: PSyIR for the value to initialise the variable that \ + computes the sum. + :rtype: :py:class:`psyclone.psyir.nodes.Literal` + + ''' + intrinsic = symbol_var.datatype.intrinsic + precision = symbol_var.datatype.precision + scalar_type = ScalarType(intrinsic, precision) + if intrinsic == ScalarType.Intrinsic.REAL: + value_str = "0.0" + elif intrinsic == ScalarType.Intrinsic.INTEGER: + value_str = "0" + # Note, the validate method guarantees that an else branch is + # not required. + return Literal(value_str, scalar_type) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py index 9dadf4740b..8635704457 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py @@ -91,7 +91,7 @@ def test_apply_maxval(idim1, idim2, rdim11, rdim12, rdim21, rdim22, f" real, dimension({idim1},{idim2}) :: array\n" f" real :: result\n real :: maxval_var\n" f" integer :: i_0\n integer :: i_1\n\n" - f" maxval_var = array({rdim11},{rdim21})\n" + f" maxval_var = TINY(maxval_var)\n" f" do i_1 = {rdim21}, {rdim22}, 1\n" f" do i_0 = {rdim11}, {rdim12}, 1\n" f" if (maxval_var < array(i_0,i_1)) then\n" @@ -135,7 +135,7 @@ def test_apply_sum_multi(fortran_reader, fortran_writer, datatype, zero): f" real :: value1\n real :: value2\n" f" real :: result\n {datatype} :: maxval_var\n" f" integer :: i_0\n integer :: i_1\n\n" - f" maxval_var = array(1,1)\n" + f" maxval_var = TINY(maxval_var)\n" f" do i_1 = 1, m, 1\n" f" do i_0 = 1, n, 1\n" f" if (maxval_var < array(i_0,i_1)) then\n" @@ -175,7 +175,7 @@ def test_apply_dimension_1d(fortran_reader, fortran_writer): " real :: value1\n real :: value2\n" " real :: result\n real :: maxval_var\n" " integer :: i_0\n\n" - " maxval_var = array(LBOUND(array, 1))\n" + " maxval_var = TINY(maxval_var)\n" " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" " if (maxval_var < array(i_0)) then\n" " maxval_var = array(i_0)\n" @@ -216,11 +216,7 @@ def test_apply_dimension_multid(fortran_reader, fortran_writer): " real, dimension(n,p) :: result\n" " real, dimension(n,p) :: maxval_var\n" " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " do i_2 = 1, p, 1\n" - " do i_0 = 1, n, 1\n" - " maxval_var(i_0,i_2) = array(i_0,1,i_2)\n" - " enddo\n" - " enddo\n" + " maxval_var(:,:) = TINY(maxval_var)\n" " do i_2 = 1, p, 1\n" " do i_1 = 1, m, 1\n" " do i_0 = 1, n, 1\n" @@ -266,11 +262,7 @@ def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): " integer :: i_0\n" " integer :: i_1\n" " integer :: i_2\n\n" - " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" - " maxval_var(i_0,i_2) = array(i_0,LBOUND(array, 2),i_2)\n" - " enddo\n" - " enddo\n" + " maxval_var(:,:) = TINY(maxval_var)\n" " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" @@ -317,11 +309,7 @@ def test_apply_dimension_multid_range(fortran_reader, fortran_writer): " real, dimension(n,p) :: result\n" " real, dimension(n,p) :: maxval_var\n" " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " do i_2 = 1, p, 1\n" - " do i_0 = 1, n, 1\n" - " maxval_var(i_0,i_2) = array(i_0,m - 1,i_2)\n" - " enddo\n" - " enddo\n" + " maxval_var(:,:) = TINY(maxval_var)\n" " do i_2 = 1, p, 1\n" " do i_1 = m - 1, m, 1\n" " do i_0 = 1, n, 1\n" @@ -362,7 +350,7 @@ def test_mask(): " real :: maxval_var\n" " integer :: i_0\n" " integer :: i_1\n\n" - " maxval_var = array(1,1)\n" + " maxval_var = TINY(maxval_var)\n" " do i_1 = 1, 10, 1\n" " do i_0 = 1, 10, 1\n" " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" @@ -405,9 +393,7 @@ def test_mask_dimension(): " real, dimension(10) :: maxval_var\n" " integer :: i_0\n" " integer :: i_1\n\n" - " do i_0 = 1, 10, 1\n" - " maxval_var(i_0) = array(i_0,1)\n" - " enddo\n" + " maxval_var(:) = TINY(maxval_var)\n" " do i_1 = 1, 10, 1\n" " do i_0 = 1, 10, 1\n" " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py index 6b9682d1bb..80d3072747 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py @@ -185,9 +185,11 @@ def test_array_shape(fortran_reader, monkeypatch): monkeypatch.setattr(array_symbol._datatype, "_shape", [None]) trans = Sum2CodeTrans() - with pytest.raises(TransformationError) as info: + with pytest.raises(TypeError) as info: trans.validate(sum_node) - assert ("Unexpected shape for array 'array': " in str(info.value)) + assert ("ArrayType shape-list elements can only be 'int', " + "ArrayType.Extent, 'DataNode' or a 2-tuple thereof but found " + "'NoneType'." in str(info.value)) def test_array_type_arg(fortran_reader): @@ -359,7 +361,7 @@ def test_apply_dimension_multid(fortran_reader, fortran_writer): " real, dimension(n,p) :: result\n" " real, dimension(n,p) :: sum_var\n" " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " sum_var = 0.0\n" + " sum_var(:,:) = 0.0\n" " do i_2 = 1, p, 1\n" " do i_1 = 1, m, 1\n" " do i_0 = 1, n, 1\n" @@ -367,7 +369,7 @@ def test_apply_dimension_multid(fortran_reader, fortran_writer): " enddo\n" " enddo\n" " enddo\n" - " result(:,:) = value1 + sum_var * value2\n\n" + " result(:,:) = value1 + sum_var(:,:) * value2\n\n" "end subroutine sum_test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ @@ -403,7 +405,7 @@ def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): " integer :: i_0\n" " integer :: i_1\n" " integer :: i_2\n\n" - " sum_var = 0.0\n" + " sum_var(:,:) = 0.0\n" " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" @@ -411,7 +413,7 @@ def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): " enddo\n" " enddo\n" " enddo\n" - " result(:,:) = value1 + sum_var * value2\n\n" + " result(:,:) = value1 + sum_var(:,:) * value2\n\n" "end subroutine sum_test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ @@ -448,7 +450,7 @@ def test_apply_dimension_multid_range(fortran_reader, fortran_writer): " real, dimension(n,p) :: result\n" " real, dimension(n,p) :: sum_var\n" " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " sum_var = 0.0\n" + " sum_var(:,:) = 0.0\n" " do i_2 = 1, p, 1\n" " do i_1 = m - 1, m, 1\n" " do i_0 = 1, n, 1\n" @@ -456,7 +458,7 @@ def test_apply_dimension_multid_range(fortran_reader, fortran_writer): " enddo\n" " enddo\n" " enddo\n" - " result(:,:) = value1 + sum_var * value2\n\n" + " result(:,:) = value1 + sum_var(:,:) * value2\n\n" "end subroutine sum_test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ @@ -528,7 +530,7 @@ def test_mask_dimension(): " real, dimension(10) :: sum_var\n" " integer :: i_0\n" " integer :: i_1\n\n" - " sum_var = 0.0\n" + " sum_var(:) = 0.0\n" " do i_1 = 1, 10, 1\n" " do i_0 = 1, 10, 1\n" " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" @@ -536,7 +538,7 @@ def test_mask_dimension(): " end if\n" " enddo\n" " enddo\n" - " result = sum_var\n\n" + " result = sum_var(:)\n\n" "end program sum_test") reader = FortranReader() psyir = reader.psyir_from_source(code) From fe6035eb5c50b7b14f8492f933379f79206a898c Mon Sep 17 00:00:00 2001 From: rupertford Date: Mon, 22 May 2023 20:12:21 +0100 Subject: [PATCH 03/11] pr #2148. Updated sum2ode transformation and added mms_base tests. --- .../intrinsics/mms_base_trans.py | 107 +----- .../intrinsics/mms_base_trans_test.py | 314 ++++++++++++++++++ .../intrinsics/sum2code_trans_test.py | 152 +-------- 3 files changed, 325 insertions(+), 248 deletions(-) create mode 100644 src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index f6a6dcdbd1..0ced105cc7 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -53,117 +53,29 @@ class MMSBaseTrans(Transformation, ABC): - '''Provides a transformation from a PSyIR SUM Operator node to - equivalent code in a PSyIR tree. Validity checks are also - performed. - - If SUM contains a single positional argument which is an array, - all element on that array are summed and the result returned in - the scalar R. - - .. code-block:: python - - R = SUM(ARRAY) - - For example, if the array is two dimensional, the equivalent code - for real data is: - - .. code-block:: python - - R = 0.0 - DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) - DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - R = R + ARRAY(I,J) - - If the dimension argument is provided then only that dimension is - summed: - - .. code-block:: python - - R = SUM(ARRAY, dimension=2) - - If the array is two dimensional, the equivalent code - for real data is: - - .. code-block:: python - - R(:) = 0.0 - DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) - DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - R(I) = R(I) + ARRAY(I,J) - - If the mask argument is provided then the mask is used to - determine whether the sum is applied: - - .. code-block:: python - - R = SUM(ARRAY, mask=MOD(ARRAY, 2.0)==1) - - If the array is two dimensional, the equivalent code - for real data is: - - .. code-block:: python - - R = 0.0 - DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) - DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - if (MOD(ARRAY(I,J), 2.0)==1): - R = R + ARRAY(I,J) - - For example: - - >>> from psyclone.psyir.backend.fortran import FortranWriter - >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.psyir.transformations import Sum2CodeTrans - >>> code = ("subroutine sum_test(array,n,m)\\n" - ... " integer :: n, m\\n" - ... " real :: array(10,10)\\n" - ... " real :: result\\n" - ... " result = sum(array)\\n" - ... "end subroutine\\n") - >>> psyir = FortranReader().psyir_from_source(code) - >>> sum_node = psyir.children[0].children[0].children[1] - >>> Sum2CodeTrans().apply(sum_node) - >>> print(FortranWriter()(psyir)) - subroutine sum_test(array, n, m) - integer :: n - integer :: m - real, dimension(10,10) :: array - real :: result - real :: sum_var - integer :: i_0 - integer :: i_1 - - sum_var = 0.0 - do i_1 = 1, 10, 1 - do i_0 = 1, 10, 1 - sum_var = sum_var + array(i_0,i_1) - enddo - enddo - result = sum_var - - end subroutine sum_test - + '''An abstract parent class providing common functionality to the + sum2code_trans, minval2code_trans and maxval2_code trans + tranformations. ''' _INTRINSIC_NAME = None @staticmethod def _get_args(node): - '''Utility method that returns the sum arguments, (array reference, - dimension and mask). + '''Utility method that returns the minval, maxval or sum arguments, + (array reference, dimension and mask). - :param node: a Sum intrinsic. + :param node: a minval, maxval or sum intrinsic. :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - returns: a tuple containing the 3 sum arguments. + returns: a tuple containing the 3 arguments. rtype: Tuple[py:class:`psyclone.psyir.nodes.reference.Reference`, \ py:class:`psyclone.psyir.nodes.Literal` | \ :py:class:`psyclone.psyir.nodes.Reference`, \ Optional[:py:class:`psyclone.psyir.nodes.node`]] ''' - # Determine the arguments to sum + # Determine the arguments to the intrinsic args = [None, None, None] arg_names_map = {"array": 0, "dim": 1, "mask": 2} for idx, child in enumerate(node.children): @@ -180,7 +92,8 @@ def _get_args(node): return (array_ref, dimension_ref, mask_ref) def __str__(self): - return f"Convert the PSyIR {self._INTRINSIC_NAME} intrinsic to equivalent PSyIR code." + return (f"Convert the PSyIR {self._INTRINSIC_NAME} intrinsic " + "to equivalent PSyIR code.") def validate(self, node, options=None): '''Check that the input node is valid before applying the diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py new file mode 100644 index 0000000000..08fd0632fb --- /dev/null +++ b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py @@ -0,0 +1,314 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, 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 Laboratory + +'''Module containing tests for the mms_base_trans which is an abstract +parent class for the sum2code_trans, minval2code_trans and +maxval2code_trans transformations. + +''' +import pytest + +#from psyclone.psyir.frontend.fortran import FortranReader +#from psyclone.psyir.backend.fortran import FortranWriter +from psyclone.psyir.nodes import IntrinsicCall, Reference, Literal, Assignment +from psyclone.psyir.symbols import ( + Symbol, BOOLEAN_TYPE, INTEGER_TYPE, DataSymbol, REAL_TYPE) +from psyclone.psyir.transformations import TransformationError +from psyclone.psyir.transformations.intrinsics.mms_base_trans import ( + MMSBaseTrans) + + +class TestTrans(MMSBaseTrans): + '''Utility class to allow the abstract MMSBaseTrans to be tested.''' + + def _loop_body(self, _1, _2, _3, _4): + '''Minimal implementation of the abstract _loop_body method.''' + node = Assignment.create(Literal("99.0", REAL_TYPE), Literal("33.0", REAL_TYPE)) + return node + + def _init_var(self, _): + '''Minimal implementation of the abstract _init_var method.''' + node = Literal("99.0", REAL_TYPE) + return node + + +class FakeSumTrans(TestTrans): + '''Utility class to allow the abstract MMSBaseTrans to be tested. Acts + as if it modifies the SUM intrinsic to help with testing. + + ''' + _INTRINSIC_NAME = "SUM" + + +def test_init_exception(): + '''Check that this class can't be created as it is abstract.''' + with pytest.raises(TypeError) as info: + _ = MMSBaseTrans() + assert ("Can't instantiate abstract class MMSBaseTrans with abstract " + "methods _init_var, _loop_body" in str(info.value)) + + +def test_get_args(): + '''Check the _get_args static method works as expected.''' + # array + array_reference = Reference(Symbol("array")) + node = IntrinsicCall.create(IntrinsicCall.Intrinsic.SUM, [array_reference]) + result = MMSBaseTrans._get_args(node) + assert result == (array_reference, None, None) + + # array, mask, dim + mask_reference = Literal("true", BOOLEAN_TYPE) + dim_reference = Literal("1", INTEGER_TYPE) + node = IntrinsicCall.create(IntrinsicCall.Intrinsic.SUM, [ + array_reference.copy(), ("mask", mask_reference), + ("dim", dim_reference)]) + result = MMSBaseTrans._get_args(node) + assert result == (array_reference, dim_reference, mask_reference) + + +def test_str(): + ''' Check that the __str__ method behaves as expected. ''' + assert str(TestTrans()) == ("Convert the PSyIR None intrinsic to " + "equivalent PSyIR code.") + assert str(FakeSumTrans()) == ("Convert the PSyIR SUM intrinsic to " + "equivalent PSyIR code.") + + +# validate method + +def test_validate_node(): + '''Check that an incorrect node raises the expected exception.''' + trans = FakeSumTrans() + with pytest.raises(TransformationError) as info: + trans.validate(None) + assert ("Error in FakeSumTrans transformation. The supplied node " + "argument is not an intrinsic, found 'NoneType'." + in str(info.value)) + + intrinsic = IntrinsicCall.create( + IntrinsicCall.Intrinsic.MINVAL, + [Reference(DataSymbol("array", REAL_TYPE))]) + with pytest.raises(TransformationError) as info: + trans.validate(intrinsic) + assert ("The supplied node argument is not a sum intrinsic, found " + "'MINVAL'." in str(info.value)) + + +def test_structure_error(fortran_reader): + '''Test that the transformation raises an exception if the array node + is part of a structure, as this is not currently supported. + + ''' + code = ( + "subroutine sum_test(n,m)\n" + " integer :: n, m\n" + " type :: array_type\n" + " real :: array(10,10)\n" + " end type\n" + " type(array_type) :: ref\n" + " real :: result\n" + " integer :: dimension\n" + " result = sum(ref%array)\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + sum_node = psyir.children[0].children[0].children[1] + trans = FakeSumTrans() + with pytest.raises(TransformationError) as info: + trans.validate(sum_node) + assert ("FakeSumTrans only support arrays for the first argument, but " + "found 'StructureReference'." in str(info.value)) + + +def test_indexed_array_error(fortran_reader): + '''Test that the transformation raises an exception if the array node + has a literal index, as this is invalid. + + ''' + code = ( + "subroutine sum_test(array,n,m)\n" + " integer :: n, m\n" + " real :: array(10,10)\n" + " real :: result\n" + " integer :: dimension\n" + " result = sum(array(1,1))\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + sum_node = psyir.children[0].children[0].children[1] + trans = FakeSumTrans() + with pytest.raises(TransformationError) as info: + trans.validate(sum_node) + assert ("FakeSumTrans only supports arrays with array ranges, but " + "found a fixed dimension in 'array(1,1)'." in str(info.value)) + + +def test_dimension_arg(fortran_reader): + '''Test that the expected exception is raised if the dimension arg is + not a literal or a variable. + + ''' + code = ( + "subroutine sum_test(array,n,m)\n" + " integer :: n, m\n" + " real :: array(10,10)\n" + " real :: result\n" + " integer :: dimension\n" + " result = sum(array, dim=dimension*2)\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + trans = FakeSumTrans() + with pytest.raises(TransformationError) as info: + trans.validate(sum_node) + assert ("Can't find the value of the dimension argument. Expected it to " + "be a literal or a reference but found 'dimension * 2' which is " + "a 'BinaryOperation'." in str(info.value)) + + +def test_array_arg(fortran_reader): + '''Test that the expected exception is raised if the array argument is + not an array. + + ''' + code = ( + "subroutine sum_test(array,n,m)\n" + " integer :: n, m\n" + " real :: array\n" + " real :: result\n" + " result = sum(array)\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + trans = FakeSumTrans() + with pytest.raises(TransformationError) as info: + trans.validate(sum_node) + assert "Expected 'array' to be an array." in str(info.value) + + +def test_array_shape(fortran_reader, monkeypatch): + '''Tests that the expected exception is raised if the array shape is + not a valid value. Requires monkeypatching. + + ''' + code = ( + "subroutine sum_test(array,n,m)\n" + " integer :: n, m\n" + " real :: array(1)\n" + " real :: result\n" + " result = sum(array)\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + + # Modify array shape from sum_node to create exception + array_ref = sum_node.children[0] + array_symbol = array_ref.symbol + monkeypatch.setattr(array_symbol._datatype, "_shape", [None]) + + trans = FakeSumTrans() + with pytest.raises(TypeError) as info: + trans.validate(sum_node) + assert ("ArrayType shape-list elements can only be 'int', " + "ArrayType.Extent, 'DataNode' or a 2-tuple thereof but found " + "'NoneType'." in str(info.value)) + + +def test_array_type_arg(fortran_reader): + '''Test that the expected exception is raised if the array is an + unsupported datatype. + + ''' + code = ( + "subroutine sum_test(array,n,m)\n" + " integer :: n, m\n" + " logical :: array(10)\n" + " real :: result\n" + " result = sum(array)\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + trans = FakeSumTrans() + with pytest.raises(TransformationError) as info: + trans.validate(sum_node) + assert ("Only real and integer types supported for array 'array', " + "but found 'BOOLEAN'." in str(info.value)) + + +# apply + +@pytest.mark.parametrize("idim1,idim2,rdim11,rdim12,rdim21,rdim22", + [("10", "20", "1", "10", "1", "20"), + ("n", "m", "1", "n", "1", "m"), + ("0:n", "2:m", "0", "n", "2", "m"), + (":", ":", "LBOUND(array, 1)", "UBOUND(array, 1)", + "LBOUND(array, 2)", "UBOUND(array, 2)")]) +def test_apply_sum(idim1, idim2, rdim11, rdim12, rdim21, rdim22, + fortran_reader, fortran_writer): + '''Test that a sum intrinsic as the only term on the rhs of an + assignment with a single array argument gets transformed as + expected. Test with known and unknown array sizes. + + ''' + code = ( + f"subroutine sum_test(array,n,m)\n" + f" integer :: n, m\n" + f" real :: array({idim1},{idim2})\n" + f" real :: result\n" + f" result = sum(array)\n" + f"end subroutine\n") + expected = ( + f"subroutine sum_test(array, n, m)\n" + f" integer :: n\n integer :: m\n" + f" real, dimension({idim1},{idim2}) :: array\n" + f" real :: result\n real :: sum_var\n" + f" integer :: i_0\n integer :: i_1\n\n" + f" sum_var = 99.0\n" + f" do i_1 = {rdim21}, {rdim22}, 1\n" + f" do i_0 = {rdim11}, {rdim12}, 1\n" + f" 99.0 = 33.0\n" + f" enddo\n" + f" enddo\n" + f" result = sum_var\n\n" + f"end subroutine sum_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + sum_node = psyir.children[0].children[0].children[1] + trans = FakeSumTrans() + trans.apply(sum_node) + result = fortran_writer(psyir) + assert result == expected diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py index 80d3072747..cd468caf0b 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py @@ -53,6 +53,7 @@ def test_initialise(): assert trans.name == "Sum2CodeTrans" +# Check validate is called def test_validate_node(): '''Check that an incorrect node raises the expected exception.''' trans = Sum2CodeTrans() @@ -62,157 +63,6 @@ def test_validate_node(): "argument is not an intrinsic, found 'NoneType'." in str(info.value)) - intrinsic = IntrinsicCall.create( - IntrinsicCall.Intrinsic.MINVAL, - [Reference(DataSymbol("array", REAL_TYPE))]) - with pytest.raises(TransformationError) as info: - trans.validate(intrinsic) - assert ("The supplied node argument is not a sum intrinsic, found " - "'MINVAL'." in str(info.value)) - - -def test_structure_error(fortran_reader): - '''Test that the transformation raises an exception if the array node - is part of a structure, as this is not currently supported. - - ''' - code = ( - "subroutine sum_test(n,m)\n" - " integer :: n, m\n" - " type :: array_type\n" - " real :: array(10,10)\n" - " end type\n" - " type(array_type) :: ref\n" - " real :: result\n" - " integer :: dimension\n" - " result = sum(ref%array)\n" - "end subroutine\n") - psyir = fortran_reader.psyir_from_source(code) - sum_node = psyir.children[0].children[0].children[1] - trans = Sum2CodeTrans() - with pytest.raises(TransformationError) as info: - trans.validate(sum_node) - assert ("Sum2CodeTrans only support arrays for the first argument, but " - "found 'StructureReference'." in str(info.value)) - - -def test_indexed_array_error(fortran_reader): - '''Test that the transformation raises an exception if the array node - has a literal index, as this is invalid. - - ''' - code = ( - "subroutine sum_test(array,n,m)\n" - " integer :: n, m\n" - " real :: array(10,10)\n" - " real :: result\n" - " integer :: dimension\n" - " result = sum(array(1,1))\n" - "end subroutine\n") - psyir = fortran_reader.psyir_from_source(code) - sum_node = psyir.children[0].children[0].children[1] - trans = Sum2CodeTrans() - with pytest.raises(TransformationError) as info: - trans.validate(sum_node) - assert ("Sum2CodeTrans only supports arrays with array ranges, but " - "found a fixed dimension in 'array(1,1)'." in str(info.value)) - - -def test_dimension_arg(fortran_reader): - '''Test that the expected exception is raised if the dimension arg is - not a literal or a variable. - - ''' - code = ( - "subroutine sum_test(array,n,m)\n" - " integer :: n, m\n" - " real :: array(10,10)\n" - " real :: result\n" - " integer :: dimension\n" - " result = sum(array, dim=dimension*2)\n" - "end subroutine\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = Sum2CodeTrans() - with pytest.raises(TransformationError) as info: - trans.validate(sum_node) - assert ("Can't find the value of the dimension argument. Expected it to " - "be a literal or a reference but found 'dimension * 2' which is " - "a 'BinaryOperation'." in str(info.value)) - - -def test_array_arg(fortran_reader): - '''Test that the expected exception is raised if the array argument is - not an array. - - ''' - code = ( - "subroutine sum_test(array,n,m)\n" - " integer :: n, m\n" - " real :: array\n" - " real :: result\n" - " result = sum(array)\n" - "end subroutine\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = Sum2CodeTrans() - with pytest.raises(TransformationError) as info: - trans.validate(sum_node) - assert "Expected 'array' to be an array." in str(info.value) - - -def test_array_shape(fortran_reader, monkeypatch): - '''Tests that the expected exception is raised if the array shape is - not a valid value. Requires monkeypatching. - - ''' - code = ( - "subroutine sum_test(array,n,m)\n" - " integer :: n, m\n" - " real :: array(1)\n" - " real :: result\n" - " result = sum(array)\n" - "end subroutine\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - - # Modify array shape from sum_node to create exception - array_ref = sum_node.children[0] - array_symbol = array_ref.symbol - monkeypatch.setattr(array_symbol._datatype, "_shape", [None]) - - trans = Sum2CodeTrans() - with pytest.raises(TypeError) as info: - trans.validate(sum_node) - assert ("ArrayType shape-list elements can only be 'int', " - "ArrayType.Extent, 'DataNode' or a 2-tuple thereof but found " - "'NoneType'." in str(info.value)) - - -def test_array_type_arg(fortran_reader): - '''Test that the expected exception is raised if the array is an - unsupported datatype. - - ''' - code = ( - "subroutine sum_test(array,n,m)\n" - " integer :: n, m\n" - " logical :: array(10)\n" - " real :: result\n" - " result = sum(array)\n" - "end subroutine\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = Sum2CodeTrans() - with pytest.raises(TransformationError) as info: - trans.validate(sum_node) - assert ("Only real and integer types supported for array 'array', " - "but found 'BOOLEAN'." in str(info.value)) - # apply tests From 77970b174bf84556babee04b9b4833d4ebf24013 Mon Sep 17 00:00:00 2001 From: rupertford Date: Sun, 11 Jun 2023 20:10:00 +0100 Subject: [PATCH 04/11] pr #2148. Added tests. --- .../psyir/transformations/__init__.py | 4 +- .../intrinsics/maxval2code_trans.py | 20 +- .../intrinsics/minval2code_trans.py | 18 +- .../intrinsics/mms_base_trans.py | 59 +-- .../intrinsics/sum2code_trans.py | 28 +- .../intrinsics/maxval2code_trans_test.py | 427 ++++-------------- .../intrinsics/minval2code_trans_test.py | 175 +++++++ .../intrinsics/mms_base_trans_test.py | 407 +++++++++++++++-- .../intrinsics/sum2code_trans_test.py | 400 ++++------------ 9 files changed, 779 insertions(+), 759 deletions(-) create mode 100644 src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py diff --git a/src/psyclone/psyir/transformations/__init__.py b/src/psyclone/psyir/transformations/__init__.py index 46c5b8a771..70dad16057 100644 --- a/src/psyclone/psyir/transformations/__init__.py +++ b/src/psyclone/psyir/transformations/__init__.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2022, Science and Technology Facilities Council. +# Copyright (c) 2019-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -70,6 +70,8 @@ Maxval2CodeTrans from psyclone.psyir.transformations.intrinsics.min2code_trans import \ Min2CodeTrans +from psyclone.psyir.transformations.intrinsics.minval2code_trans import \ + Minval2CodeTrans from psyclone.psyir.transformations.intrinsics.sign2code_trans import \ Sign2CodeTrans from psyclone.psyir.transformations.intrinsics.sum2code_trans import \ diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py index 05e0e6872a..58137dc523 100644 --- a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py @@ -144,7 +144,7 @@ class Maxval2CodeTrans(MMSBaseTrans): ''' _INTRINSIC_NAME = "MAXVAL" - def _loop_body(self, array_reduction, array_iterators, symbol_var, + def _loop_body(self, array_reduction, array_iterators, var_symbol, array_ref): '''Provide the body of the nested loop that computes the maximum value of an array. @@ -153,12 +153,12 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, provide a maximum over a particular array dimension and False \ if the maximum is for all elements the array. :param array_iterators: a list of datasymbols containing the \ - loop iterators ordered from outermost loop symbol to innermost \ + loop iterators ordered from innermost loop symbol to outermost \ loop symbol. :type array_iterators: \ List[:py:class:`psyclone.psyir.symbols.DataSymbol`] - :param symbol_var: the symbol used to store the final result. - :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param var_symbol: the symbol used to store the final result. + :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` :param array_ref: a reference to the array from which the maximum is being determined. :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` @@ -171,9 +171,9 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, if array_reduction: array_indices = [Reference(iterator) for iterator in array_iterators] - lhs = ArrayReference.create(symbol_var, array_indices) + lhs = ArrayReference.create(var_symbol, array_indices) else: - lhs = Reference(symbol_var) + lhs = Reference(var_symbol) rhs = array_ref assignment = Assignment.create(lhs, rhs) @@ -188,12 +188,12 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, # end if return IfBlock.create(if_condition, [assignment]) - def _init_var(self, symbol_var): + def _init_var(self, var_symbol): '''The initial value for the variable that computes the maximum value of an array. - :param symbol_var: the symbol used to store the final result. - :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param var_symbol: the symbol used to store the final result. + :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` :returns: PSyIR for the value to initialise the variable that \ computes the maximum value. @@ -201,4 +201,4 @@ def _init_var(self, symbol_var): ''' return IntrinsicCall.create( - IntrinsicCall.Intrinsic.TINY, [Reference(symbol_var)]) + IntrinsicCall.Intrinsic.TINY, [Reference(var_symbol)]) diff --git a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py index 2c4d380103..2335c6f6db 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py @@ -144,7 +144,7 @@ class Minval2CodeTrans(MMSBaseTrans): ''' _INTRINSIC_NAME = "MINVAL" - def _loop_body(self, array_reduction, array_iterators, symbol_var, + def _loop_body(self, array_reduction, array_iterators, var_symbol, array_ref): '''Provide the body of the nested loop that computes the minimum value of an array. @@ -157,8 +157,8 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, loop symbol. :type array_iterators: \ List[:py:class:`psyclone.psyir.symbols.DataSymbol`] - :param symbol_var: the symbol used to store the final result. - :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param var_symbol: the symbol used to store the final result. + :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` :param array_ref: a reference to the array from which the minimum is being determined. :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` @@ -171,9 +171,9 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, if array_reduction: array_indices = [Reference(iterator) for iterator in array_iterators] - lhs = ArrayReference.create(symbol_var, array_indices) + lhs = ArrayReference.create(var_symbol, array_indices) else: - lhs = Reference(symbol_var) + lhs = Reference(var_symbol) rhs = array_ref assignment = Assignment.create(lhs, rhs) @@ -188,12 +188,12 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, # end if return IfBlock.create(if_condition, [assignment]) - def _init_var(self, symbol_var): + def _init_var(self, var_symbol): '''The initial value for the variable that computes the minimum value of an array. - :param symbol_var: the symbol used to store the final result. - :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param var_symbol: the symbol used to store the final result. + :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` :returns: PSyIR for the value to initialise the variable that \ computes the minimum value. @@ -201,4 +201,4 @@ def _init_var(self, symbol_var): ''' return IntrinsicCall.create( - IntrinsicCall.Intrinsic.HUGE, [Reference(symbol_var)]) + IntrinsicCall.Intrinsic.HUGE, [Reference(var_symbol)]) diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index 0ced105cc7..a99221f2b0 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -34,10 +34,8 @@ # Author: R. W. Ford, STFC Daresbury Lab # Modified: S. Siso, STFC Daresbury Lab -'''Module providing a transformation from a PSyIR SUM intrinsic to -PSyIR code. This could be useful if the SUM operator is not supported -by the back-end or if the performance in the inline code is better -than the intrinsic. +'''Module providing common functionality to transformation from a +PSyIR SUM, MINVAL or MAXVAL intrinsic to PSyIR code. ''' from abc import ABC, abstractmethod @@ -99,15 +97,15 @@ def validate(self, node, options=None): '''Check that the input node is valid before applying the transformation. - :param node: a Sum intrinsic. + :param node: a Sum, Minval or Maxval intrinsic. :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` :param options: options for the transformation. :type options: Optional[Dict[str, Any]] :raises TransformationError: if the supplied node is not an \ intrinsic. - :raises TransformationError: if the supplied node is not a sum \ - intrinsic. + :raises TransformationError: if the supplied node is not a sum, \ + minval, or maxval intrinsic. :raises TransformationError: if a valid value for the \ dimension argument can't be determined. :raises TransformationError: if the array argument is not an array. @@ -126,8 +124,8 @@ def validate(self, node, options=None): if node.routine.name.upper() != self._INTRINSIC_NAME: raise TransformationError( f"Error in {self.name} transformation. The supplied node " - f"argument is not a {self._INTRINSIC_NAME.lower()} intrinsic, found " - f"'{node.routine.name}'.") + f"argument is not a {self._INTRINSIC_NAME.lower()} " + f"intrinsic, found '{node.routine.name}'.") array_ref, dim_ref, _ = self._get_args(node) if dim_ref and not isinstance(dim_ref, (Literal, Reference)): @@ -175,11 +173,12 @@ def validate(self, node, options=None): # pylint: disable=too-many-branches # pylint: disable=too-many-statements def apply(self, node, options=None): - '''Apply the SUM intrinsic conversion transformation to the specified - node. This node must be a SUM Operation which is converted to - equivalent inline code. + '''Apply the SUM, MINVAL or MAXVAL intrinsic conversion transformation + to the specified node. This node must be one of these + intrinsic operations which is converted to equivalent inline + code. - :param node: a Sum intrinsic. + :param node: a Sum, Minval or Maxval intrinsic. :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` :param options: options for the transformation. :type options: Optional[Dict[str, Any]] @@ -259,17 +258,18 @@ def apply(self, node, options=None): array_ref = node.children[0].detach() - # Create temporary sum variable (sum_var) - symbol_sum_var = symbol_table.new_symbol( - f"{self._INTRINSIC_NAME.lower()}_var", symbol_type=DataSymbol, datatype=datatype) - # Replace operation with a temporary variable (sum_var). + # Create temporary variable based on the name of the intrinsic. + var_symbol = symbol_table.new_symbol( + f"{self._INTRINSIC_NAME.lower()}_var", symbol_type=DataSymbol, + datatype=datatype) + # Replace operation with a temporary variable. if array_reduction: array_indices = [] for idx in range(ndims-1): array_indices.append(":") - reference = ArrayReference.create(symbol_sum_var, array_indices) + reference = ArrayReference.create(var_symbol, array_indices) else: - reference = Reference(symbol_sum_var) + reference = Reference(var_symbol) node.replace_with(reference) # Create the loop iterators @@ -283,8 +283,7 @@ def apply(self, node, options=None): array_iterators.append(loop_iterator) # Initialise the temporary variable. - # new_assignment = self._init_var(symbol_sum_var, scalar_type, array_ref, loop_bounds, loop_iterators, array_iterators, array_reduction, dimension_literal) - rhs = self._init_var(symbol_sum_var) + rhs = self._init_var(var_symbol) lhs = reference.copy() new_assignment = Assignment.create(lhs, rhs) assignment.parent.children.insert(assignment.position, new_assignment) @@ -295,7 +294,7 @@ def apply(self, node, options=None): array_ref = ArrayReference.create(array_ref.symbol, array_indices) statement = self._loop_body( - array_reduction, array_iterators, symbol_sum_var, array_ref) + array_reduction, array_iterators, var_symbol, array_ref) if mask_ref: # A mask argument has been provided @@ -309,15 +308,19 @@ def apply(self, node, options=None): for idx in range(ndims): statement = Loop.create( - loop_iterators[idx].copy(), loop_bounds[idx][0].copy(), loop_bounds[idx][1].copy(), - Literal("1", INTEGER_TYPE), [statement]) + loop_iterators[idx].copy(), loop_bounds[idx][0].copy(), + loop_bounds[idx][1].copy(), Literal("1", INTEGER_TYPE), + [statement]) assignment.parent.children.insert(assignment.position, statement) @abstractmethod - def _loop_body(self, array_reduction, array_iterators, symbol_var, array_ref): - pass + def _loop_body( + self, array_reduction, array_iterators, var_symbol, array_ref): + '''The intrinsic-specific content of the created loop body.''' @abstractmethod - def _init_var(self, symbol_var): - pass + def _init_var(self, var_symbol): + '''The intrinsic-specific initial value for the temporary variable. + + ''' diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index e545088623..4db891c9da 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -36,8 +36,8 @@ '''Module providing a transformation from a PSyIR SUM intrinsic to PSyIR code. This could be useful if the SUM operator is not supported -by the back-end or if the performance in the inline code is better -than the intrinsic. +by the back-end, the required parallelisation approach, or if the +performance in the inline code is better than the intrinsic. ''' from psyclone.psyir.nodes import ( @@ -143,7 +143,7 @@ class Sum2CodeTrans(MMSBaseTrans): ''' _INTRINSIC_NAME = "SUM" - def _loop_body(self, array_reduction, array_iterators, symbol_var, + def _loop_body(self, array_reduction, array_iterators, var_symbol, array_ref): '''Provide the body of the nested loop that computes the sum of an array. @@ -156,8 +156,8 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, loop symbol. :type array_iterators: \ List[:py:class:`psyclone.psyir.symbols.DataSymbol`] - :param symbol_var: the symbol used to store the final result. - :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param var_symbol: the symbol used to store the final result. + :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` :param array_ref: a reference to the array from which the sum is being determined. :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` @@ -170,34 +170,34 @@ def _loop_body(self, array_reduction, array_iterators, symbol_var, # sum_var(i,...) = sum_var(i,...) + array(i,...) array_indices = [Reference(iterator) for iterator in array_iterators] - lhs = ArrayReference.create(symbol_var, array_indices) + lhs = ArrayReference.create(var_symbol, array_indices) array_indices = [Reference(iterator) for iterator in array_iterators] - rhs_child1 = ArrayReference.create(symbol_var, array_indices) + rhs_child1 = ArrayReference.create(var_symbol, array_indices) else: # sum_var = sum_var + array(i,...) - lhs = Reference(symbol_var) - rhs_child1 = Reference(symbol_var) + lhs = Reference(var_symbol) + rhs_child1 = Reference(var_symbol) rhs_child2 = array_ref rhs = BinaryOperation.create(BinaryOperation.Operator.ADD, rhs_child1, rhs_child2) return Assignment.create(lhs, rhs) - def _init_var(self, symbol_var): + def _init_var(self, var_symbol): '''The initial value for the variable that computes the sum of an array. - :param symbol_var: the symbol used to store the final result. - :type symbol_var: :py:class:`psyclone.psyir.symbols.DataSymbol` + :param var_symbol: the symbol used to store the final result. + :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` :returns: PSyIR for the value to initialise the variable that \ computes the sum. :rtype: :py:class:`psyclone.psyir.nodes.Literal` ''' - intrinsic = symbol_var.datatype.intrinsic - precision = symbol_var.datatype.precision + intrinsic = var_symbol.datatype.intrinsic + precision = var_symbol.datatype.precision scalar_type = ScalarType(intrinsic, precision) if intrinsic == ScalarType.Intrinsic.REAL: value_str = "0.0" diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py index 8635704457..568774058c 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py @@ -33,384 +33,143 @@ # ----------------------------------------------------------------------------- # Author: R. W. Ford, STFC Daresbury Laboratory -'''Module containing tests for the sum2code transformation.''' +'''Module containing tests for the maxval2code transformation.''' import pytest -from psyclone.psyir.frontend.fortran import FortranReader -from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.nodes import IntrinsicCall, Reference -from psyclone.psyir.symbols import REAL_TYPE, DataSymbol -from psyclone.psyir.transformations import Maxval2CodeTrans, TransformationError +from psyclone.psyir.nodes import Reference, ArrayReference +from psyclone.psyir.symbols import ( + REAL_TYPE, DataSymbol, INTEGER_TYPE, ArrayType) +from psyclone.psyir.transformations import ( + Maxval2CodeTrans, TransformationError) def test_initialise(): - ''' Test that we can create an instance of the transformation ''' - trans = Maxval2CodeTrans() - assert isinstance(trans, Maxval2CodeTrans) - assert (str(trans) == "Convert the PSyIR MAXVAL intrinsic to equivalent " - "PSyIR code.") - assert trans.name == "Maxval2CodeTrans" - - -def test_validate_node(): - '''Check that an incorrect node raises the expected exception.''' - trans = Maxval2CodeTrans() - with pytest.raises(TransformationError) as info: - trans.validate(None) - assert ("Error in Maxval2CodeTrans transformation. The supplied node " - "argument is not an intrinsic, found 'NoneType'." - in str(info.value)) - - -# apply tests - -@pytest.mark.parametrize("idim1,idim2,rdim11,rdim12,rdim21,rdim22", - [("10", "20", "1", "10", "1", "20"), - ("n", "m", "1", "n", "1", "m"), - ("0:n", "2:m", "0", "n", "2", "m"), - (":", ":", "LBOUND(array, 1)", "UBOUND(array, 1)", - "LBOUND(array, 2)", "UBOUND(array, 2)")]) -def test_apply_maxval(idim1, idim2, rdim11, rdim12, rdim21, rdim22, - fortran_reader, fortran_writer): - '''Test that a sum intrinsic as the only term on the rhs of an - assignment with a single array argument gets transformed as - expected. Test with known and unknown array sizes. + '''Test that we can create an instance of the transformation and that + _INTRINSIC_NAME is set up as expected. ''' - code = ( - f"subroutine maxval_test(array,n,m)\n" - f" integer :: n, m\n" - f" real :: array({idim1},{idim2})\n" - f" real :: result\n" - f" result = maxval(array)\n" - f"end subroutine\n") - expected = ( - f"subroutine maxval_test(array, n, m)\n" - f" integer :: n\n integer :: m\n" - f" real, dimension({idim1},{idim2}) :: array\n" - f" real :: result\n real :: maxval_var\n" - f" integer :: i_0\n integer :: i_1\n\n" - f" maxval_var = TINY(maxval_var)\n" - f" do i_1 = {rdim21}, {rdim22}, 1\n" - f" do i_0 = {rdim11}, {rdim12}, 1\n" - f" if (maxval_var < array(i_0,i_1)) then\n" - f" maxval_var = array(i_0,i_1)\n" - f" end if\n" - f" enddo\n" - f" enddo\n" - f" result = maxval_var\n\n" - f"end subroutine maxval_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] trans = Maxval2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + assert isinstance(trans, Maxval2CodeTrans) + assert trans._INTRINSIC_NAME == "MAXVAL" -@pytest.mark.parametrize("datatype,zero", [("real", "0.0"), ("integer", "0"), - ("real(kind=r_def)", "0.0_r_def")]) -def test_apply_sum_multi(fortran_reader, fortran_writer, datatype, zero): - '''Test that a sum intrinsic as part of multiple term on the rhs of an - assignment with a single array argument gets transformed as - expected. Test with real, integer and with a specified precision. +def test_loop_body(): + '''Test that the _loop_body method works as expected, without an array + reduction. ''' - code = ( - f"subroutine maxval_test(array,n,m,value1,value2)\n" - f" use precision\n" - f" integer :: n, m\n" - f" {datatype} :: array(n,m)\n" - f" real :: value1, value2\n" - f" real :: result\n" - f" result = value1 + maxval(array) * value2\n" - f"end subroutine\n") - expected = ( - f"subroutine maxval_test(array, n, m, value1, value2)\n" - f" use precision\n" - f" integer :: n\n integer :: m\n" - f" {datatype}, dimension(n,m) :: array\n" - f" real :: value1\n real :: value2\n" - f" real :: result\n {datatype} :: maxval_var\n" - f" integer :: i_0\n integer :: i_1\n\n" - f" maxval_var = TINY(maxval_var)\n" - f" do i_1 = 1, m, 1\n" - f" do i_0 = 1, n, 1\n" - f" if (maxval_var < array(i_0,i_1)) then\n" - f" maxval_var = array(i_0,i_1)\n" - f" end if\n" - f" enddo\n" - f" enddo\n" - f" result = value1 + maxval_var * value2\n\n" - f"end subroutine maxval_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Maxval2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected - - -def test_apply_dimension_1d(fortran_reader, fortran_writer): - '''Test that the apply method works as expected when a dimension - argument is specified and the array is one dimensional. This - should be the same as if dimension were not specified at all. + i_iterator = DataSymbol("i", INTEGER_TYPE) + j_iterator = DataSymbol("j", INTEGER_TYPE) + array_iterators = [j_iterator, i_iterator] + var_symbol = DataSymbol("var", REAL_TYPE) + array_symbol = DataSymbol("array", ArrayType(REAL_TYPE, [10, 10])) + array_ref = ArrayReference.create( + array_symbol, [Reference(i_iterator), Reference(j_iterator)]) + result = trans._loop_body(False, array_iterators, var_symbol, array_ref) + assert result.debug_string() == ( + "if (var < array(i,j)) then\n" + " var = array(i,j)\n" + "end if\n") + + +def test_loop_body_reduction(): + '''Test that the _loop_body method works as expected, with an array + reduction. ''' - code = ( - "subroutine maxval_test(array,value1,value2)\n" - " real :: array(:)\n" - " real :: value1, value2\n" - " real :: result\n" - " result = value1 + maxval(array,dim=1) * value2\n" - "end subroutine\n") - expected = ( - "subroutine maxval_test(array, value1, value2)\n" - " real, dimension(:) :: array\n" - " real :: value1\n real :: value2\n" - " real :: result\n real :: maxval_var\n" - " integer :: i_0\n\n" - " maxval_var = TINY(maxval_var)\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" - " if (maxval_var < array(i_0)) then\n" - " maxval_var = array(i_0)\n" - " end if\n" - " enddo\n" - " result = value1 + maxval_var * value2\n\n" - "end subroutine maxval_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Maxval2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + i_iterator = DataSymbol("i", INTEGER_TYPE) + j_iterator = DataSymbol("j", INTEGER_TYPE) + k_iterator = DataSymbol("k", INTEGER_TYPE) + array_iterators = [i_iterator, k_iterator] + var_symbol = DataSymbol("var", ArrayType(REAL_TYPE, [10, 10])) + array_symbol = DataSymbol("array", ArrayType(REAL_TYPE, [10, 10, 10])) + array_ref = ArrayReference.create( + array_symbol, [Reference(i_iterator), Reference(j_iterator), + Reference(k_iterator)]) + result = trans._loop_body(True, array_iterators, var_symbol, array_ref) + assert result.debug_string() == ( + "if (var(i,k) < array(i,j,k)) then\n" + " var(i,k) = array(i,j,k)\n" + "end if\n") + + +def test_init_var(): + '''Test that the _init_var method works as expected.''' + trans = Maxval2CodeTrans() + var_symbol = DataSymbol("var", REAL_TYPE) + result = trans._init_var(var_symbol) + # As 'tiny' is not yet part of an expression, the 'debug_string()' + # method incorrectly assumes it is a call. + assert result.debug_string() == "call TINY(var)\n" -def test_apply_dimension_multid(fortran_reader, fortran_writer): - '''Test that the apply method works as expected when a dimension - argument is specified and the array is multi-dimensional. Only the - specified dimension should be summed. +def test_str(): + '''Test that the str method, implemented in the parent class, works + as expected. ''' - code = ( - "subroutine maxval_test(array,value1,value2,n,m,p)\n" - " integer :: n,m,p\n" - " real :: array(n,m,p)\n" - " real :: value1, value2\n" - " real :: result(n,p)\n" - " result(:,:) = value1 + maxval(array,dim=2) * value2\n" - "end subroutine\n") - expected = ( - "subroutine maxval_test(array, value1, value2, n, m, p)\n" - " integer :: n\n integer :: m\n integer :: p\n" - " real, dimension(n,m,p) :: array\n" - " real :: value1\n real :: value2\n" - " real, dimension(n,p) :: result\n" - " real, dimension(n,p) :: maxval_var\n" - " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " maxval_var(:,:) = TINY(maxval_var)\n" - " do i_2 = 1, p, 1\n" - " do i_1 = 1, m, 1\n" - " do i_0 = 1, n, 1\n" - " if (maxval_var(i_0,i_2) < array(i_0,i_1,i_2)) then\n" - " maxval_var(i_0,i_2) = array(i_0,i_1,i_2)\n" - " end if\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + maxval_var(:,:) * value2\n\n" - "end subroutine maxval_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Maxval2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + assert str(trans) == ("Convert the PSyIR MAXVAL intrinsic to equivalent " + "PSyIR code.") -def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): - '''Test that lbound and ubound are used if the bounds of the array are - not known. +def test_name(): + '''Test that the name method, implemented in the parent class, works + as expected. ''' - code = ( - "subroutine maxval_test(array,value1,value2,result)\n" - " real :: array(:,:,:)\n" - " real :: value1, value2\n" - " real :: result(:,:)\n" - " result(:,:) = value1 + maxval(array,dim=2) * value2\n" - "end subroutine\n") - expected = ( - "subroutine maxval_test(array, value1, value2, result)\n" - " real, dimension(:,:,:) :: array\n" - " real :: value1\n" - " real :: value2\n" - " real, dimension(:,:) :: result\n" - " real, dimension(LBOUND(array, 1):UBOUND(array, 1),LBOUND(array, 3):" - "UBOUND(array, 3)) :: maxval_var\n" - " integer :: i_0\n" - " integer :: i_1\n" - " integer :: i_2\n\n" - " maxval_var(:,:) = TINY(maxval_var)\n" - " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" - " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" - " if (maxval_var(i_0,i_2) < array(i_0,i_1,i_2)) then\n" - " maxval_var(i_0,i_2) = array(i_0,i_1,i_2)\n" - " end if\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + maxval_var(:,:) * value2\n\n" - "end subroutine maxval_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Maxval2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + assert trans.name == "Maxval2CodeTrans" -# specified array range -def test_apply_dimension_multid_range(fortran_reader, fortran_writer): - '''Test that the apply method works as expected when an array range is - specified and the array is multi-dimensional. Only the specified - dimension should be summed. +def test_validate(): + '''Test that the validate method, implemented in the parent class, + works as expected. ''' - code = ( - "subroutine maxval_test(array,value1,value2,n,m,p)\n" - " integer :: n,m,p\n" - " real :: array(:,:,:)\n" - " real :: value1, value2\n" - " real :: result(n,p)\n" - " result(:,:) = value1 + maxval(array(1:n,m-1:m,1:p),dim=2) * " - "value2\n" - "end subroutine\n") - expected = ( - "subroutine maxval_test(array, value1, value2, n, m, p)\n" - " integer :: n\n integer :: m\n integer :: p\n" - " real, dimension(:,:,:) :: array\n" - " real :: value1\n real :: value2\n" - " real, dimension(n,p) :: result\n" - " real, dimension(n,p) :: maxval_var\n" - " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " maxval_var(:,:) = TINY(maxval_var)\n" - " do i_2 = 1, p, 1\n" - " do i_1 = m - 1, m, 1\n" - " do i_0 = 1, n, 1\n" - " if (maxval_var(i_0,i_2) < array(i_0,i_1,i_2)) then\n" - " maxval_var(i_0,i_2) = array(i_0,i_1,i_2)\n" - " end if\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + maxval_var(:,:) * value2\n\n" - "end subroutine maxval_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Maxval2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + with pytest.raises(TransformationError) as info: + trans.validate(None) + assert ("Error in Maxval2CodeTrans transformation. The supplied node " + "argument is not an intrinsic, found 'NoneType'." + in str(info.value)) -def test_mask(): - '''Test that the sum transformation works when there is a mask - specified. +def test_apply(fortran_reader, fortran_writer): + '''Test that the apply method, implemented in the parent class, works + as expected. ''' code = ( - "program maxval_test\n" - " real :: array(10,10)\n" + "subroutine maxval_test(array,n,m)\n" + " integer :: n, m\n" + " real :: array(10,20)\n" " real :: result\n" - " result = maxval(array, mask=MOD(array, 2.0)==1)\n" - "end program\n") + " result = maxval(array)\n" + "end subroutine\n") expected = ( - "program maxval_test\n" - " real, dimension(10,10) :: array\n" - " real :: result\n" - " real :: maxval_var\n" - " integer :: i_0\n" - " integer :: i_1\n\n" + "subroutine maxval_test(array, n, m)\n" + " integer :: n\n integer :: m\n" + " real, dimension(10,20) :: array\n" + " real :: result\n real :: maxval_var\n" + " integer :: i_0\n integer :: i_1\n\n" " maxval_var = TINY(maxval_var)\n" - " do i_1 = 1, 10, 1\n" + " do i_1 = 1, 20, 1\n" " do i_0 = 1, 10, 1\n" - " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" - " if (maxval_var < array(i_0,i_1)) then\n" - " maxval_var = array(i_0,i_1)\n" - " end if\n" + " if (maxval_var < array(i_0,i_1)) then\n" + " maxval_var = array(i_0,i_1)\n" " end if\n" " enddo\n" " enddo\n" " result = maxval_var\n\n" - "end program maxval_test") - reader = FortranReader() - psyir = reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = Maxval2CodeTrans() - trans.apply(sum_node) - writer = FortranWriter() - result = writer(psyir) - assert expected in result - - -def test_mask_dimension(): - '''Test that the sum transformation works when there is a mask and a - dimension specified. - - ''' - code = ( - "program maxval_test\n" - " real :: array(10,10)\n" - " real :: result(10)\n" - " integer, parameter :: dimension=2\n" - " result(:) = maxval(array, dimension, mask=MOD(array, 2.0)==1)\n" - "end program\n") - expected = ( - "program maxval_test\n" - " integer, parameter :: dimension = 2\n" - " real, dimension(10,10) :: array\n" - " real, dimension(10) :: result\n" - " real, dimension(10) :: maxval_var\n" - " integer :: i_0\n" - " integer :: i_1\n\n" - " maxval_var(:) = TINY(maxval_var)\n" - " do i_1 = 1, 10, 1\n" - " do i_0 = 1, 10, 1\n" - " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" - " if (maxval_var(i_0) < array(i_0,i_1)) then\n" - " maxval_var(i_0) = array(i_0,i_1)\n" - " end if\n" - " end if\n" - " enddo\n" - " enddo\n" - " result(:) = maxval_var(:)\n\n" - "end program maxval_test") - reader = FortranReader() - psyir = reader.psyir_from_source(code) + "end subroutine maxval_test\n") + psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] + intrinsic_node = psyir.children[0].children[0].children[1] trans = Maxval2CodeTrans() - trans.apply(sum_node) - writer = FortranWriter() - result = writer(psyir) - assert expected in result + trans.apply(intrinsic_node) + result = fortran_writer(psyir) + assert result == expected diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py new file mode 100644 index 0000000000..617351030a --- /dev/null +++ b/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py @@ -0,0 +1,175 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, 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 Laboratory + +'''Module containing tests for the minval2code transformation.''' + +import pytest + +from psyclone.psyir.nodes import Reference, ArrayReference +from psyclone.psyir.symbols import ( + REAL_TYPE, DataSymbol, INTEGER_TYPE, ArrayType) +from psyclone.psyir.transformations import ( + Minval2CodeTrans, TransformationError) + + +def test_initialise(): + '''Test that we can create an instance of the transformation and that + _INTRINSIC_NAME is set up as expected. + + ''' + trans = Minval2CodeTrans() + assert isinstance(trans, Minval2CodeTrans) + assert trans._INTRINSIC_NAME == "MINVAL" + + +def test_loop_body(): + '''Test that the _loop_body method works as expected, without an array + reduction. + + ''' + trans = Minval2CodeTrans() + i_iterator = DataSymbol("i", INTEGER_TYPE) + j_iterator = DataSymbol("j", INTEGER_TYPE) + array_iterators = [j_iterator, i_iterator] + var_symbol = DataSymbol("var", REAL_TYPE) + array_symbol = DataSymbol("array", ArrayType(REAL_TYPE, [10, 10])) + array_ref = ArrayReference.create( + array_symbol, [Reference(i_iterator), Reference(j_iterator)]) + result = trans._loop_body(False, array_iterators, var_symbol, array_ref) + assert result.debug_string() == ( + "if (var > array(i,j)) then\n" + " var = array(i,j)\n" + "end if\n") + + +def test_loop_body_reduction(): + '''Test that the _loop_body method works as expected, with an array + reduction. + + ''' + trans = Minval2CodeTrans() + i_iterator = DataSymbol("i", INTEGER_TYPE) + j_iterator = DataSymbol("j", INTEGER_TYPE) + k_iterator = DataSymbol("k", INTEGER_TYPE) + array_iterators = [i_iterator, k_iterator] + var_symbol = DataSymbol("var", ArrayType(REAL_TYPE, [10, 10])) + array_symbol = DataSymbol("array", ArrayType(REAL_TYPE, [10, 10, 10])) + array_ref = ArrayReference.create( + array_symbol, [Reference(i_iterator), Reference(j_iterator), + Reference(k_iterator)]) + result = trans._loop_body(True, array_iterators, var_symbol, array_ref) + assert result.debug_string() == ( + "if (var(i,k) > array(i,j,k)) then\n" + " var(i,k) = array(i,j,k)\n" + "end if\n") + + +def test_init_var(): + '''Test that the _init_var method works as expected.''' + trans = Minval2CodeTrans() + var_symbol = DataSymbol("var", REAL_TYPE) + result = trans._init_var(var_symbol) + # As 'huge' is not yet part of an expression, the 'debug_string()' + # method incorrectly assumes it is a call. + assert result.debug_string() == "call HUGE(var)\n" + + +def test_str(): + '''Test that the str method, implemented in the parent class, works + as expected. + + ''' + trans = Minval2CodeTrans() + assert str(trans) == ("Convert the PSyIR MINVAL intrinsic to equivalent " + "PSyIR code.") + + +def test_name(): + '''Test that the name method, implemented in the parent class, works + as expected. + + ''' + trans = Minval2CodeTrans() + assert trans.name == "Minval2CodeTrans" + + +def test_validate(): + '''Test that the validate method, implemented in the parent class, + works as expected. + + ''' + trans = Minval2CodeTrans() + with pytest.raises(TransformationError) as info: + trans.validate(None) + assert ("Error in Minval2CodeTrans transformation. The supplied node " + "argument is not an intrinsic, found 'NoneType'." + in str(info.value)) + + +def test_apply(fortran_reader, fortran_writer): + '''Test that the apply method, implemented in the parent class, works + as expected. + + ''' + code = ( + "subroutine minval_test(array,n,m)\n" + " integer :: n, m\n" + " real :: array(10,20)\n" + " real :: result\n" + " result = minval(array)\n" + "end subroutine\n") + expected = ( + "subroutine minval_test(array, n, m)\n" + " integer :: n\n integer :: m\n" + " real, dimension(10,20) :: array\n" + " real :: result\n real :: minval_var\n" + " integer :: i_0\n integer :: i_1\n\n" + " minval_var = HUGE(minval_var)\n" + " do i_1 = 1, 20, 1\n" + " do i_0 = 1, 10, 1\n" + " if (minval_var > array(i_0,i_1)) then\n" + " minval_var = array(i_0,i_1)\n" + " end if\n" + " enddo\n" + " enddo\n" + " result = minval_var\n\n" + "end subroutine minval_test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + intrinsic_node = psyir.children[0].children[0].children[1] + trans = Minval2CodeTrans() + trans.apply(intrinsic_node) + result = fortran_writer(psyir) + assert result == expected diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py index 08fd0632fb..4124dc040d 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py @@ -40,8 +40,6 @@ ''' import pytest -#from psyclone.psyir.frontend.fortran import FortranReader -#from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import IntrinsicCall, Reference, Literal, Assignment from psyclone.psyir.symbols import ( Symbol, BOOLEAN_TYPE, INTEGER_TYPE, DataSymbol, REAL_TYPE) @@ -53,9 +51,9 @@ class TestTrans(MMSBaseTrans): '''Utility class to allow the abstract MMSBaseTrans to be tested.''' - def _loop_body(self, _1, _2, _3, _4): + def _loop_body(self, _1, _2, _3, array_ref): '''Minimal implementation of the abstract _loop_body method.''' - node = Assignment.create(Literal("99.0", REAL_TYPE), Literal("33.0", REAL_TYPE)) + node = Assignment.create(array_ref, Literal("33.0", REAL_TYPE)) return node def _init_var(self, _): @@ -64,9 +62,10 @@ def _init_var(self, _): return node -class FakeSumTrans(TestTrans): - '''Utility class to allow the abstract MMSBaseTrans to be tested. Acts - as if it modifies the SUM intrinsic to help with testing. +class NamedTestTrans(TestTrans): + '''Utility class to allow the abstract MMSBaseTrans to be tested. Sets + the internal intrinsic_name. Needs to use an existing intrinsic + for the tests to work. ''' _INTRINSIC_NAME = "SUM" @@ -102,18 +101,18 @@ def test_str(): ''' Check that the __str__ method behaves as expected. ''' assert str(TestTrans()) == ("Convert the PSyIR None intrinsic to " "equivalent PSyIR code.") - assert str(FakeSumTrans()) == ("Convert the PSyIR SUM intrinsic to " - "equivalent PSyIR code.") + assert str(NamedTestTrans()) == ("Convert the PSyIR SUM intrinsic to " + "equivalent PSyIR code.") # validate method def test_validate_node(): '''Check that an incorrect node raises the expected exception.''' - trans = FakeSumTrans() + trans = NamedTestTrans() with pytest.raises(TransformationError) as info: trans.validate(None) - assert ("Error in FakeSumTrans transformation. The supplied node " + assert ("Error in NamedTestTrans transformation. The supplied node " "argument is not an intrinsic, found 'NoneType'." in str(info.value)) @@ -132,7 +131,7 @@ def test_structure_error(fortran_reader): ''' code = ( - "subroutine sum_test(n,m)\n" + "subroutine test(n,m)\n" " integer :: n, m\n" " type :: array_type\n" " real :: array(10,10)\n" @@ -143,11 +142,11 @@ def test_structure_error(fortran_reader): " result = sum(ref%array)\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - sum_node = psyir.children[0].children[0].children[1] - trans = FakeSumTrans() + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() with pytest.raises(TransformationError) as info: - trans.validate(sum_node) - assert ("FakeSumTrans only support arrays for the first argument, but " + trans.validate(node) + assert ("NamedTestTrans only support arrays for the first argument, but " "found 'StructureReference'." in str(info.value)) @@ -157,7 +156,7 @@ def test_indexed_array_error(fortran_reader): ''' code = ( - "subroutine sum_test(array,n,m)\n" + "subroutine test(array,n,m)\n" " integer :: n, m\n" " real :: array(10,10)\n" " real :: result\n" @@ -165,11 +164,11 @@ def test_indexed_array_error(fortran_reader): " result = sum(array(1,1))\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - sum_node = psyir.children[0].children[0].children[1] - trans = FakeSumTrans() + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() with pytest.raises(TransformationError) as info: - trans.validate(sum_node) - assert ("FakeSumTrans only supports arrays with array ranges, but " + trans.validate(node) + assert ("NamedTestTrans only supports arrays with array ranges, but " "found a fixed dimension in 'array(1,1)'." in str(info.value)) @@ -179,7 +178,7 @@ def test_dimension_arg(fortran_reader): ''' code = ( - "subroutine sum_test(array,n,m)\n" + "subroutine test(array,n,m)\n" " integer :: n, m\n" " real :: array(10,10)\n" " real :: result\n" @@ -188,10 +187,10 @@ def test_dimension_arg(fortran_reader): "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = FakeSumTrans() + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() with pytest.raises(TransformationError) as info: - trans.validate(sum_node) + trans.validate(node) assert ("Can't find the value of the dimension argument. Expected it to " "be a literal or a reference but found 'dimension * 2' which is " "a 'BinaryOperation'." in str(info.value)) @@ -203,7 +202,7 @@ def test_array_arg(fortran_reader): ''' code = ( - "subroutine sum_test(array,n,m)\n" + "subroutine test(array,n,m)\n" " integer :: n, m\n" " real :: array\n" " real :: result\n" @@ -211,20 +210,20 @@ def test_array_arg(fortran_reader): "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = FakeSumTrans() + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() with pytest.raises(TransformationError) as info: - trans.validate(sum_node) + trans.validate(node) assert "Expected 'array' to be an array." in str(info.value) def test_array_shape(fortran_reader, monkeypatch): - '''Tests that the expected exception is raised if the array shape is + '''Tests that the expected exception is raised if the array range is not a valid value. Requires monkeypatching. ''' code = ( - "subroutine sum_test(array,n,m)\n" + "subroutine test(array,n,m)\n" " integer :: n, m\n" " real :: array(1)\n" " real :: result\n" @@ -232,28 +231,55 @@ def test_array_shape(fortran_reader, monkeypatch): "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] + node = psyir.children[0].children[0].children[1] - # Modify array shape from sum_node to create exception - array_ref = sum_node.children[0] + # Modify array shape from node to create exception + array_ref = node.children[0] array_symbol = array_ref.symbol monkeypatch.setattr(array_symbol._datatype, "_shape", [None]) - trans = FakeSumTrans() + trans = NamedTestTrans() with pytest.raises(TypeError) as info: - trans.validate(sum_node) + trans.validate(node) assert ("ArrayType shape-list elements can only be 'int', " "ArrayType.Extent, 'DataNode' or a 2-tuple thereof but found " "'NoneType'." in str(info.value)) +def test_unexpected_shape(fortran_reader, monkeypatch): + '''Tests that the expected exception is raised if the array shape is + not a valid value. Requires monkeypatching. + + ''' + code = ( + "subroutine test(array,n,m)\n" + " integer :: n, m\n" + " real :: array(:)\n" + " real :: result\n" + " result = sum(array(:))\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + node = psyir.children[0].children[0].children[1] + array_ref = node.children[0] + # Modify the shape of the array reference shape to create an + # exception + monkeypatch.setattr(array_ref.symbol._datatype, "_shape", [1]) + + trans = NamedTestTrans() + with pytest.raises(TransformationError) as info: + trans.validate(node) + assert ("Unexpected shape for array. Expecting one of Deferred, Attribute " + "or Bounds but found '1'." in str(info.value)) + + def test_array_type_arg(fortran_reader): '''Test that the expected exception is raised if the array is an unsupported datatype. ''' code = ( - "subroutine sum_test(array,n,m)\n" + "subroutine test(array,n,m)\n" " integer :: n, m\n" " logical :: array(10)\n" " real :: result\n" @@ -261,10 +287,10 @@ def test_array_type_arg(fortran_reader): "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = FakeSumTrans() + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() with pytest.raises(TransformationError) as info: - trans.validate(sum_node) + trans.validate(node) assert ("Only real and integer types supported for array 'array', " "but found 'BOOLEAN'." in str(info.value)) @@ -277,22 +303,22 @@ def test_array_type_arg(fortran_reader): ("0:n", "2:m", "0", "n", "2", "m"), (":", ":", "LBOUND(array, 1)", "UBOUND(array, 1)", "LBOUND(array, 2)", "UBOUND(array, 2)")]) -def test_apply_sum(idim1, idim2, rdim11, rdim12, rdim21, rdim22, - fortran_reader, fortran_writer): +def test_apply(idim1, idim2, rdim11, rdim12, rdim21, rdim22, + fortran_reader, fortran_writer): '''Test that a sum intrinsic as the only term on the rhs of an assignment with a single array argument gets transformed as expected. Test with known and unknown array sizes. ''' code = ( - f"subroutine sum_test(array,n,m)\n" + f"subroutine test(array,n,m)\n" f" integer :: n, m\n" f" real :: array({idim1},{idim2})\n" f" real :: result\n" f" result = sum(array)\n" f"end subroutine\n") expected = ( - f"subroutine sum_test(array, n, m)\n" + f"subroutine test(array, n, m)\n" f" integer :: n\n integer :: m\n" f" real, dimension({idim1},{idim2}) :: array\n" f" real :: result\n real :: sum_var\n" @@ -300,15 +326,298 @@ def test_apply_sum(idim1, idim2, rdim11, rdim12, rdim21, rdim22, f" sum_var = 99.0\n" f" do i_1 = {rdim21}, {rdim22}, 1\n" f" do i_0 = {rdim11}, {rdim12}, 1\n" - f" 99.0 = 33.0\n" + f" array(i_0,i_1) = 33.0\n" f" enddo\n" f" enddo\n" f" result = sum_var\n\n" - f"end subroutine sum_test\n") + f"end subroutine test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = FakeSumTrans() - trans.apply(sum_node) + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected + + +def test_apply_multi(fortran_reader, fortran_writer): + '''Test that a sum intrinsic as part of multiple term on the rhs of an + assignment with a single array argument gets transformed as + expected. + + ''' + code = ( + "subroutine test(array,n,m,value1,value2)\n" + " use precision\n" + " integer :: n, m\n" + " real :: array(n,m)\n" + " real :: value1, value2\n" + " real :: result\n" + " result = value1 + sum(array) * value2\n" + "end subroutine\n") + expected = ( + "subroutine test(array, n, m, value1, value2)\n" + " use precision\n" + " integer :: n\n integer :: m\n" + " real, dimension(n,m) :: array\n" + " real :: value1\n real :: value2\n" + " real :: result\n real :: sum_var\n" + " integer :: i_0\n integer :: i_1\n\n" + " sum_var = 99.0\n" + " do i_1 = 1, m, 1\n" + " do i_0 = 1, n, 1\n" + " array(i_0,i_1) = 33.0\n" + " enddo\n" + " enddo\n" + " result = value1 + sum_var * value2\n\n" + "end subroutine test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = NamedTestTrans() + trans.apply(node) result = fortran_writer(psyir) assert result == expected + + +def test_apply_dimension_1d(fortran_reader, fortran_writer): + '''Test that the apply method works as expected when a dimension + argument is specified and the array is one dimensional. This + should be the same as if dimension were not specified at all. + + ''' + code = ( + "subroutine test(array,value1,value2)\n" + " real :: array(:)\n" + " real :: value1, value2\n" + " real :: result\n" + " result = value1 + sum(array,dim=1) * value2\n" + "end subroutine\n") + expected = ( + "subroutine test(array, value1, value2)\n" + " real, dimension(:) :: array\n" + " real :: value1\n real :: value2\n" + " real :: result\n real :: sum_var\n" + " integer :: i_0\n\n" + " sum_var = 99.0\n" + " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" + " array(i_0) = 33.0\n" + " enddo\n" + " result = value1 + sum_var * value2\n\n" + "end subroutine test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = NamedTestTrans() + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected + + +def test_apply_dimension_multid(fortran_reader, fortran_writer): + '''Test that the apply method works as expected when a dimension + argument is specified and the array is multi-dimensional. + + ''' + code = ( + "subroutine test(array,value1,value2,n,m,p)\n" + " integer :: n,m,p\n" + " real :: array(n,m,p)\n" + " real :: value1, value2\n" + " real :: result(n,p)\n" + " result(:,:) = value1 + sum(array,dim=2) * value2\n" + "end subroutine\n") + expected = ( + "subroutine test(array, value1, value2, n, m, p)\n" + " integer :: n\n integer :: m\n integer :: p\n" + " real, dimension(n,m,p) :: array\n" + " real :: value1\n real :: value2\n" + " real, dimension(n,p) :: result\n" + " real, dimension(n,p) :: sum_var\n" + " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" + " sum_var(:,:) = 99.0\n" + " do i_2 = 1, p, 1\n" + " do i_1 = 1, m, 1\n" + " do i_0 = 1, n, 1\n" + " array(i_0,i_1,i_2) = 33.0\n" + " enddo\n" + " enddo\n" + " enddo\n" + " result(:,:) = value1 + sum_var(:,:) * value2\n\n" + "end subroutine test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = NamedTestTrans() + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected + + +def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): + '''Test that lbound and ubound are used if the bounds of the array are + not known. + + ''' + code = ( + "subroutine test(array,value1,value2,result)\n" + " real :: array(:,:,:)\n" + " real :: value1, value2\n" + " real :: result(:,:)\n" + " result(:,:) = value1 + sum(array,dim=2) * value2\n" + "end subroutine\n") + expected = ( + "subroutine test(array, value1, value2, result)\n" + " real, dimension(:,:,:) :: array\n" + " real :: value1\n" + " real :: value2\n" + " real, dimension(:,:) :: result\n" + " real, dimension(LBOUND(array, 1):UBOUND(array, 1),LBOUND(array, 3):" + "UBOUND(array, 3)) :: sum_var\n" + " integer :: i_0\n" + " integer :: i_1\n" + " integer :: i_2\n\n" + " sum_var(:,:) = 99.0\n" + " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" + " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" + " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" + " array(i_0,i_1,i_2) = 33.0\n" + " enddo\n" + " enddo\n" + " enddo\n" + " result(:,:) = value1 + sum_var(:,:) * value2\n\n" + "end subroutine test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = NamedTestTrans() + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected + + +# specified array range +def test_apply_dimension_multid_range(fortran_reader, fortran_writer): + '''Test that the apply method works as expected when an array range is + specified and the array is multi-dimensional. + + ''' + code = ( + "subroutine test(array,value1,value2,n,m,p)\n" + " integer :: n,m,p\n" + " real :: array(:,:,:)\n" + " real :: value1, value2\n" + " real :: result(n,p)\n" + " result(:,:) = value1 + sum(array(1:n,m-1:m,1:p),dim=2) * " + "value2\n" + "end subroutine\n") + expected = ( + "subroutine test(array, value1, value2, n, m, p)\n" + " integer :: n\n integer :: m\n integer :: p\n" + " real, dimension(:,:,:) :: array\n" + " real :: value1\n real :: value2\n" + " real, dimension(n,p) :: result\n" + " real, dimension(n,p) :: sum_var\n" + " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" + " sum_var(:,:) = 99.0\n" + " do i_2 = 1, p, 1\n" + " do i_1 = m - 1, m, 1\n" + " do i_0 = 1, n, 1\n" + " array(i_0,i_1,i_2) = 33.0\n" + " enddo\n" + " enddo\n" + " enddo\n" + " result(:,:) = value1 + sum_var(:,:) * value2\n\n" + "end subroutine test\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ + # BinaryOperation(MUL)/UnaryOperation + node = psyir.children[0].children[0].children[1].children[1]. \ + children[0] + trans = NamedTestTrans() + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected + + +def test_mask(fortran_reader, fortran_writer): + '''Test that the transformation works when there is a mask specified. + + ''' + code = ( + "program test\n" + " real :: array(10,10)\n" + " real :: result\n" + " result = sum(array, mask=MOD(array, 2.0)==1)\n" + "end program\n") + expected = ( + "program test\n" + " real, dimension(10,10) :: array\n" + " real :: result\n" + " real :: sum_var\n" + " integer :: i_0\n" + " integer :: i_1\n\n" + " sum_var = 99.0\n" + " do i_1 = 1, 10, 1\n" + " do i_0 = 1, 10, 1\n" + " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" + " array(i_0,i_1) = 33.0\n" + " end if\n" + " enddo\n" + " enddo\n" + " result = sum_var\n\n" + "end program test") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() + trans.apply(node) + result = fortran_writer(psyir) + assert expected in result + + +def test_mask_dimension(fortran_reader, fortran_writer): + '''Test that the transformation works when there is a mask and a + dimension specified. + + ''' + code = ( + "program test\n" + " real :: array(10,10)\n" + " real :: result(10)\n" + " integer, parameter :: dimension=2\n" + " result = sum(array, dimension, mask=MOD(array, 2.0)==1)\n" + "end program\n") + expected = ( + "program test\n" + " integer, parameter :: dimension = 2\n" + " real, dimension(10,10) :: array\n" + " real, dimension(10) :: result\n" + " real, dimension(10) :: sum_var\n" + " integer :: i_0\n" + " integer :: i_1\n\n" + " sum_var(:) = 99.0\n" + " do i_1 = 1, 10, 1\n" + " do i_0 = 1, 10, 1\n" + " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" + " array(i_0,i_1) = 33.0\n" + " end if\n" + " enddo\n" + " enddo\n" + " result = sum_var(:)\n\n" + "end program test") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Assignment/UnaryOperation + node = psyir.children[0].children[0].children[1] + trans = NamedTestTrans() + trans.apply(node) + result = fortran_writer(psyir) + assert expected in result diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py index cd468caf0b..de0f87585a 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py @@ -37,365 +37,137 @@ import pytest -from psyclone.psyir.frontend.fortran import FortranReader -from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.nodes import IntrinsicCall, Reference -from psyclone.psyir.symbols import REAL_TYPE, DataSymbol +from psyclone.psyir.nodes import Reference, ArrayReference +from psyclone.psyir.symbols import ( + REAL_TYPE, DataSymbol, INTEGER_TYPE, ArrayType, ScalarType) from psyclone.psyir.transformations import Sum2CodeTrans, TransformationError def test_initialise(): - ''' Test that we can create an instance of the transformation ''' - trans = Sum2CodeTrans() - assert isinstance(trans, Sum2CodeTrans) - assert (str(trans) == "Convert the PSyIR SUM intrinsic to equivalent " - "PSyIR code.") - assert trans.name == "Sum2CodeTrans" + '''Test that we can create an instance of the transformation and that + _INTRINSIC_NAME is set up as expected. - -# Check validate is called -def test_validate_node(): - '''Check that an incorrect node raises the expected exception.''' + ''' trans = Sum2CodeTrans() - with pytest.raises(TransformationError) as info: - trans.validate(None) - assert ("Error in Sum2CodeTrans transformation. The supplied node " - "argument is not an intrinsic, found 'NoneType'." - in str(info.value)) - + assert isinstance(trans, Sum2CodeTrans) + assert trans._INTRINSIC_NAME == "SUM" -# apply tests -@pytest.mark.parametrize("idim1,idim2,rdim11,rdim12,rdim21,rdim22", - [("10", "20", "1", "10", "1", "20"), - ("n", "m", "1", "n", "1", "m"), - ("0:n", "2:m", "0", "n", "2", "m"), - (":", ":", "LBOUND(array, 1)", "UBOUND(array, 1)", - "LBOUND(array, 2)", "UBOUND(array, 2)")]) -def test_apply_sum(idim1, idim2, rdim11, rdim12, rdim21, rdim22, - fortran_reader, fortran_writer): - '''Test that a sum intrinsic as the only term on the rhs of an - assignment with a single array argument gets transformed as - expected. Test with known and unknown array sizes. +def test_loop_body(): + '''Test that the _loop_body method works as expected, without an array + reduction. ''' - code = ( - f"subroutine sum_test(array,n,m)\n" - f" integer :: n, m\n" - f" real :: array({idim1},{idim2})\n" - f" real :: result\n" - f" result = sum(array)\n" - f"end subroutine\n") - expected = ( - f"subroutine sum_test(array, n, m)\n" - f" integer :: n\n integer :: m\n" - f" real, dimension({idim1},{idim2}) :: array\n" - f" real :: result\n real :: sum_var\n" - f" integer :: i_0\n integer :: i_1\n\n" - f" sum_var = 0.0\n" - f" do i_1 = {rdim21}, {rdim22}, 1\n" - f" do i_0 = {rdim11}, {rdim12}, 1\n" - f" sum_var = sum_var + array(i_0,i_1)\n" - f" enddo\n" - f" enddo\n" - f" result = sum_var\n\n" - f"end subroutine sum_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] trans = Sum2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + i_iterator = DataSymbol("i", INTEGER_TYPE) + j_iterator = DataSymbol("j", INTEGER_TYPE) + array_iterators = [j_iterator, i_iterator] + var_symbol = DataSymbol("var", REAL_TYPE) + array_symbol = DataSymbol("array", ArrayType(REAL_TYPE, [10, 10])) + array_ref = ArrayReference.create( + array_symbol, [Reference(i_iterator), Reference(j_iterator)]) + result = trans._loop_body(False, array_iterators, var_symbol, array_ref) + assert result.debug_string() == "var = var + array(i,j)\n" -@pytest.mark.parametrize("datatype,zero", [("real", "0.0"), ("integer", "0"), - ("real(kind=r_def)", "0.0_r_def")]) -def test_apply_sum_multi(fortran_reader, fortran_writer, datatype, zero): - '''Test that a sum intrinsic as part of multiple term on the rhs of an - assignment with a single array argument gets transformed as - expected. Test with real, integer and with a specified precision. +def test_loop_body_reduction(): + '''Test that the _loop_body method works as expected, with an array + reduction. ''' - code = ( - f"subroutine sum_test(array,n,m,value1,value2)\n" - f" use precision\n" - f" integer :: n, m\n" - f" {datatype} :: array(n,m)\n" - f" real :: value1, value2\n" - f" real :: result\n" - f" result = value1 + sum(array) * value2\n" - f"end subroutine\n") - expected = ( - f"subroutine sum_test(array, n, m, value1, value2)\n" - f" use precision\n" - f" integer :: n\n integer :: m\n" - f" {datatype}, dimension(n,m) :: array\n" - f" real :: value1\n real :: value2\n" - f" real :: result\n {datatype} :: sum_var\n" - f" integer :: i_0\n integer :: i_1\n\n" - f" sum_var = {zero}\n" - f" do i_1 = 1, m, 1\n" - f" do i_0 = 1, n, 1\n" - f" sum_var = sum_var + array(i_0,i_1)\n" - f" enddo\n" - f" enddo\n" - f" result = value1 + sum_var * value2\n\n" - f"end subroutine sum_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Sum2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected - - -def test_apply_dimension_1d(fortran_reader, fortran_writer): - '''Test that the apply method works as expected when a dimension - argument is specified and the array is one dimensional. This - should be the same as if dimension were not specified at all. + i_iterator = DataSymbol("i", INTEGER_TYPE) + j_iterator = DataSymbol("j", INTEGER_TYPE) + k_iterator = DataSymbol("k", INTEGER_TYPE) + array_iterators = [i_iterator, k_iterator] + var_symbol = DataSymbol("var", ArrayType(REAL_TYPE, [10, 10])) + array_symbol = DataSymbol("array", ArrayType(REAL_TYPE, [10, 10, 10])) + array_ref = ArrayReference.create( + array_symbol, [Reference(i_iterator), Reference(j_iterator), + Reference(k_iterator)]) + result = trans._loop_body(True, array_iterators, var_symbol, array_ref) + assert result.debug_string() == "var(i,k) = var(i,k) + array(i,j,k)\n" + + +@pytest.mark.parametrize("name,precision,zero", [ + (ScalarType.Intrinsic.REAL, ScalarType.Precision.UNDEFINED, "0.0"), + (ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED, "0"), + (ScalarType.Intrinsic.REAL, DataSymbol("r_def", INTEGER_TYPE), + "0.0_r_def")]) +def test_init_var(name, precision, zero): + '''Test that the _init_var method works as expected. Test with real, + integer and with a specified precision. ''' - code = ( - "subroutine sum_test(array,value1,value2)\n" - " real :: array(:)\n" - " real :: value1, value2\n" - " real :: result\n" - " result = value1 + sum(array,dim=1) * value2\n" - "end subroutine\n") - expected = ( - "subroutine sum_test(array, value1, value2)\n" - " real, dimension(:) :: array\n" - " real :: value1\n real :: value2\n" - " real :: result\n real :: sum_var\n" - " integer :: i_0\n\n" - " sum_var = 0.0\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" - " sum_var = sum_var + array(i_0)\n" - " enddo\n" - " result = value1 + sum_var * value2\n\n" - "end subroutine sum_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Sum2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + datatype = ScalarType(name, precision) + var_symbol = DataSymbol("var", datatype) + result = trans._init_var(var_symbol) + assert result.debug_string() == zero -def test_apply_dimension_multid(fortran_reader, fortran_writer): - '''Test that the apply method works as expected when a dimension - argument is specified and the array is multi-dimensional. Only the - specified dimension should be summed. +def test_str(): + '''Test that the str method, implemented in the parent class, works + as expected. ''' - code = ( - "subroutine sum_test(array,value1,value2,n,m,p)\n" - " integer :: n,m,p\n" - " real :: array(n,m,p)\n" - " real :: value1, value2\n" - " real :: result(n,p)\n" - " result(:,:) = value1 + sum(array,dim=2) * value2\n" - "end subroutine\n") - expected = ( - "subroutine sum_test(array, value1, value2, n, m, p)\n" - " integer :: n\n integer :: m\n integer :: p\n" - " real, dimension(n,m,p) :: array\n" - " real :: value1\n real :: value2\n" - " real, dimension(n,p) :: result\n" - " real, dimension(n,p) :: sum_var\n" - " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " sum_var(:,:) = 0.0\n" - " do i_2 = 1, p, 1\n" - " do i_1 = 1, m, 1\n" - " do i_0 = 1, n, 1\n" - " sum_var(i_0,i_2) = sum_var(i_0,i_2) + array(i_0,i_1,i_2)\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + sum_var(:,:) * value2\n\n" - "end subroutine sum_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Sum2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + assert str(trans) == ("Convert the PSyIR SUM intrinsic to equivalent " + "PSyIR code.") -def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): - '''Test that lbound and ubound are used if the bounds of the array are - not known. +def test_name(): + '''Test that the name method, implemented in the parent class, works + as expected. ''' - code = ( - "subroutine sum_test(array,value1,value2,result)\n" - " real :: array(:,:,:)\n" - " real :: value1, value2\n" - " real :: result(:,:)\n" - " result(:,:) = value1 + sum(array,dim=2) * value2\n" - "end subroutine\n") - expected = ( - "subroutine sum_test(array, value1, value2, result)\n" - " real, dimension(:,:,:) :: array\n" - " real :: value1\n" - " real :: value2\n" - " real, dimension(:,:) :: result\n" - " real, dimension(LBOUND(array, 1):UBOUND(array, 1),LBOUND(array, 3):" - "UBOUND(array, 3)) :: sum_var\n" - " integer :: i_0\n" - " integer :: i_1\n" - " integer :: i_2\n\n" - " sum_var(:,:) = 0.0\n" - " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" - " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" - " sum_var(i_0,i_2) = sum_var(i_0,i_2) + array(i_0,i_1,i_2)\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + sum_var(:,:) * value2\n\n" - "end subroutine sum_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Sum2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + assert trans.name == "Sum2CodeTrans" -# specified array range -def test_apply_dimension_multid_range(fortran_reader, fortran_writer): - '''Test that the apply method works as expected when an array range is - specified and the array is multi-dimensional. Only the specified - dimension should be summed. +def test_validate(): + '''Test that the validate method, implemented in the parent class, + works as expected. ''' - code = ( - "subroutine sum_test(array,value1,value2,n,m,p)\n" - " integer :: n,m,p\n" - " real :: array(:,:,:)\n" - " real :: value1, value2\n" - " real :: result(n,p)\n" - " result(:,:) = value1 + sum(array(1:n,m-1:m,1:p),dim=2) * " - "value2\n" - "end subroutine\n") - expected = ( - "subroutine sum_test(array, value1, value2, n, m, p)\n" - " integer :: n\n integer :: m\n integer :: p\n" - " real, dimension(:,:,:) :: array\n" - " real :: value1\n real :: value2\n" - " real, dimension(n,p) :: result\n" - " real, dimension(n,p) :: sum_var\n" - " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " sum_var(:,:) = 0.0\n" - " do i_2 = 1, p, 1\n" - " do i_1 = m - 1, m, 1\n" - " do i_0 = 1, n, 1\n" - " sum_var(i_0,i_2) = sum_var(i_0,i_2) + array(i_0,i_1,i_2)\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + sum_var(:,:) * value2\n\n" - "end subroutine sum_test\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation - sum_node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] trans = Sum2CodeTrans() - trans.apply(sum_node) - result = fortran_writer(psyir) - assert result == expected + with pytest.raises(TransformationError) as info: + trans.validate(None) + assert ("Error in Sum2CodeTrans transformation. The supplied node " + "argument is not an intrinsic, found 'NoneType'." + in str(info.value)) -def test_mask(): - '''Test that the sum transformation works when there is a mask - specified. +def test_apply(fortran_reader, fortran_writer): + '''Test that the apply method, implemented in the parent class, works + as expected. ''' code = ( - "program sum_test\n" - " real :: array(10,10)\n" + "subroutine sum_test(array,n,m)\n" + " integer :: n, m\n" + " real :: array(10,20)\n" " real :: result\n" - " result = sum(array, mask=MOD(array, 2.0)==1)\n" - "end program\n") + " result = sum(array)\n" + "end subroutine\n") expected = ( - "program sum_test\n" - " real, dimension(10,10) :: array\n" - " real :: result\n" - " real :: sum_var\n" - " integer :: i_0\n" - " integer :: i_1\n\n" + "subroutine sum_test(array, n, m)\n" + " integer :: n\n integer :: m\n" + " real, dimension(10,20) :: array\n" + " real :: result\n real :: sum_var\n" + " integer :: i_0\n integer :: i_1\n\n" " sum_var = 0.0\n" - " do i_1 = 1, 10, 1\n" + " do i_1 = 1, 20, 1\n" " do i_0 = 1, 10, 1\n" - " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" - " sum_var = sum_var + array(i_0,i_1)\n" - " end if\n" + " sum_var = sum_var + array(i_0,i_1)\n" " enddo\n" " enddo\n" " result = sum_var\n\n" - "end program sum_test") - reader = FortranReader() - psyir = reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] - trans = Sum2CodeTrans() - trans.apply(sum_node) - writer = FortranWriter() - result = writer(psyir) - assert expected in result - - -def test_mask_dimension(): - '''Test that the sum transformation works when there is a mask and a - dimension specified. - - ''' - code = ( - "program sum_test\n" - " real :: array(10,10)\n" - " real :: result(10)\n" - " integer, parameter :: dimension=2\n" - " result = sum(array, dimension, mask=MOD(array, 2.0)==1)\n" - "end program\n") - expected = ( - "program sum_test\n" - " integer, parameter :: dimension = 2\n" - " real, dimension(10,10) :: array\n" - " real, dimension(10) :: result\n" - " real, dimension(10) :: sum_var\n" - " integer :: i_0\n" - " integer :: i_1\n\n" - " sum_var(:) = 0.0\n" - " do i_1 = 1, 10, 1\n" - " do i_0 = 1, 10, 1\n" - " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" - " sum_var(i_0) = sum_var(i_0) + array(i_0,i_1)\n" - " end if\n" - " enddo\n" - " enddo\n" - " result = sum_var(:)\n\n" - "end program sum_test") - reader = FortranReader() - psyir = reader.psyir_from_source(code) + "end subroutine sum_test\n") + psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/UnaryOperation - sum_node = psyir.children[0].children[0].children[1] + intrinsic_node = psyir.children[0].children[0].children[1] trans = Sum2CodeTrans() - trans.apply(sum_node) - writer = FortranWriter() - result = writer(psyir) - assert expected in result + trans.apply(intrinsic_node) + result = fortran_writer(psyir) + assert result == expected From 4a8e34d67a14b0a2a4e874921425c247e45d27c7 Mon Sep 17 00:00:00 2001 From: rupertford Date: Sun, 11 Jun 2023 20:52:08 +0100 Subject: [PATCH 05/11] pr #2148. Updated documentation. --- doc/user_guide/transformations.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/user_guide/transformations.rst b/doc/user_guide/transformations.rst index 234a59319d..25d6d5809b 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -1,7 +1,7 @@ .. ----------------------------------------------------------------------------- .. BSD 3-Clause License .. -.. Copyright (c) 2017-2022, Science and Technology Facilities Council +.. Copyright (c) 2017-2023, Science and Technology Facilities Council .. All rights reserved. .. .. Redistribution and use in source and binary forms, with or without @@ -282,6 +282,12 @@ can be found in the API-specific sections). #### +.. autoclass:: psyclone.psyir.transformations.Maxval2CodeTrans + :members: apply + :noindex: + +#### + .. autoclass:: psyclone.psyir.transformations.Min2CodeTrans :members: apply :noindex: @@ -293,6 +299,12 @@ can be found in the API-specific sections). #### +.. autoclass:: psyclone.psyir.transformations.Minval2CodeTrans + :members: apply + :noindex: + +#### + .. _sec_move_trans: .. autoclass:: psyclone.transformations.MoveTrans From be61410f8233e4d0f42c5836b3e2a3c500168a7e Mon Sep 17 00:00:00 2001 From: rupertford Date: Mon, 7 Aug 2023 11:13:57 +0100 Subject: [PATCH 06/11] pr #2148. Started to address reviewer's comments. --- .../intrinsics/maxval2code_trans.py | 40 +++++++++---------- .../intrinsics/minval2code_trans.py | 40 +++++++++---------- .../intrinsics/mms_base_trans.py | 30 +++++++------- .../intrinsics/sum2code_trans.py | 30 +++++++------- 4 files changed, 71 insertions(+), 69 deletions(-) diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py index 58137dc523..0b998a8623 100644 --- a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py @@ -47,7 +47,7 @@ class Maxval2CodeTrans(MMSBaseTrans): - '''Provides a transformation from a PSyIR MAXVAL Operator node to + '''Provides a transformation from a PSyIR MAXVAL IntrinsicCall node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -55,56 +55,56 @@ class Maxval2CodeTrans(MMSBaseTrans): the maximum value of all of the elements in the array is returned in the the scalar R. - .. code-block:: python + .. code-block:: fortran R = MAXVAL(ARRAY) For example, if the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R = TINY(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - IF R < ARRAY(I,J) THEN + IF (R < ARRAY(I,J)) THEN R = ARRAY(I,J) If the dimension argument is provided then the maximum value is returned along the row for each entry in that dimension: - .. code-block:: python + .. code-block:: fortran R = MAXVAL(ARRAY, dimension=2) If the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R(:) = TINY(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - IF R(I) < ARRAY(I,J) THEN + IF (R(I) < ARRAY(I,J)) THEN R(I) = ARRAY(I,J) If the mask argument is provided then the mask is used to determine whether the maxval is applied: - .. code-block:: python + .. code-block:: fortran R = MAXVAL(ARRAY, mask=MOD(ARRAY, 2.0)==1) If the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R = TINY(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - IF MOD(ARRAY(I,J), 2.0)==1 THEN - IF R < ARRAY(I,J) THEN + IF (MOD(ARRAY(I,J), 2.0)==1) THEN + IF (R < ARRAY(I,J)) THEN R = ARRAY(I,J) For example: @@ -112,7 +112,7 @@ class Maxval2CodeTrans(MMSBaseTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.transformations import Maxval2CodeTrans - >>> code = ("subroutine maxval_test(array,n,m)\\n" + >>> code = ("subroutine maxval_test(array)\\n" ... " real :: array(10,10)\\n" ... " real :: result\\n" ... " result = maxval(array)\\n" @@ -121,7 +121,7 @@ class Maxval2CodeTrans(MMSBaseTrans): >>> sum_node = psyir.children[0].children[0].children[1] >>> Maxval2CodeTrans().apply(sum_node) >>> print(FortranWriter()(psyir)) - subroutine maxval_test(array, n, m) + subroutine maxval_test(array) real, dimension(10,10) :: array real :: result real :: maxval_var @@ -149,13 +149,13 @@ def _loop_body(self, array_reduction, array_iterators, var_symbol, '''Provide the body of the nested loop that computes the maximum value of an array. - :param bool array_reduction: True if the implementation should \ - provide a maximum over a particular array dimension and False \ - if the maximum is for all elements the array. - :param array_iterators: a list of datasymbols containing the \ - loop iterators ordered from innermost loop symbol to outermost \ + :param bool array_reduction: True if the implementation should + provide a maximum over a particular array dimension and False + if the maximum is for all elements of the array. + :param array_iterators: a list of datasymbols containing the + loop iterators ordered from innermost loop symbol to outermost loop symbol. - :type array_iterators: \ + :type array_iterators: List[:py:class:`psyclone.psyir.symbols.DataSymbol`] :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` @@ -195,7 +195,7 @@ def _init_var(self, var_symbol): :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` - :returns: PSyIR for the value to initialise the variable that \ + :returns: PSyIR for the value to initialise the variable that computes the maximum value. :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` diff --git a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py index 2335c6f6db..70daebd22c 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py @@ -47,7 +47,7 @@ class Minval2CodeTrans(MMSBaseTrans): - '''Provides a transformation from a PSyIR MINVAL Operator node to + '''Provides a transformation from a PSyIR MINVAL IntrinsicCall node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -55,56 +55,56 @@ class Minval2CodeTrans(MMSBaseTrans): the minimum value of all of the elements in the array is returned in the the scalar R. - .. code-block:: python + .. code-block:: fortran R = MINVAL(ARRAY) For example, if the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R = HUGE(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - IF R > ARRAY(I,J) THEN + IF (R > ARRAY(I,J)) THEN R = ARRAY(I,J) If the dimension argument is provided then the minimum value is returned along the row for each entry in that dimension: - .. code-block:: python + .. code-block:: fortran R = MINVAL(ARRAY, dimension=2) If the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R(:) = HUGE(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - IF R(I) > ARRAY(I,J) THEN + IF (R(I) > ARRAY(I,J)) THEN R(I) = ARRAY(I,J) If the mask argument is provided then the mask is used to determine whether the minval is applied: - .. code-block:: python + .. code-block:: fortran R = MINVAL(ARRAY, mask=MOD(ARRAY, 2.0)==1) If the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R = HUGE(R) DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - IF MOD(ARRAY(I,J), 2.0)==1 THEN - IF R > ARRAY(I,J) THEN + IF (MOD(ARRAY(I,J), 2.0)==1) THEN + IF (R > ARRAY(I,J)) THEN R = ARRAY(I,J) For example: @@ -112,7 +112,7 @@ class Minval2CodeTrans(MMSBaseTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.transformations import Minval2CodeTrans - >>> code = ("subroutine minval_test(array,n,m)\\n" + >>> code = ("subroutine minval_test(array)\\n" ... " real :: array(10,10)\\n" ... " real :: result\\n" ... " result = minval(array)\\n" @@ -121,7 +121,7 @@ class Minval2CodeTrans(MMSBaseTrans): >>> sum_node = psyir.children[0].children[0].children[1] >>> Minval2CodeTrans().apply(sum_node) >>> print(FortranWriter()(psyir)) - subroutine minval_test(array, n, m) + subroutine minval_test(array) real, dimension(10,10) :: array real :: result real :: minval_var @@ -149,13 +149,13 @@ def _loop_body(self, array_reduction, array_iterators, var_symbol, '''Provide the body of the nested loop that computes the minimum value of an array. - :param bool array_reduction: True if the implementation should \ - provide a minimum over a particular array dimension and False \ - if the minimum is for all elements the array. - :param array_iterators: a list of datasymbols containing the \ - loop iterators ordered from outermost loop symbol to innermost \ + :param bool array_reduction: True if the implementation should + provide a minimum over a particular array dimension and False + if the minimum is for all elements of the array. + :param array_iterators: a list of datasymbols containing the + loop iterators ordered from outermost loop symbol to innermost loop symbol. - :type array_iterators: \ + :type array_iterators: List[:py:class:`psyclone.psyir.symbols.DataSymbol`] :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` @@ -195,7 +195,7 @@ def _init_var(self, var_symbol): :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` - :returns: PSyIR for the value to initialise the variable that \ + :returns: PSyIR for the value to initialise the variable that computes the minimum value. :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall` diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index a99221f2b0..b2c554d630 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -53,7 +53,7 @@ class MMSBaseTrans(Transformation, ABC): '''An abstract parent class providing common functionality to the sum2code_trans, minval2code_trans and maxval2_code trans - tranformations. + transformations. ''' _INTRINSIC_NAME = None @@ -67,10 +67,10 @@ def _get_args(node): :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` returns: a tuple containing the 3 arguments. - rtype: Tuple[py:class:`psyclone.psyir.nodes.reference.Reference`, \ - py:class:`psyclone.psyir.nodes.Literal` | \ - :py:class:`psyclone.psyir.nodes.Reference`, \ - Optional[:py:class:`psyclone.psyir.nodes.node`]] + rtype: Tuple[py:class:`psyclone.psyir.nodes.reference.Reference`, + py:class:`psyclone.psyir.nodes.Literal` | + :py:class:`psyclone.psyir.nodes.Reference`, + Optional[:py:class:`psyclone.psyir.nodes.Node`]] ''' # Determine the arguments to the intrinsic @@ -84,10 +84,12 @@ def _get_args(node): # named arg name = node.argument_names[idx].lower() args[arg_names_map[name]] = child - array_ref = args[0] - dimension_ref = args[1] - mask_ref = args[2] - return (array_ref, dimension_ref, mask_ref) + # RF TODO WORKS????? + return tuple(args) + #array_ref = args[0] + #dimension_ref = args[1] + #mask_ref = args[2] + #return (array_ref, dimension_ref, mask_ref) def __str__(self): return (f"Convert the PSyIR {self._INTRINSIC_NAME} intrinsic " @@ -102,16 +104,16 @@ def validate(self, node, options=None): :param options: options for the transformation. :type options: Optional[Dict[str, Any]] - :raises TransformationError: if the supplied node is not an \ + :raises TransformationError: if the supplied node is not an intrinsic. - :raises TransformationError: if the supplied node is not a sum, \ + :raises TransformationError: if the supplied node is not a sum, minval, or maxval intrinsic. - :raises TransformationError: if a valid value for the \ + :raises TransformationError: if a valid value for the dimension argument can't be determined. :raises TransformationError: if the array argument is not an array. - :raises TransformationError: if the shape of the array is not \ + :raises TransformationError: if the shape of the array is not supported. - :raises TransformationError: if the array datatype is not \ + :raises TransformationError: if the array datatype is not supported. ''' diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index 4db891c9da..c90d981d54 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -48,7 +48,7 @@ class Sum2CodeTrans(MMSBaseTrans): - '''Provides a transformation from a PSyIR SUM Operator node to + '''Provides a transformation from a PSyIR SUM IntrinsicCall node to equivalent code in a PSyIR tree. Validity checks are also performed. @@ -56,14 +56,14 @@ class Sum2CodeTrans(MMSBaseTrans): all element on that array are summed and the result returned in the scalar R. - .. code-block:: python + .. code-block:: fortran R = SUM(ARRAY) For example, if the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R = 0.0 DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) @@ -73,14 +73,14 @@ class Sum2CodeTrans(MMSBaseTrans): If the dimension argument is provided then only that dimension is summed: - .. code-block:: python + .. code-block:: fortran R = SUM(ARRAY, dimension=2) If the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R(:) = 0.0 DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) @@ -90,19 +90,19 @@ class Sum2CodeTrans(MMSBaseTrans): If the mask argument is provided then the mask is used to determine whether the sum is applied: - .. code-block:: python + .. code-block:: fortran R = SUM(ARRAY, mask=MOD(ARRAY, 2.0)==1) If the array is two dimensional, the equivalent code for real data is: - .. code-block:: python + .. code-block:: fortran R = 0.0 DO J=LBOUND(ARRAY,2),UBOUND(ARRAY,2) DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) - if (MOD(ARRAY(I,J), 2.0)==1): + IF (MOD(ARRAY(I,J), 2.0)==1) THEN R = R + ARRAY(I,J) For example: @@ -148,13 +148,13 @@ def _loop_body(self, array_reduction, array_iterators, var_symbol, '''Provide the body of the nested loop that computes the sum of an array. - :param bool array_reduction: True if the implementation should \ - provide a sum over a particular array dimension and False \ - if the sum is for all elements the array. - :param array_iterators: a list of datasymbols containing the \ - loop iterators ordered from outermost loop symbol to innermost \ + :param bool array_reduction: True if the implementation should + provide a sum over a particular array dimension and False + if the sum is for all elements of the array. + :param array_iterators: a list of datasymbols containing the + loop iterators ordered from outermost loop symbol to innermost loop symbol. - :type array_iterators: \ + :type array_iterators: List[:py:class:`psyclone.psyir.symbols.DataSymbol`] :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` @@ -191,7 +191,7 @@ def _init_var(self, var_symbol): :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` - :returns: PSyIR for the value to initialise the variable that \ + :returns: PSyIR for the value to initialise the variable that computes the sum. :rtype: :py:class:`psyclone.psyir.nodes.Literal` From b01444b4aa9b15c6caad41d9ad6bb0ff52498cf5 Mon Sep 17 00:00:00 2001 From: rupertford Date: Fri, 8 Sep 2023 00:45:47 +0100 Subject: [PATCH 07/11] pr #2148. Fixed broken code and tests after merge with master. --- .../intrinsics/mms_base_trans.py | 2 +- .../intrinsics/sum2code_trans.py | 225 ------------------ 2 files changed, 1 insertion(+), 226 deletions(-) diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index b2c554d630..ab11e5941c 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -199,7 +199,7 @@ def apply(self, node, options=None): dimension_literal = dimension_ref elif (isinstance(dimension_ref, Reference) and dimension_ref.symbol.is_constant): - dimension_literal = dimension_ref.symbol.constant_value + dimension_literal = dimension_ref.symbol.initial_value # else exception is handled by the validate method. # Determine the dimension and extent of the array diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index ba1c644c22..c90d981d54 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -141,233 +141,8 @@ class Sum2CodeTrans(MMSBaseTrans): ''' - _INTRINSIC_NAME = "SUM" - @staticmethod - def _get_args(node): - '''Utility method that returns the sum arguments, (array reference, - dimension and mask). - - :param node: a Sum intrinsic. - :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - - returns: a tuple containing the 3 sum arguments. - rtype: Tuple[py:class:`psyclone.psyir.nodes.reference.Reference`, \ - py:class:`psyclone.psyir.nodes.Literal` | \ - :py:class:`psyclone.psyir.nodes.Reference`, \ - Optional[:py:class:`psyclone.psyir.nodes.node`]] - - ''' - # Determine the arguments to sum - args = [None, None, None] - arg_names_map = {"array": 0, "dim": 1, "mask": 2} - for idx, child in enumerate(node.children): - if not node.argument_names[idx]: - # positional arg - args[idx] = child - else: - # named arg - name = node.argument_names[idx].lower() - args[arg_names_map[name]] = child - array_ref = args[0] - dimension_ref = args[1] - mask_ref = args[2] - return (array_ref, dimension_ref, mask_ref) - - def __str__(self): - return "Convert the PSyIR SUM intrinsic to equivalent PSyIR code." - - def validate(self, node, options=None): - '''Check that the input node is valid before applying the - transformation. - - :param node: a Sum intrinsic. - :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - :param options: options for the transformation. - :type options: Optional[Dict[str, Any]] - - :raises TransformationError: if the supplied node is not an \ - intrinsic. - :raises TransformationError: if the supplied node is not a sum \ - intrinsic. - :raises TransformationError: if a valid value for the \ - dimension argument can't be determined. - :raises TransformationError: if the array argument is not an array. - :raises TransformationError: if the shape of the array is not \ - supported. - :raises TransformationError: if the array datatype is not \ - supported. - - ''' - if not isinstance(node, IntrinsicCall): - raise TransformationError( - f"Error in {self.name} transformation. The supplied node " - f"argument is not an intrinsic, found " - f"'{type(node).__name__}'.") - - if node.routine.name.lower() != "sum": - raise TransformationError( - f"Error in {self.name} transformation. The supplied node " - f"argument is not a sum intrinsic, found " - f"'{node.routine.name}'.") - - array_ref, dim_ref, _ = self._get_args(node) - if dim_ref and not isinstance(dim_ref, (Literal, Reference)): - raise TransformationError( - f"Can't find the value of the dimension argument. Expected " - f"it to be a literal or a reference but found " - f"'{dim_ref.debug_string()}' which is a " - f"'{type(dim_ref).__name__}'.") - - # pylint: disable=unidiomatic-typecheck - if not (isinstance(array_ref, ArrayReference) or - type(array_ref) is Reference): - raise TransformationError( - f"Sum2CodeTrans only support arrays for the first argument, " - f"but found '{type(array_ref).__name__}'.") - - if len(array_ref.children) == 0: - if not array_ref.symbol.is_array: - raise TransformationError( - f"Expected '{array_ref.name}' to be an array.") - - for shape in array_ref.children: - if not isinstance(shape, Range): - raise TransformationError( - f"Sum2CodeTrans only supports arrays with array ranges, " - f"but found a fixed dimension in " - f"'{array_ref.debug_string()}'.") - - try: - _ = array_ref.symbol.shape - except TypeError as err: - raise TransformationError( - f"Unexpected shape for array '{array_ref.symbol.name}': " - f"{err}") from err - - array_intrinsic = array_ref.symbol.datatype.intrinsic - if array_intrinsic not in [ScalarType.Intrinsic.REAL, - ScalarType.Intrinsic.INTEGER]: - raise TransformationError( - f"Only real and integer types supported for array " - f"'{array_ref.name}', but found '{array_intrinsic.name}'.") - - # pylint: disable=too-many-locals - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - def apply(self, node, options=None): - '''Apply the SUM intrinsic conversion transformation to the specified - node. This node must be a SUM Operation which is converted to - equivalent inline code. - - :param node: a Sum intrinsic. - :type node: :py:class:`psyclone.psyir.nodes.IntrinsicCall` - :param options: options for the transformation. - :type options: Optional[Dict[str, Any]] - - ''' - self.validate(node) - - array_ref, dimension_ref, mask_ref = self._get_args(node) - - # Determine the literal value of the dimension argument - dimension_literal = None - if not dimension_ref: - # there is no dimension argument - pass - elif isinstance(dimension_ref, Literal): - dimension_literal = dimension_ref - elif (isinstance(dimension_ref, Reference) and - dimension_ref.symbol.is_constant): - dimension_literal = dimension_ref.symbol.initial_value - # else exception is handled by the validate method. - - # Determine the dimension and extent of the array - ndims = None - if len(array_ref.children) == 0: - # Note, the potential 'if not array_ref.symbol.is_array:' - # exception is already handled by the validate method. - ndims = len(array_ref.symbol.shape) - - loop_bounds = [] - for idx, shape in enumerate(array_ref.symbol.shape): - if shape in [ArrayType.Extent.DEFERRED, - ArrayType.Extent.ATTRIBUTE]: - # runtime extent using LBOUND and UBOUND required - lbound = BinaryOperation.create( - BinaryOperation.Operator.LBOUND, - Reference(array_ref.symbol), - Literal(str(idx+1), INTEGER_TYPE)) - ubound = BinaryOperation.create( - BinaryOperation.Operator.UBOUND, - Reference(array_ref.symbol), - Literal(str(idx+1), INTEGER_TYPE)) - loop_bounds.append((lbound, ubound)) - elif isinstance(shape, ArrayType.ArrayBounds): - # array extent is defined in the array declaration - loop_bounds.append(shape) - # Note, the validate method guarantees that an else - # clause is not required. - else: - # The validate method guarantees that this is an array - # reference. - loop_bounds = [] - ndims = len(array_ref.children) - for shape in array_ref.children: - loop_bounds.append((shape.start.copy(), shape.stop.copy())) - - # Determine the datatype of the array's values and create a - # scalar of that type - array_intrinsic = array_ref.symbol.datatype.intrinsic - array_precision = array_ref.symbol.datatype.precision - scalar_type = ScalarType(array_intrinsic, array_precision) - - symbol_table = node.scope.symbol_table - assignment = node.ancestor(Assignment) - - datatype = scalar_type - array_reduction = False - if dimension_ref and ndims > 1: - array_reduction = True - # We are reducing from one array to another - shape = [] - for idx, bounds in enumerate(loop_bounds): - if int(dimension_literal.value)-1 == idx: - pass - else: - shape.append(bounds) - datatype = ArrayType(scalar_type, shape) - - # Create temporary sum variable (sum_var) - symbol_sum_var = symbol_table.new_symbol( - "sum_var", symbol_type=DataSymbol, datatype=datatype) - - # Replace operation with a temporary variable (sum_var). - node.replace_with(Reference(symbol_sum_var)) - - # sum_var=0.0 or 0 - lhs = Reference(symbol_sum_var) - if scalar_type.intrinsic == scalar_type.Intrinsic.REAL: - rhs = Literal("0.0", scalar_type) - elif scalar_type.intrinsic == scalar_type.Intrinsic.INTEGER: - rhs = Literal("0", scalar_type) - # Note, the validate method guarantees that an else branch is - # not required. - - new_assignment = Assignment.create(lhs, rhs) - assignment.parent.children.insert(assignment.position, new_assignment) - - # Create the loop iterators - loop_iterators = [] - array_iterators = [] - for idx in range(ndims): - loop_iterator = symbol_table.new_symbol( - f"i_{idx}", symbol_type=DataSymbol, datatype=INTEGER_TYPE) - loop_iterators.append(loop_iterator) - if array_reduction and idx != int(dimension_literal.value)-1: - array_iterators.append(loop_iterator) - def _loop_body(self, array_reduction, array_iterators, var_symbol, array_ref): '''Provide the body of the nested loop that computes the sum of an From 13eb05076ee9e318859258ce7851768bac95e527 Mon Sep 17 00:00:00 2001 From: rupertford Date: Sun, 10 Sep 2023 23:51:32 +0100 Subject: [PATCH 08/11] pr #2148. Hopefully addressed the reviewer's comments. --- .../intrinsics/maxval2code_trans.py | 16 ++ .../intrinsics/minval2code_trans.py | 16 ++ .../intrinsics/mms_base_trans.py | 102 +++++--- .../intrinsics/sum2code_trans.py | 16 ++ .../intrinsics/mms_base_trans_test.py | 221 ++++++++++++++++-- 5 files changed, 320 insertions(+), 51 deletions(-) diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py index 0b998a8623..42407d25f1 100644 --- a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py @@ -88,6 +88,10 @@ class Maxval2CodeTrans(MMSBaseTrans): IF (R(I) < ARRAY(I,J)) THEN R(I) = ARRAY(I,J) + A restriction is that the value of dimension must be able to be + determined by PSyclone, either being a literal or a reference to + something with a known value. + If the mask argument is provided then the mask is used to determine whether the maxval is applied: @@ -107,6 +111,18 @@ class Maxval2CodeTrans(MMSBaseTrans): IF (R < ARRAY(I,J)) THEN R = ARRAY(I,J) + The array passed to MAXVAL may use array syntax, array notation or + array sections (or a mixture of the two), but scalar bounds are + not allowed: + + .. code-block:: fortran + + R = MAXVAL(ARRAY) ! array syntax + R = MAXVAL(ARRAY(:,:)) ! array notation + R = MAXVAL(ARRAY(1:10,lo:hi) ! array sections + R = MAXVAL(ARRAY(1:10,:) ! mixture of array sections and array notation + R = MAXVAL(ARRAY(1:10,2) ! NOT SUPPORTED as 2 is a scalar bound + For example: >>> from psyclone.psyir.backend.fortran import FortranWriter diff --git a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py index 70daebd22c..c8210b88cc 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py @@ -88,6 +88,10 @@ class Minval2CodeTrans(MMSBaseTrans): IF (R(I) > ARRAY(I,J)) THEN R(I) = ARRAY(I,J) + A restriction is that the value of dimension must be able to be + determined by PSyclone, either being a literal or a reference to + something with a known value. + If the mask argument is provided then the mask is used to determine whether the minval is applied: @@ -107,6 +111,18 @@ class Minval2CodeTrans(MMSBaseTrans): IF (R > ARRAY(I,J)) THEN R = ARRAY(I,J) + The array passed to MINVAL may use array syntax, array notation or + array sections (or a mixture of the two), but scalar bounds are + not allowed: + + .. code-block:: fortran + + R = MINVAL(ARRAY) ! array syntax + R = MINVAL(ARRAY(:,:)) ! array notation + R = MINVAL(ARRAY(1:10,lo:hi) ! array sections + R = MINVAL(ARRAY(1:10,:) ! mixture of array sections and array notation + R = MINVAL(ARRAY(1:10,2) ! NOT SUPPORTED as 2 is a scalar bound + For example: >>> from psyclone.psyir.backend.fortran import FortranWriter diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index ab11e5941c..fa628dadc2 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -84,17 +84,13 @@ def _get_args(node): # named arg name = node.argument_names[idx].lower() args[arg_names_map[name]] = child - # RF TODO WORKS????? return tuple(args) - #array_ref = args[0] - #dimension_ref = args[1] - #mask_ref = args[2] - #return (array_ref, dimension_ref, mask_ref) def __str__(self): return (f"Convert the PSyIR {self._INTRINSIC_NAME} intrinsic " "to equivalent PSyIR code.") + # pylint: disable=too-many-branches def validate(self, node, options=None): '''Check that the input node is valid before applying the transformation. @@ -130,24 +126,43 @@ def validate(self, node, options=None): f"intrinsic, found '{node.routine.name}'.") array_ref, dim_ref, _ = self._get_args(node) - if dim_ref and not isinstance(dim_ref, (Literal, Reference)): + + # If there is a dim argument then PSyclone curently needs to + # be able to determine the literal value. + # pylint: disable=unidiomatic-typecheck + if dim_ref and not ( + isinstance(dim_ref, Literal) + or (type(dim_ref) == Reference and + dim_ref.symbol.is_constant and + isinstance(dim_ref.symbol.initial_value, Literal))): + if isinstance(dim_ref, Reference): + info = f"a reference to a '{type(dim_ref.symbol).__name__}'" + else: + info = f"type '{type(dim_ref).__name__}'" raise TransformationError( f"Can't find the value of the dimension argument. Expected " - f"it to be a literal or a reference but found " - f"'{dim_ref.debug_string()}' which is a " - f"'{type(dim_ref).__name__}'.") + f"it to be a literal or a reference to a known constant " + f"value, but found '{dim_ref.debug_string()}' which is " + f"{info}.") # pylint: disable=unidiomatic-typecheck if not (isinstance(array_ref, ArrayReference) or - type(array_ref) == Reference): + (type(array_ref) == Reference)): raise TransformationError( - f"{self.name} only support arrays for the first argument, " - f"but found '{type(array_ref).__name__}'.") + f"{self.name} only supports arrays or plain references for " + f"the first argument, but found '{type(array_ref).__name__}'.") if len(array_ref.children) == 0: if not array_ref.symbol.is_array: raise TransformationError( f"Expected '{array_ref.name}' to be an array.") + for shape in array_ref.symbol.shape: + if not (shape in [ + ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE] + or isinstance(shape, ArrayType.ArrayBounds)): + raise TransformationError( + f"Unexpected shape for array. Expecting one of " + f"Deferred, Attribute or Bounds but found '{shape}'.") for shape in array_ref.children: if not isinstance(shape, Range): @@ -156,14 +171,6 @@ def validate(self, node, options=None): f"but found a fixed dimension in " f"'{array_ref.debug_string()}'.") - for shape in array_ref.symbol.shape: - if not (shape in [ - ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE] - or isinstance(shape, ArrayType.ArrayBounds)): - raise TransformationError( - f"Unexpected shape for array. Expecting one of Deferred, " - f"Attribute or Bounds but found '{shape}'.") - array_intrinsic = array_ref.symbol.datatype.intrinsic if array_intrinsic not in [ScalarType.Intrinsic.REAL, ScalarType.Intrinsic.INTEGER]: @@ -192,27 +199,33 @@ def apply(self, node, options=None): # Determine the literal value of the dimension argument dimension_literal = None + # pylint: disable=unidiomatic-typecheck if not dimension_ref: # there is no dimension argument pass elif isinstance(dimension_ref, Literal): dimension_literal = dimension_ref - elif (isinstance(dimension_ref, Reference) and + elif ((type(dimension_ref) == Reference) and dimension_ref.symbol.is_constant): dimension_literal = dimension_ref.symbol.initial_value # else exception is handled by the validate method. # Determine the dimension and extent of the array ndims = None + allocatable = False if len(array_ref.children) == 0: + # There is no bounds information in the array reference, + # so look at the declaration. # Note, the potential 'if not array_ref.symbol.is_array:' # exception is already handled by the validate method. - ndims = len(array_ref.symbol.shape) + ndims = len(array_ref.datatype.shape) loop_bounds = [] - for idx, shape in enumerate(array_ref.symbol.shape): + for idx, shape in enumerate(array_ref.datatype.shape): if shape in [ArrayType.Extent.DEFERRED, ArrayType.Extent.ATTRIBUTE]: + if shape == ArrayType.Extent.DEFERRED: + allocatable = True # runtime extent using LBOUND and UBOUND required lbound = BinaryOperation.create( BinaryOperation.Operator.LBOUND, @@ -238,8 +251,8 @@ def apply(self, node, options=None): # Determine the datatype of the array's values and create a # scalar of that type - array_intrinsic = array_ref.symbol.datatype.intrinsic - array_precision = array_ref.symbol.datatype.precision + array_intrinsic = array_ref.datatype.intrinsic + array_precision = array_ref.datatype.precision scalar_type = ScalarType(array_intrinsic, array_precision) symbol_table = node.scope.symbol_table @@ -252,12 +265,26 @@ def apply(self, node, options=None): # We are reducing from one array to another shape = [] for idx, bounds in enumerate(loop_bounds): + # The validation constrains the transformation to only + # allow cases where the literal value for dimension + # is known. if int(dimension_literal.value)-1 == idx: + # This is the dimension we are performing the + # reduction over so do not loop over it. pass else: shape.append(bounds) - datatype = ArrayType(scalar_type, shape) + if allocatable: + # Reduction and allocatable means we need to make the + # reduction array allocatable. We keep the bounds in + # datatype_keep for allocating the array. + datatype = ArrayType( + scalar_type, len(shape)*[ArrayType.Extent.DEFERRED]) + else: + datatype = ArrayType(scalar_type, shape) + # Detach the intrinsics array-reference argument to the + # intrinsic as it will be used later within a loop nest. array_ref = node.children[0].detach() # Create temporary variable based on the name of the intrinsic. @@ -266,14 +293,25 @@ def apply(self, node, options=None): datatype=datatype) # Replace operation with a temporary variable. if array_reduction: - array_indices = [] - for idx in range(ndims-1): - array_indices.append(":") + # This is a reduction so the number of array dimensions is + # reduced by 1 cf. the original array. + array_indices = (ndims - 1)*[":"] reference = ArrayReference.create(var_symbol, array_indices) else: reference = Reference(var_symbol) node.replace_with(reference) + if allocatable and array_reduction: + range_list = [ + Range.create(lbound, ubound) for (lbound, ubound) in shape] + # Allocate the reduction and place it just before it is + # initialised. + allocate = IntrinsicCall.create( + IntrinsicCall.Intrinsic.ALLOCATE, + [ArrayReference.create(var_symbol, range_list)]) + assignment = reference.parent + assignment.parent.children.insert(assignment.position, allocate) + # Create the loop iterators loop_iterators = [] array_iterators = [] @@ -298,11 +336,13 @@ def apply(self, node, options=None): statement = self._loop_body( array_reduction, array_iterators, var_symbol, array_ref) + # pylint: disable=unidiomatic-typecheck if mask_ref: # A mask argument has been provided for ref in mask_ref.walk(Reference): - if ref.name == array_ref.name: - # The array needs indexing + if ref.name == array_ref.name and type(ref) == Reference: + # The array is not indexed so it needs indexing + # for the loop nest. shape = [Reference(obj) for obj in loop_iterators] reference = ArrayReference.create(ref.symbol, shape) ref.replace_with(reference) diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index c90d981d54..9c743c7080 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -87,6 +87,10 @@ class Sum2CodeTrans(MMSBaseTrans): DO I=LBOUND(ARRAY,1),UBOUND(ARRAY,1) R(I) = R(I) + ARRAY(I,J) + A restriction is that the value of dimension must be able to be + determined by PSyclone, either being a literal or a reference to + something with a known value. + If the mask argument is provided then the mask is used to determine whether the sum is applied: @@ -105,6 +109,18 @@ class Sum2CodeTrans(MMSBaseTrans): IF (MOD(ARRAY(I,J), 2.0)==1) THEN R = R + ARRAY(I,J) + The array passed to SUM may use array syntax, array notation or + array sections (or a mixture of the two), but scalar bounds are + not allowed: + + .. code-block:: fortran + + R = SUM(ARRAY) ! array syntax + R = SUM(ARRAY(:,:)) ! array notation + R = SUM(ARRAY(1:10,lo:hi) ! array sections + R = SUM(ARRAY(1:10,:) ! mixture of array sections and array notation + R = SUM(ARRAY(1:10,2) ! NOT SUPPORTED as 2 is a scalar bound + For example: >>> from psyclone.psyir.backend.fortran import FortranWriter diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py index 4124dc040d..5131d83b34 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py @@ -43,7 +43,7 @@ from psyclone.psyir.nodes import IntrinsicCall, Reference, Literal, Assignment from psyclone.psyir.symbols import ( Symbol, BOOLEAN_TYPE, INTEGER_TYPE, DataSymbol, REAL_TYPE) -from psyclone.psyir.transformations import TransformationError +from psyclone.psyir.transformations import TransformationError, Sum2CodeTrans from psyclone.psyir.transformations.intrinsics.mms_base_trans import ( MMSBaseTrans) @@ -73,6 +73,7 @@ class NamedTestTrans(TestTrans): def test_init_exception(): '''Check that this class can't be created as it is abstract.''' + # pylint: disable=abstract-class-instantiated with pytest.raises(TypeError) as info: _ = MMSBaseTrans() assert ("Can't instantiate abstract class MMSBaseTrans with abstract " @@ -146,8 +147,9 @@ def test_structure_error(fortran_reader): trans = NamedTestTrans() with pytest.raises(TransformationError) as info: trans.validate(node) - assert ("NamedTestTrans only support arrays for the first argument, but " - "found 'StructureReference'." in str(info.value)) + assert ("NamedTestTrans only supports arrays or plain references for " + "the first argument, but found 'StructureReference'." + in str(info.value)) def test_indexed_array_error(fortran_reader): @@ -186,14 +188,103 @@ def test_dimension_arg(fortran_reader): " result = sum(array, dim=dimension*2)\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] trans = NamedTestTrans() with pytest.raises(TransformationError) as info: trans.validate(node) assert ("Can't find the value of the dimension argument. Expected it to " - "be a literal or a reference but found 'dimension * 2' which is " - "a 'BinaryOperation'." in str(info.value)) + "be a literal or a reference to a known constant value, but " + "found 'dimension * 2' which is type 'BinaryOperation'." + in str(info.value)) + + +def test_non_constant_dim_value_binop(fortran_reader): + '''Test that the expected exception is raised if the literal value of + the dim arg can not be determined (as it is a binary + operation). Use the Sum2CodeTrans transformations (a subclass of + MMSBaseTrans), as it makes it easier to raise this exception. + + ''' + code = ( + "subroutine test(array,a,b)\n" + " integer :: a,b\n" + " real :: array(10)\n" + " real :: result\n" + " result = sum(array, dim=a+b)\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + trans = Sum2CodeTrans() + # FileContainer/Routine/Assignment/IntrinsicCall + node = psyir.children[0].children[0].children[1] + with pytest.raises(TransformationError) as info: + trans.validate(node) + assert ("Can't find the value of the dimension argument. Expected it to " + "be a literal or a reference to a known constant value, but " + "found 'a + b' which is type 'BinaryOperation'." + in str(info.value)) + + +def test_non_constant_dim_value_ref(fortran_reader): + '''Test that the expected exception is raised if the literal value of + the dim arg can not be determined (as it references an unknown + value). Use the Sum2CodeTrans transformations (a subclass of + MMSBaseTrans), as it makes it easier to raise this exception. + + ''' + code = ( + "subroutine test(array)\n" + " use my_mod, only : y\n" + " integer :: x=y\n" + " real :: array(10)\n" + " real :: result\n" + " result = sum(array, dim=x)\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + trans = Sum2CodeTrans() + # FileContainer/Routine/Assignment/IntrinsicCall + node = psyir.children[0].children[0].children[1] + with pytest.raises(TransformationError) as info: + trans.validate(node) + assert ("Can't find the value of the dimension argument. Expected it to " + "be a literal or a reference to a known constant value, but " + "found 'x' which is a reference to a 'DataSymbol'." + in str(info.value)) + + +def test_lhs(fortran_reader, fortran_writer): + '''Test that the correct code is produced when the minval, maxval or + sum is on the LHS of an asssignment. Use the value). Use the + Sum2CodeTrans transformations (a subclass of MMSBaseTrans), as it + is easier to test. + + ''' + code = ( + "subroutine test(array)\n" + " real :: array(10)\n" + " real :: result\n" + " result(sum(array)) = 0.0\n" + "end subroutine\n") + expected = ( + "subroutine test(array)\n" + " real, dimension(10) :: array\n" + " real :: result\n" + " real :: sum_var\n" + " integer :: i_0\n\n" + " sum_var = 0.0\n" + " do i_0 = 1, 10, 1\n" + " sum_var = sum_var + array(i_0)\n" + " enddo\n" + " result(sum_var) = 0.0\n\n" + "end subroutine test\n") + + psyir = fortran_reader.psyir_from_source(code) + trans = Sum2CodeTrans() + # FileContainer/Routine/Assignment/IntrinsicCall + node = psyir.children[0].children[0].children[0].children[0] + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected def test_array_arg(fortran_reader): @@ -209,7 +300,7 @@ def test_array_arg(fortran_reader): " result = sum(array)\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] trans = NamedTestTrans() with pytest.raises(TransformationError) as info: @@ -230,7 +321,7 @@ def test_array_shape(fortran_reader, monkeypatch): " result = sum(array)\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] # Modify array shape from node to create exception @@ -254,12 +345,12 @@ def test_unexpected_shape(fortran_reader, monkeypatch): code = ( "subroutine test(array,n,m)\n" " integer :: n, m\n" - " real :: array(:)\n" + " real :: array(1)\n" " real :: result\n" - " result = sum(array(:))\n" + " result = sum(array)\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] array_ref = node.children[0] # Modify the shape of the array reference shape to create an @@ -286,7 +377,7 @@ def test_array_type_arg(fortran_reader): " result = sum(array)\n" "end subroutine\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] trans = NamedTestTrans() with pytest.raises(TransformationError) as info: @@ -332,7 +423,7 @@ def test_apply(idim1, idim2, rdim11, rdim12, rdim21, rdim22, f" result = sum_var\n\n" f"end subroutine test\n") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] trans = NamedTestTrans() trans.apply(node) @@ -373,7 +464,7 @@ def test_apply_multi(fortran_reader, fortran_writer): "end subroutine test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation + # BinaryOperation(MUL)/IntrinsicCall node = psyir.children[0].children[0].children[1].children[1]. \ children[0] trans = NamedTestTrans() @@ -409,7 +500,7 @@ def test_apply_dimension_1d(fortran_reader, fortran_writer): "end subroutine test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation + # BinaryOperation(MUL)/IntrinsicCall node = psyir.children[0].children[0].children[1].children[1]. \ children[0] trans = NamedTestTrans() @@ -451,7 +542,7 @@ def test_apply_dimension_multid(fortran_reader, fortran_writer): "end subroutine test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation + # BinaryOperation(MUL)/IntrinsicCall node = psyir.children[0].children[0].children[1].children[1]. \ children[0] trans = NamedTestTrans() @@ -495,7 +586,7 @@ def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): "end subroutine test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation + # BinaryOperation(MUL)/IntrinsicCall node = psyir.children[0].children[0].children[1].children[1]. \ children[0] trans = NamedTestTrans() @@ -539,7 +630,7 @@ def test_apply_dimension_multid_range(fortran_reader, fortran_writer): "end subroutine test\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ - # BinaryOperation(MUL)/UnaryOperation + # BinaryOperation(MUL)/IntrinsicCall node = psyir.children[0].children[0].children[1].children[1]. \ children[0] trans = NamedTestTrans() @@ -576,7 +667,7 @@ def test_mask(fortran_reader, fortran_writer): " result = sum_var\n\n" "end program test") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] trans = NamedTestTrans() trans.apply(node) @@ -615,9 +706,99 @@ def test_mask_dimension(fortran_reader, fortran_writer): " result = sum_var(:)\n\n" "end program test") psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/UnaryOperation + # FileContainer/Routine/Assignment/IntrinsicCall node = psyir.children[0].children[0].children[1] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) assert expected in result + + +def test_mask_array_indexed(fortran_reader, fortran_writer): + '''Test that the mask code works if the array iself it used as part of + the mask. In this case it will already be indexed. Use the + Sum2CodeTrans transformations (a subclass of MMSBaseTrans), as it + is easier to test. + + ''' + code = ( + "program sum_test\n" + " integer :: a(4)\n" + " integer :: result\n" + " a(1) = 2\n" + " a(2) = 1\n" + " a(3) = 2\n" + " a(4) = 1\n" + " result = sum(a, mask=a(1)>a)\n" + "end program\n") + expected = ( + "program sum_test\n" + " integer, dimension(4) :: a\n" + " integer :: result\n" + " integer :: sum_var\n" + " integer :: i_0\n\n" + " a(1) = 2\n" + " a(2) = 1\n" + " a(3) = 2\n" + " a(4) = 1\n" + " sum_var = 0\n" + " do i_0 = 1, 4, 1\n" + " if (a(1) > a(i_0)) then\n" + " sum_var = sum_var + a(i_0)\n" + " end if\n" + " enddo\n" + " result = sum_var\n\n" + "end program sum_test\n") + psyir = fortran_reader.psyir_from_source(code) + trans = Sum2CodeTrans() + # FileContainer/Routine/Assignment/IntrinsicCall + node = psyir.children[0].children[4].children[1] + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected + + +def test_allocate_dim(fortran_reader, fortran_writer): + '''Test that a newly created array is allocated after the original + array is allocated (if the original array is allocated). Use the + Sum2CodeTrans transformations (a subclass of MMSBaseTrans), as it + is easier to test. + + ''' + code = ( + "program sum_test\n" + " integer, allocatable :: a(:,:,:)\n" + " integer :: result\n" + " allocate(a(4,4,4))\n" + " result = sum(a, dim=2)\n" + " deallocate(a)\n" + "end program\n") + expected = ( + "program sum_test\n" + " integer, allocatable, dimension(:,:,:) :: a\n" + " integer :: result\n" + " integer, allocatable, dimension(:,:) :: sum_var\n" + " integer :: i_0\n" + " integer :: i_1\n" + " integer :: i_2\n\n" + " ALLOCATE(a(1:4,1:4,1:4))\n" + " ALLOCATE(sum_var(LBOUND(a, 1):UBOUND(a, 1)," + "LBOUND(a, 3):UBOUND(a, 3)))\n" + " sum_var(:,:) = 0\n" + " do i_2 = LBOUND(a, 3), UBOUND(a, 3), 1\n" + " do i_1 = LBOUND(a, 2), UBOUND(a, 2), 1\n" + " do i_0 = LBOUND(a, 1), UBOUND(a, 1), 1\n" + " sum_var(i_0,i_2) = sum_var(i_0,i_2) + a(i_0,i_1,i_2)\n" + " enddo\n" + " enddo\n" + " enddo\n" + " result = sum_var(:,:)\n" + " DEALLOCATE(a)\n\n" + "end program sum_test\n") + psyir = fortran_reader.psyir_from_source(code) + trans = Sum2CodeTrans() + # FileContainer/Routine/Assignment/IntrinsicCall + node = psyir.children[0].children[1].children[1] + trans.apply(node) + result = fortran_writer(psyir) + assert result == expected From 203f62b82ceba9a0f958be4446433fde7a201dda Mon Sep 17 00:00:00 2001 From: rupertford Date: Tue, 12 Sep 2023 15:05:38 +0100 Subject: [PATCH 09/11] pr #2148. Addressed reviewer's comments. --- .../intrinsics/maxval2code_trans.py | 12 +- .../intrinsics/minval2code_trans.py | 12 +- .../intrinsics/mms_base_trans.py | 12 +- .../intrinsics/sum2code_trans.py | 14 +- .../intrinsics/maxval2code_trans_test.py | 4 +- .../intrinsics/minval2code_trans_test.py | 4 +- .../intrinsics/mms_base_trans_test.py | 322 ++++++++---------- .../intrinsics/sum2code_trans_test.py | 4 +- 8 files changed, 185 insertions(+), 199 deletions(-) diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py index 42407d25f1..f22b3dd063 100644 --- a/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2code_trans.py @@ -117,11 +117,11 @@ class Maxval2CodeTrans(MMSBaseTrans): .. code-block:: fortran - R = MAXVAL(ARRAY) ! array syntax - R = MAXVAL(ARRAY(:,:)) ! array notation - R = MAXVAL(ARRAY(1:10,lo:hi) ! array sections - R = MAXVAL(ARRAY(1:10,:) ! mixture of array sections and array notation - R = MAXVAL(ARRAY(1:10,2) ! NOT SUPPORTED as 2 is a scalar bound + R = MAXVAL(ARRAY) ! array syntax + R = MAXVAL(ARRAY(:,:)) ! array notation + R = MAXVAL(ARRAY(1:10,lo:hi)) ! array sections + R = MAXVAL(ARRAY(1:10,:)) ! mix of array sections and array notation + R = MAXVAL(ARRAY(1:10,2)) ! NOT SUPPORTED as 2 is a scalar bound For example: @@ -175,7 +175,7 @@ def _loop_body(self, array_reduction, array_iterators, var_symbol, List[:py:class:`psyclone.psyir.symbols.DataSymbol`] :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` - :param array_ref: a reference to the array from which the + :param array_ref: a reference to the array for which the maximum is being determined. :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` diff --git a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py index c8210b88cc..572ee8103d 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minval2code_trans.py @@ -117,11 +117,11 @@ class Minval2CodeTrans(MMSBaseTrans): .. code-block:: fortran - R = MINVAL(ARRAY) ! array syntax - R = MINVAL(ARRAY(:,:)) ! array notation - R = MINVAL(ARRAY(1:10,lo:hi) ! array sections - R = MINVAL(ARRAY(1:10,:) ! mixture of array sections and array notation - R = MINVAL(ARRAY(1:10,2) ! NOT SUPPORTED as 2 is a scalar bound + R = MINVAL(ARRAY) ! array syntax + R = MINVAL(ARRAY(:,:)) ! array notation + R = MINVAL(ARRAY(1:10,lo:hi)) ! array sections + R = MINVAL(ARRAY(1:10,:)) ! mix of array sections and array notation + R = MINVAL(ARRAY(1:10,2)) ! NOT SUPPORTED as 2 is a scalar bound For example: @@ -175,7 +175,7 @@ def _loop_body(self, array_reduction, array_iterators, var_symbol, List[:py:class:`psyclone.psyir.symbols.DataSymbol`] :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` - :param array_ref: a reference to the array from which the + :param array_ref: a reference to the array for which the minimum is being determined. :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` diff --git a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py index fa628dadc2..255c9ee2b5 100644 --- a/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/mms_base_trans.py @@ -111,6 +111,8 @@ def validate(self, node, options=None): supported. :raises TransformationError: if the array datatype is not supported. + :raises TransformationError: if the intrinsic is not part of + an assignment. ''' if not isinstance(node, IntrinsicCall): @@ -140,7 +142,8 @@ def validate(self, node, options=None): else: info = f"type '{type(dim_ref).__name__}'" raise TransformationError( - f"Can't find the value of the dimension argument. Expected " + f"Can't find the value of the 'dim' argument to the " + f"{self._INTRINSIC_NAME} intrinsic. Expected " f"it to be a literal or a reference to a known constant " f"value, but found '{dim_ref.debug_string()}' which is " f"{info}.") @@ -178,6 +181,11 @@ def validate(self, node, options=None): f"Only real and integer types supported for array " f"'{array_ref.name}', but found '{array_intrinsic.name}'.") + if not node.ancestor(Assignment): + raise TransformationError( + f"{self.name} only works when the intrinsic is part " + f"of an Assignment.") + # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -336,10 +344,10 @@ def apply(self, node, options=None): statement = self._loop_body( array_reduction, array_iterators, var_symbol, array_ref) - # pylint: disable=unidiomatic-typecheck if mask_ref: # A mask argument has been provided for ref in mask_ref.walk(Reference): + # pylint: disable=unidiomatic-typecheck if ref.name == array_ref.name and type(ref) == Reference: # The array is not indexed so it needs indexing # for the loop nest. diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index 9c743c7080..fe3ef757fe 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -53,7 +53,7 @@ class Sum2CodeTrans(MMSBaseTrans): performed. If SUM contains a single positional argument which is an array, - all element on that array are summed and the result returned in + all elements of that array are summed and the result returned in the scalar R. .. code-block:: fortran @@ -115,11 +115,11 @@ class Sum2CodeTrans(MMSBaseTrans): .. code-block:: fortran - R = SUM(ARRAY) ! array syntax - R = SUM(ARRAY(:,:)) ! array notation - R = SUM(ARRAY(1:10,lo:hi) ! array sections - R = SUM(ARRAY(1:10,:) ! mixture of array sections and array notation - R = SUM(ARRAY(1:10,2) ! NOT SUPPORTED as 2 is a scalar bound + R = SUM(ARRAY) ! array syntax + R = SUM(ARRAY(:,:)) ! array notation + R = SUM(ARRAY(1:10,lo:hi)) ! array sections + R = SUM(ARRAY(1:10,:)) ! mix of array sections and array notation + R = SUM(ARRAY(1:10,2)) ! NOT SUPPORTED as 2 is a scalar bound For example: @@ -174,7 +174,7 @@ def _loop_body(self, array_reduction, array_iterators, var_symbol, List[:py:class:`psyclone.psyir.symbols.DataSymbol`] :param var_symbol: the symbol used to store the final result. :type var_symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` - :param array_ref: a reference to the array from which the + :param array_ref: a reference to the array for which the sum is being determined. :type array_ref: :py:class:`psyclone.psyir.nodes.ArrayReference` diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py index 568774058c..8e634538e2 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/maxval2code_trans_test.py @@ -42,6 +42,7 @@ REAL_TYPE, DataSymbol, INTEGER_TYPE, ArrayType) from psyclone.psyir.transformations import ( Maxval2CodeTrans, TransformationError) +from psyclone.tests.utilities import Compile def test_initialise(): @@ -138,7 +139,7 @@ def test_validate(): in str(info.value)) -def test_apply(fortran_reader, fortran_writer): +def test_apply(fortran_reader, fortran_writer, tmpdir): '''Test that the apply method, implemented in the parent class, works as expected. @@ -173,3 +174,4 @@ def test_apply(fortran_reader, fortran_writer): trans.apply(intrinsic_node) result = fortran_writer(psyir) assert result == expected + assert Compile(tmpdir).string_compiles(result) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py index 617351030a..03517f7feb 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/minval2code_trans_test.py @@ -42,6 +42,7 @@ REAL_TYPE, DataSymbol, INTEGER_TYPE, ArrayType) from psyclone.psyir.transformations import ( Minval2CodeTrans, TransformationError) +from psyclone.tests.utilities import Compile def test_initialise(): @@ -138,7 +139,7 @@ def test_validate(): in str(info.value)) -def test_apply(fortran_reader, fortran_writer): +def test_apply(fortran_reader, fortran_writer, tmpdir): '''Test that the apply method, implemented in the parent class, works as expected. @@ -173,3 +174,4 @@ def test_apply(fortran_reader, fortran_writer): trans.apply(intrinsic_node) result = fortran_writer(psyir) assert result == expected + assert Compile(tmpdir).string_compiles(result) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py index 5131d83b34..640a3463c7 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py @@ -46,6 +46,7 @@ from psyclone.psyir.transformations import TransformationError, Sum2CodeTrans from psyclone.psyir.transformations.intrinsics.mms_base_trans import ( MMSBaseTrans) +from psyclone.tests.utilities import Compile class TestTrans(MMSBaseTrans): @@ -193,10 +194,10 @@ def test_dimension_arg(fortran_reader): trans = NamedTestTrans() with pytest.raises(TransformationError) as info: trans.validate(node) - assert ("Can't find the value of the dimension argument. Expected it to " - "be a literal or a reference to a known constant value, but " - "found 'dimension * 2' which is type 'BinaryOperation'." - in str(info.value)) + assert ("Can't find the value of the 'dim' argument to the SUM " + "intrinsic. Expected it to be a literal or a reference to " + "a known constant value, but found 'dimension * 2' which " + "is type 'BinaryOperation'." in str(info.value)) def test_non_constant_dim_value_binop(fortran_reader): @@ -219,10 +220,10 @@ def test_non_constant_dim_value_binop(fortran_reader): node = psyir.children[0].children[0].children[1] with pytest.raises(TransformationError) as info: trans.validate(node) - assert ("Can't find the value of the dimension argument. Expected it to " - "be a literal or a reference to a known constant value, but " - "found 'a + b' which is type 'BinaryOperation'." - in str(info.value)) + assert ("Can't find the value of the 'dim' argument to the SUM " + "intrinsic. Expected it to be a literal or a reference to " + "a known constant value, but found 'a + b' which is type " + "'BinaryOperation'." in str(info.value)) def test_non_constant_dim_value_ref(fortran_reader): @@ -246,29 +247,29 @@ def test_non_constant_dim_value_ref(fortran_reader): node = psyir.children[0].children[0].children[1] with pytest.raises(TransformationError) as info: trans.validate(node) - assert ("Can't find the value of the dimension argument. Expected it to " - "be a literal or a reference to a known constant value, but " - "found 'x' which is a reference to a 'DataSymbol'." - in str(info.value)) + assert ("Can't find the value of the 'dim' argument to the SUM " + "intrinsic. Expected it to be a literal or a reference to " + "a known constant value, but found 'x' which is a reference " + "to a 'DataSymbol'." in str(info.value)) -def test_lhs(fortran_reader, fortran_writer): +def test_lhs(fortran_reader, fortran_writer, tmpdir): '''Test that the correct code is produced when the minval, maxval or - sum is on the LHS of an asssignment. Use the value). Use the - Sum2CodeTrans transformations (a subclass of MMSBaseTrans), as it - is easier to test. + sum is on the LHS of an asssignment. Uses the Sum2CodeTrans + transformation (a subclass of MMSBaseTrans), as it is easier to + test. ''' code = ( "subroutine test(array)\n" " real :: array(10)\n" - " real :: result\n" + " real :: result(10)\n" " result(sum(array)) = 0.0\n" "end subroutine\n") expected = ( "subroutine test(array)\n" " real, dimension(10) :: array\n" - " real :: result\n" + " real, dimension(10) :: result\n" " real :: sum_var\n" " integer :: i_0\n\n" " sum_var = 0.0\n" @@ -285,6 +286,7 @@ def test_lhs(fortran_reader, fortran_writer): trans.apply(node) result = fortran_writer(psyir) assert result == expected + assert Compile(tmpdir).string_compiles(result) def test_array_arg(fortran_reader): @@ -386,6 +388,27 @@ def test_array_type_arg(fortran_reader): "but found 'BOOLEAN'." in str(info.value)) +def test_not_assignment(fortran_reader): + '''Test that the expected exception is raised if the intrinsic call is + not part of an assignment (e.g. is an argument to a subroutine), + as this is not currently supported. + + ''' + code = ( + "subroutine test(array)\n" + " integer :: array(10)\n" + " call routine(sum(array))\n" + "end subroutine\n") + psyir = fortran_reader.psyir_from_source(code) + # FileContainer/Routine/Call/IntrinsicCall + node = psyir.children[0].children[0].children[0] + trans = NamedTestTrans() + with pytest.raises(TransformationError) as info: + trans.validate(node) + assert ("NamedTestTrans only works when the intrinsic is part " + "of an Assignment" in str(info.value)) + + # apply @pytest.mark.parametrize("idim1,idim2,rdim11,rdim12,rdim21,rdim22", @@ -395,10 +418,11 @@ def test_array_type_arg(fortran_reader): (":", ":", "LBOUND(array, 1)", "UBOUND(array, 1)", "LBOUND(array, 2)", "UBOUND(array, 2)")]) def test_apply(idim1, idim2, rdim11, rdim12, rdim21, rdim22, - fortran_reader, fortran_writer): + fortran_reader, fortran_writer, tmpdir): '''Test that a sum intrinsic as the only term on the rhs of an assignment with a single array argument gets transformed as - expected. Test with known and unknown array sizes. + expected. Test with known and unknown array sizes. What we care + about here are the sum_var array and the generated loop bounds. ''' code = ( @@ -408,75 +432,62 @@ def test_apply(idim1, idim2, rdim11, rdim12, rdim21, rdim22, f" real :: result\n" f" result = sum(array)\n" f"end subroutine\n") - expected = ( - f"subroutine test(array, n, m)\n" - f" integer :: n\n integer :: m\n" - f" real, dimension({idim1},{idim2}) :: array\n" - f" real :: result\n real :: sum_var\n" - f" integer :: i_0\n integer :: i_1\n\n" - f" sum_var = 99.0\n" + expected_decl = " real :: sum_var\n" + expected_bounds = ( f" do i_1 = {rdim21}, {rdim22}, 1\n" - f" do i_0 = {rdim11}, {rdim12}, 1\n" - f" array(i_0,i_1) = 33.0\n" - f" enddo\n" - f" enddo\n" - f" result = sum_var\n\n" - f"end subroutine test\n") + f" do i_0 = {rdim11}, {rdim12}, 1\n") + expected_result = " result = sum_var\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/IntrinsicCall - node = psyir.children[0].children[0].children[1] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) - assert result == expected + assert expected_decl in result + assert expected_bounds in result + assert expected_result in result + assert Compile(tmpdir).string_compiles(result) -def test_apply_multi(fortran_reader, fortran_writer): +def test_apply_multi(fortran_reader, fortran_writer, tmpdir): '''Test that a sum intrinsic as part of multiple term on the rhs of an assignment with a single array argument gets transformed as - expected. + expected. What we care about here are the sum_var array and the + generated loop bounds. ''' code = ( "subroutine test(array,n,m,value1,value2)\n" - " use precision\n" " integer :: n, m\n" " real :: array(n,m)\n" " real :: value1, value2\n" " real :: result\n" " result = value1 + sum(array) * value2\n" "end subroutine\n") - expected = ( - "subroutine test(array, n, m, value1, value2)\n" - " use precision\n" - " integer :: n\n integer :: m\n" - " real, dimension(n,m) :: array\n" - " real :: value1\n real :: value2\n" - " real :: result\n real :: sum_var\n" - " integer :: i_0\n integer :: i_1\n\n" - " sum_var = 99.0\n" + expected_decl = " real :: sum_var\n" + expected_bounds = ( " do i_1 = 1, m, 1\n" - " do i_0 = 1, n, 1\n" - " array(i_0,i_1) = 33.0\n" - " enddo\n" - " enddo\n" - " result = value1 + sum_var * value2\n\n" - "end subroutine test\n") + " do i_0 = 1, n, 1\n") + expected_result = " result = value1 + sum_var * value2\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) - assert result == expected + assert expected_decl in result + assert expected_bounds in result + assert expected_result in result + assert Compile(tmpdir).string_compiles(result) -def test_apply_dimension_1d(fortran_reader, fortran_writer): +def test_apply_dimension_1d(fortran_reader, fortran_writer, tmpdir): '''Test that the apply method works as expected when a dimension argument is specified and the array is one dimensional. This should be the same as if dimension were not specified at all. + What we care about here are the sum_var array and the generated + loop bounds. ''' code = ( @@ -486,32 +497,27 @@ def test_apply_dimension_1d(fortran_reader, fortran_writer): " real :: result\n" " result = value1 + sum(array,dim=1) * value2\n" "end subroutine\n") - expected = ( - "subroutine test(array, value1, value2)\n" - " real, dimension(:) :: array\n" - " real :: value1\n real :: value2\n" - " real :: result\n real :: sum_var\n" - " integer :: i_0\n\n" - " sum_var = 99.0\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" - " array(i_0) = 33.0\n" - " enddo\n" - " result = value1 + sum_var * value2\n\n" - "end subroutine test\n") + expected_decl = " real :: sum_var\n" + expected_bounds = " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" + expected_result = " result = value1 + sum_var * value2\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) - assert result == expected + assert expected_decl in result + assert expected_bounds in result + assert expected_result in result + assert Compile(tmpdir).string_compiles(result) -def test_apply_dimension_multid(fortran_reader, fortran_writer): +def test_apply_dimension_multid(fortran_reader, fortran_writer, tmpdir): '''Test that the apply method works as expected when a dimension - argument is specified and the array is multi-dimensional. + argument is specified and the array is multi-dimensional. What we + care about here are the sum_var array and the generated loop + bounds. ''' code = ( @@ -522,37 +528,29 @@ def test_apply_dimension_multid(fortran_reader, fortran_writer): " real :: result(n,p)\n" " result(:,:) = value1 + sum(array,dim=2) * value2\n" "end subroutine\n") - expected = ( - "subroutine test(array, value1, value2, n, m, p)\n" - " integer :: n\n integer :: m\n integer :: p\n" - " real, dimension(n,m,p) :: array\n" - " real :: value1\n real :: value2\n" - " real, dimension(n,p) :: result\n" - " real, dimension(n,p) :: sum_var\n" - " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " sum_var(:,:) = 99.0\n" + expected_decl = " real, dimension(n,p) :: sum_var\n" + expected_bounds = ( " do i_2 = 1, p, 1\n" " do i_1 = 1, m, 1\n" - " do i_0 = 1, n, 1\n" - " array(i_0,i_1,i_2) = 33.0\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + sum_var(:,:) * value2\n\n" - "end subroutine test\n") + " do i_0 = 1, n, 1\n") + expected_result = " result(:,:) = value1 + sum_var(:,:) * value2\n\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) - assert result == expected + assert expected_decl in result + assert expected_bounds in result + assert expected_result in result + assert Compile(tmpdir).string_compiles(result) -def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): - '''Test that lbound and ubound are used if the bounds of the array are +def test_apply_dimension_multid_unknown( + fortran_reader, fortran_writer, tmpdir): + '''Test that lbound and ubound are used to declare the sum_var + variable and for the loop bounds if the bounds of the array are not known. ''' @@ -563,42 +561,32 @@ def test_apply_dimension_multid_unknown(fortran_reader, fortran_writer): " real :: result(:,:)\n" " result(:,:) = value1 + sum(array,dim=2) * value2\n" "end subroutine\n") - expected = ( - "subroutine test(array, value1, value2, result)\n" - " real, dimension(:,:,:) :: array\n" - " real :: value1\n" - " real :: value2\n" - " real, dimension(:,:) :: result\n" + expected_decl = ( " real, dimension(LBOUND(array, 1):UBOUND(array, 1),LBOUND(array, 3):" - "UBOUND(array, 3)) :: sum_var\n" - " integer :: i_0\n" - " integer :: i_1\n" - " integer :: i_2\n\n" - " sum_var(:,:) = 99.0\n" + "UBOUND(array, 3)) :: sum_var\n") + expected_bounds = ( " do i_2 = LBOUND(array, 3), UBOUND(array, 3), 1\n" " do i_1 = LBOUND(array, 2), UBOUND(array, 2), 1\n" - " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n" - " array(i_0,i_1,i_2) = 33.0\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + sum_var(:,:) * value2\n\n" - "end subroutine test\n") + " do i_0 = LBOUND(array, 1), UBOUND(array, 1), 1\n") + expected_result = " result(:,:) = value1 + sum_var(:,:) * value2\n\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) - assert result == expected + assert expected_decl in result + assert expected_bounds in result + assert expected_result in result + assert Compile(tmpdir).string_compiles(result) -# specified array range -def test_apply_dimension_multid_range(fortran_reader, fortran_writer): +def test_apply_dimension_multid_range(fortran_reader, fortran_writer, tmpdir): '''Test that the apply method works as expected when an array range is - specified and the array is multi-dimensional. + specified and the array is multi-dimensional. What we + care about here are the sum_var array and the generated loop + bounds. ''' code = ( @@ -610,37 +598,29 @@ def test_apply_dimension_multid_range(fortran_reader, fortran_writer): " result(:,:) = value1 + sum(array(1:n,m-1:m,1:p),dim=2) * " "value2\n" "end subroutine\n") - expected = ( - "subroutine test(array, value1, value2, n, m, p)\n" - " integer :: n\n integer :: m\n integer :: p\n" - " real, dimension(:,:,:) :: array\n" - " real :: value1\n real :: value2\n" - " real, dimension(n,p) :: result\n" - " real, dimension(n,p) :: sum_var\n" - " integer :: i_0\n integer :: i_1\n integer :: i_2\n\n" - " sum_var(:,:) = 99.0\n" + expected_decl = " real, dimension(n,p) :: sum_var\n" + expected_bounds = ( " do i_2 = 1, p, 1\n" " do i_1 = m - 1, m, 1\n" - " do i_0 = 1, n, 1\n" - " array(i_0,i_1,i_2) = 33.0\n" - " enddo\n" - " enddo\n" - " enddo\n" - " result(:,:) = value1 + sum_var(:,:) * value2\n\n" - "end subroutine test\n") + " do i_0 = 1, n, 1\n") + expected_result = " result(:,:) = value1 + sum_var(:,:) * value2\n\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/BinaryOperation(ADD)/ # BinaryOperation(MUL)/IntrinsicCall - node = psyir.children[0].children[0].children[1].children[1]. \ - children[0] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) - assert result == expected + assert expected_decl in result + assert expected_bounds in result + assert expected_result in result + assert Compile(tmpdir).string_compiles(result) -def test_mask(fortran_reader, fortran_writer): +def test_mask(fortran_reader, fortran_writer, tmpdir): '''Test that the transformation works when there is a mask specified. + What we care about here are the sum_var array, the generated loop + bounds and the mask. ''' code = ( @@ -649,35 +629,30 @@ def test_mask(fortran_reader, fortran_writer): " real :: result\n" " result = sum(array, mask=MOD(array, 2.0)==1)\n" "end program\n") + # We are using the NamedTestTrans subclass here which simply sets + # the value of the array to 33. expected = ( - "program test\n" - " real, dimension(10,10) :: array\n" - " real :: result\n" - " real :: sum_var\n" - " integer :: i_0\n" - " integer :: i_1\n\n" - " sum_var = 99.0\n" " do i_1 = 1, 10, 1\n" " do i_0 = 1, 10, 1\n" " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" " array(i_0,i_1) = 33.0\n" " end if\n" " enddo\n" - " enddo\n" - " result = sum_var\n\n" - "end program test") + " enddo\n") psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/IntrinsicCall - node = psyir.children[0].children[0].children[1] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) assert expected in result + assert Compile(tmpdir).string_compiles(result) -def test_mask_dimension(fortran_reader, fortran_writer): +def test_mask_dimension(fortran_reader, fortran_writer, tmpdir): '''Test that the transformation works when there is a mask and a - dimension specified. + dimension specified. What we care about here are the sum_var + array, the generated loop bounds and the mask. ''' code = ( @@ -687,37 +662,32 @@ def test_mask_dimension(fortran_reader, fortran_writer): " integer, parameter :: dimension=2\n" " result = sum(array, dimension, mask=MOD(array, 2.0)==1)\n" "end program\n") - expected = ( - "program test\n" - " integer, parameter :: dimension = 2\n" - " real, dimension(10,10) :: array\n" - " real, dimension(10) :: result\n" - " real, dimension(10) :: sum_var\n" - " integer :: i_0\n" - " integer :: i_1\n\n" - " sum_var(:) = 99.0\n" + expected_decl = " real, dimension(10) :: sum_var\n" + # We are using the NamedTestTrans subclass here which simply sets + # the value of the array to 33. + expected_bounds = ( " do i_1 = 1, 10, 1\n" " do i_0 = 1, 10, 1\n" " if (MOD(array(i_0,i_1), 2.0) == 1) then\n" " array(i_0,i_1) = 33.0\n" - " end if\n" - " enddo\n" - " enddo\n" - " result = sum_var(:)\n\n" - "end program test") + " end if\n") + expected_result = " result = sum_var(:)\n" psyir = fortran_reader.psyir_from_source(code) # FileContainer/Routine/Assignment/IntrinsicCall - node = psyir.children[0].children[0].children[1] + node = psyir.walk(IntrinsicCall)[0] trans = NamedTestTrans() trans.apply(node) result = fortran_writer(psyir) - assert expected in result + assert expected_decl in result + assert expected_bounds in result + assert expected_result in result + assert Compile(tmpdir).string_compiles(result) -def test_mask_array_indexed(fortran_reader, fortran_writer): +def test_mask_array_indexed(fortran_reader, fortran_writer, tmpdir): '''Test that the mask code works if the array iself it used as part of the mask. In this case it will already be indexed. Use the - Sum2CodeTrans transformations (a subclass of MMSBaseTrans), as it + Sum2CodeTrans transformation (a subclass of MMSBaseTrans), as it is easier to test. ''' @@ -752,13 +722,14 @@ def test_mask_array_indexed(fortran_reader, fortran_writer): psyir = fortran_reader.psyir_from_source(code) trans = Sum2CodeTrans() # FileContainer/Routine/Assignment/IntrinsicCall - node = psyir.children[0].children[4].children[1] + node = psyir.walk(IntrinsicCall)[0] trans.apply(node) result = fortran_writer(psyir) assert result == expected + assert Compile(tmpdir).string_compiles(result) -def test_allocate_dim(fortran_reader, fortran_writer): +def test_allocate_dim(fortran_reader, fortran_writer, tmpdir): '''Test that a newly created array is allocated after the original array is allocated (if the original array is allocated). Use the Sum2CodeTrans transformations (a subclass of MMSBaseTrans), as it @@ -768,7 +739,7 @@ def test_allocate_dim(fortran_reader, fortran_writer): code = ( "program sum_test\n" " integer, allocatable :: a(:,:,:)\n" - " integer :: result\n" + " integer :: result(4,4)\n" " allocate(a(4,4,4))\n" " result = sum(a, dim=2)\n" " deallocate(a)\n" @@ -776,7 +747,7 @@ def test_allocate_dim(fortran_reader, fortran_writer): expected = ( "program sum_test\n" " integer, allocatable, dimension(:,:,:) :: a\n" - " integer :: result\n" + " integer, dimension(4,4) :: result\n" " integer, allocatable, dimension(:,:) :: sum_var\n" " integer :: i_0\n" " integer :: i_1\n" @@ -798,7 +769,8 @@ def test_allocate_dim(fortran_reader, fortran_writer): psyir = fortran_reader.psyir_from_source(code) trans = Sum2CodeTrans() # FileContainer/Routine/Assignment/IntrinsicCall - node = psyir.children[0].children[1].children[1] + node = psyir.walk(IntrinsicCall)[1] trans.apply(node) result = fortran_writer(psyir) assert result == expected + assert Compile(tmpdir).string_compiles(result) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py index de0f87585a..334e3b4c3c 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/sum2code_trans_test.py @@ -41,6 +41,7 @@ from psyclone.psyir.symbols import ( REAL_TYPE, DataSymbol, INTEGER_TYPE, ArrayType, ScalarType) from psyclone.psyir.transformations import Sum2CodeTrans, TransformationError +from psyclone.tests.utilities import Compile def test_initialise(): @@ -138,7 +139,7 @@ def test_validate(): in str(info.value)) -def test_apply(fortran_reader, fortran_writer): +def test_apply(fortran_reader, fortran_writer, tmpdir): '''Test that the apply method, implemented in the parent class, works as expected. @@ -171,3 +172,4 @@ def test_apply(fortran_reader, fortran_writer): trans.apply(intrinsic_node) result = fortran_writer(psyir) assert result == expected + assert Compile(tmpdir).string_compiles(result) From 9122507bab92808e5c6ed2c2d09f3ebe0270773c Mon Sep 17 00:00:00 2001 From: rupertford Date: Thu, 14 Sep 2023 12:33:23 +0100 Subject: [PATCH 10/11] pr #2148. Adressed reviewer's comment. --- .../intrinsics/mms_base_trans_test.py | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py index 640a3463c7..abd4aa0774 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/mms_base_trans_test.py @@ -175,31 +175,6 @@ def test_indexed_array_error(fortran_reader): "found a fixed dimension in 'array(1,1)'." in str(info.value)) -def test_dimension_arg(fortran_reader): - '''Test that the expected exception is raised if the dimension arg is - not a literal or a variable. - - ''' - code = ( - "subroutine test(array,n,m)\n" - " integer :: n, m\n" - " real :: array(10,10)\n" - " real :: result\n" - " integer :: dimension\n" - " result = sum(array, dim=dimension*2)\n" - "end subroutine\n") - psyir = fortran_reader.psyir_from_source(code) - # FileContainer/Routine/Assignment/IntrinsicCall - node = psyir.children[0].children[0].children[1] - trans = NamedTestTrans() - with pytest.raises(TransformationError) as info: - trans.validate(node) - assert ("Can't find the value of the 'dim' argument to the SUM " - "intrinsic. Expected it to be a literal or a reference to " - "a known constant value, but found 'dimension * 2' which " - "is type 'BinaryOperation'." in str(info.value)) - - def test_non_constant_dim_value_binop(fortran_reader): '''Test that the expected exception is raised if the literal value of the dim arg can not be determined (as it is a binary From 8505d929cde84c90fa27edeeedb310cd30dff9f3 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Thu, 14 Sep 2023 16:13:53 +0100 Subject: [PATCH 11/11] #2148 update changelog and UG --- changelog | 2 ++ psyclone.pdf | Bin 1443868 -> 1444253 bytes 2 files changed, 2 insertions(+) diff --git a/changelog b/changelog index 61d166cab1..a89893da83 100644 --- a/changelog +++ b/changelog @@ -594,6 +594,8 @@ 200) PR #2309 for #1558. Add support for out-of-order parameter statements. + 201) PR #2148 towards #2105. Adds {Min,Max}Val2Code transformations. + release 2.3.1 17th of June 2022 1) PR #1747 for #1720. Adds support for If blocks to PSyAD. diff --git a/psyclone.pdf b/psyclone.pdf index 60c075a130d28b307deee6fcad93b8575b1a7f51..32ffc1c6b7044f90d43cca9c21d8613781199fb3 100644 GIT binary patch delta 128182 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 &iot5sN5t-RfQhu`9Zqc-(@|N8>a{@A9 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 z zPOO>6%AXik4h_IaL;kGBvK}ImVEEIR8JAs2PCiYSruu2eNYDF26xIz1E{hQd4nbmQ zE{ywx{6?B@d%|CrY1uo0s0=EMJ6hfx6J+H=(3>-VHv^e_!}E*^qe!bu)(?w(zgcM7G-GeQGdUDu`L1TGA+Sdh0p#y6|w9R+?25FqAoih#PRv# zBm3?J`S}>Y(RWTh4c|lQ;Q^QH;yLw*&Dpifs~hr8V#BC!fb#G+mss+TLiyKo()uoQ z%zuQ3WawZrP9%r7TvXex3-tSk1|=NuTQN1@NqfKJkbmX997LF0rrNI4_b=p@Q8@-? zXvz*RyZmWM*m6mby%P$UNPP4tJcC#1rduS#O57hOEt1>boE|>$N2gIl&P1FINUMw? z4y0AWfVvODwIqa@F))G;i#55Rhsm1j5k?Wb@8cRk;m151pu-i77H;H|a?X2o_)ld# zl`QKJnB5mBylRXbUR=+MRQ|z7+@HKiyc3D~EvfOW`!jvCC#jJ<{Cgm7j%)>r75h$! z732hAc{s_i9BPzs@c=MVbDf!ZdqR#VHE{ujPuuTP@ZN z?!6mUaxRg9Kaz^C(TfPjE=&EqZ|A( zjIs?@B$k}CZpSMo3I;H;Dc8v5uf=riRb@`u*1Q@gVeVL(A-Nl}Bspo`dORDp&Wr`6 z0V`5*iIfey4C}dXRRG6k%_T`~=cmb9lJ2AO?cj*cTQ*}fE$)%`TXX3SAEyf$ecUEJ zCvV%!!9{MRCfk#sG>1M{tlz`jcs%QL6u84_4iD2L6y@_X=ph!*Eb%sj6a@dIKIQ{S z_rs`Y(;*_cY#CQ^Rl?sa1yDe( z^DeNbmf>}-)L!eSWvNfBR%;YD!tD1x#4tze7AdEL?u{D*p3BvzHLaS)@L0|(&j!`` zf7k95@q8X?CM*b=`9$GAZZ-dwqG&W)4=;xZWI<8@%c74F>^g|^T@ld3vHcNyjS*EM z#Z(7yG3XxTRfl=iR~VZt>N}4OUa5-PKZ@Qn^zW1U4N!B3v1jNWt9vuv-iL8S;Ab|q z*9znil0I!45^}O5iHR%Ux|1_xZq&n7)9r>e^LSm%#)a!{|0?VS%B*snxpX_3KkjmZ zx6!8^D4{*G+Hs|I%7qbmya*wVd=&N}&#ZO)>-(c*FwK*XzQWoEwJh%CSWy&mUhdtH z21UBXs_W0fkm;xy2HY$e$?dR^o?TFEMgbV{zxHD?!=!BTo)cpi$aDi~QS`Gvt6bQS z#^ zXzpyHe2VI2PF;`1ihpi3>C72p4126$Rr5la^Hfky26$=~T3{C0@Ipa--+}^~6&Vsd zq1C+`;03<;t;sM)EoD%zUO#4BL?AUzvJsycGHwfR|cC7bQp z6Kfkad;mTov7JS3Y~Iba$KhiKLtDYoF38pQ&~D5&{MX~1JJlN@v|M#dIZL?61FjQl z{9SbWXKO6BF0z9ib>i!Jj4Mgz0A4cgSktWqN-x<`V4v4=lWx23(Y`$veE7oqBH>&6 z-SU!w(z}-3TD0shwj98;-TRf`Zl^;*N9F|U?-`qG-j_Pq8_B-`ldFg?B>G00;tmZt z=ti`a9OV+n_$_*#NO8~Bed;4L@HY0rhFw^WfCP<>OXO=Q6das``BxF!_Yb1Tm!!zO)RqR#~;OXAg0J|f?!N^TxlBV^qk@XzlVcG9o|YHW`frm-mh zU2x-?AcUp>gktly{4VM+LNV=tpN;YLJet-obUK7o$V}tL*xJPC=HqMb4Or7uriwK4 zLe`fgd&UGFi1lT$T6+(nTLbST; zD4AXB*A3d7mORhc9RTBA4nih?N9Vw;$1%nySJhY|1+v*FrQwHcL%o-`AdW6#L%Scg zSz&Y@lI`Z(w7Va=7?^nE>WrMhS?EKet}l()Mz3 z5o3-Qh}VCio2TrZSMv2uxoN~=-fI-5O8)$&W|w`S-Y|`jx-cDijWBfR$&@xWh@`ny3!up z<^Hzq|K!GI-rRRbvT0BOijTe^Clwj&-X7%J_ z?-g4?);D4M^f_xIGQ$z2*ECb(Vlz^?vMH(M?#$6BOeMY>TF~^?aE6%(zBo6oO_O-n z46Ca;zREghEo}5w=pK$KDQ`5G=5yA#OxHaeIb)&OdmN0S&>c7V9#wf{n)$&_gASo6N_wXzu0 zfuTFLe3Y~fjfaQc#un=~Wwh>qW9DAyI&-ArFsRTa!KR|Y306`=@3C8vViY40w zjWpeFvL0?|%v#zlx@39bsZLAeXQ%j2$B5>gC>Bz>u6?T%T6~a44-sN)eRr;~P7_as zLVgCZlU)x6?pI7hwCjFN9v)x=)#hm|cgQsY+rlO3ejTnjs)#uIv7Ciik&g3?bQ=*N zj_(C{Ti)tB$*Be<**tLU7T`U>DNR!{ARy{@<#5?H+pGsEvqLh8M0!hqjr);vv`a29 zsb3b{5=Xhza=n?<)(i0`_MGXchXXo@UI7Q5wAl6xcKm(P#P$wC-<+7waO#PqII=D+fTSwuGv5zkpLqLpmM1=R zywYx`7t7kcpWym95qwBMX6`-K4fnTH^E}tci`-?KSHNz3*hT>^Dj;P9TI+>n-5I() z1vYSXFD+Og=^e#?KDs(C#2*fSY}yp#gK!8W@k27G5%ls4eA=ht0o!$e^%HDH#>@2i z`7$^VWb(Akn>X z_}zm3;F~j$tH!Bjxdw3$%b-*Lbb!YigKNt^75q#mqCW(?2SS4-ALe?y)qenG)j;}wo<)PyZn$r zBf5aZu1x<2FHU4Ijt2XHGwW7Re+*~#XxYi!I#hB!DP+LK`@9ZyD{k$TSr1=;EXdW{ zAdRe~Ek*nVP@+Ca;W)7e!6ny!lAdwcQ^0!kc5mgD2>!fd#K}bqkouAqo6v7&_A!(? zlqb?XK0N4l;2q}^)J4HRiOb;VgTz!i^n40hkuU`aqY2N#HYQ|JHdWby)Subee3c^+ zQp3b_x`AI6EBLV!nyYMX$NH^q`(NmfD^g>FH~3iU?P=XRVz2bi;t9|xgB5W zqEtlDn+M~)-BU6Mvaz3m<-_^y!8rl^OrC#ScsR$Oe1M^{E4iyIkKF1E8Sop5&qt@O z`E)tYxz(8g>##V@c(3D9xIk5GpJ$2m*rW*H)@ctGTj8tHJC*xk_v^TMg=C;|muDw} zEz)rhW?X4kW4T#7bz?R#8K-FX-FLDe?E%~iJAkG>Uew_jbq7R6xq&X;l} z7!AxXQPoU20T~p*6grMx1I{UKxH+&x3hXtdX1vCdzsQu0TlLV#rVAiC){vtJgNn;W z*(7sDc%&;4;yu9R{_3N2cJ8?0W{T{d`6GO%ngfs(~l>VX}p2JF?qSEF+|^e#Wg_ ze)n3TFS-qQUyWuC59wUvIlYdc_@e{&AE9*9kW13LMJQ=6+UpUGAKK(4ynxF9Y9vim zdfkKt8}7GEBi&L**@tJvbPs?v|0sQ1`uDM1L4BiOElwWG_3t6L$hSX|amELeY-Y?! zws_~+u(W)G@$8=|HNdkFR)-wTxb*$(U0|8;L}I? zJr?;V4E&lY@m$Qw2dRX_ntjIQABWh(9Gi~RazemZ<5lMn2o@q797Yzr!nHjwgJsU34s>T!n8 zxn*ko_m4W~qAtbV@{ju{${qCee)TWPotx!jFGEYbeOpsI$<67<+0{ZV7Ufue_y}iq z(nfZo6nvDg`Ruz%(OcN7911M`G>Yn^q>uBg<35gj*%^bv&|4U*CRnN-E<@r1p>g-M zxsMNWJ0JSdw?9<2eL`BP5tbSD+wU8hn=kHM^r0UIsoG}z@ChpGG_#>Uxs0ECuN)^i z$5nyxd(Zk-vxKdH*C2=`VBfxfuHQK>#UL*65&el7e(oeq`LL5Yu0F7_>*HVu`K-9I zZ57EUtGEDV6>uyX)#(yP-}!FfLVRoJZPdz}@-O@LGfl4FpWi2zf3M%r3ccQ-ch9cW zzQH@aw%Mpbk8tqwlK=N&71^T*8!c<6#e( zK$?1U+8`yXNLm{uE1W#pG;O^p?U<64TwD=Jn_RC}-R#fuE{ z8VP%HUppWDe=nf^KX~=%u6$L&apGmHHD<*58u~l8W2)`o$JE%SUf78&FWY-O)BnEj j{l2ruxd;AEe*TaATwKoq_f^ZwDa)M`7S_F?ckcfH1}a^n