diff --git a/doc/user_guide/transformations.rst b/doc/user_guide/transformations.rst index 4643462a04..a6aa6d3b21 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -270,6 +270,18 @@ can be found in the API-specific sections). #### +.. autoclass:: psyclone.psyir.transformations.Intrinsic2CodeTrans + :members: apply + :no-index: + +#### + +.. autoclass:: psyclone.psyir.transformations.ArrayIntrinsic2LoopTrans + :members: apply + :no-index: + +#### + .. autoclass:: psyclone.psyir.transformations.IncreaseRankLoopArraysTrans :members: apply :no-index: diff --git a/examples/nemo/scripts/utils.py b/examples/nemo/scripts/utils.py index 6c0b27d6ed..1162d29cc5 100755 --- a/examples/nemo/scripts/utils.py +++ b/examples/nemo/scripts/utils.py @@ -18,10 +18,10 @@ from psyclone.psyir.symbols import DataSymbol, ArrayType from psyclone.psyir.transformations import ( ArrayAssignment2LoopsTrans, HoistLoopBoundExprTrans, HoistLocalArraysTrans, - HoistTrans, InlineTrans, Maxval2LoopTrans, Sum2LoopTrans, Minval2LoopTrans, - Product2LoopTrans, ProfileTrans, OMPMinimiseSyncTrans, + HoistTrans, InlineTrans, ProfileTrans, OMPMinimiseSyncTrans, Reference2ArrayRangeTrans, ScalarisationTrans, IncreaseRankLoopArraysTrans, - MaximalRegionTrans, TransformationError, DataNodeToTempTrans) + MaximalRegionTrans, TransformationError, DataNodeToTempTrans, + ArrayIntrinsic2LoopTrans) # USE statements to chase to gather additional symbol information. NEMO_MODULES_TO_IMPORT = [ @@ -186,14 +186,7 @@ def normalise_loops( if loopify_array_intrinsics: for intr in schedule.walk(IntrinsicCall): try: - if intr.intrinsic.name == "MAXVAL": - Maxval2LoopTrans().apply(intr, verbose=True) - elif intr.intrinsic.name == "SUM": - Sum2LoopTrans().apply(intr, verbose=True) - elif intr.intrinsic.name == "MINVAL": - Minval2LoopTrans().apply(intr, verbose=True) - elif intr.intrinsic.name == "PRODUCT": - Product2LoopTrans().apply(intr, verbose=True) + ArrayIntrinsic2LoopTrans().apply(intr, verbose=True) except TransformationError as err: print(err.value) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 75ed4d6ede..ea09a26fb4 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -2372,7 +2372,11 @@ def split_kwargs(self, **kwargs) -> tuple[dict[str, Any]]: if key in trans.get_valid_options(): other_dicts[idx][key] = kwargs[key] if key not in type(self).get_valid_options(): - del first_dict[key] + # Sometimes we may have the same option in multiple + # subtransformations, so we only delete the key + # from the first_dict if it's still present. + if key in first_dict: + del first_dict[key] return first_dict, *other_dicts diff --git a/src/psyclone/psyir/transformations/__init__.py b/src/psyclone/psyir/transformations/__init__.py index b6b1981067..f1d8772dfa 100644 --- a/src/psyclone/psyir/transformations/__init__.py +++ b/src/psyclone/psyir/transformations/__init__.py @@ -116,6 +116,10 @@ # Metatransformations from psyclone.psyir.transformations.metatransformations.omp_cpu_routine_trans\ import OMPCPURoutineTrans +from psyclone.psyir.transformations.metatransformations.\ + intrinsic2code_trans import Intrinsic2CodeTrans +from psyclone.psyir.transformations.metatransformations.\ + arrayintrinsic2loop_trans import ArrayIntrinsic2LoopTrans # For AutoAPI documentation generation __all__ = [ diff --git a/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py index 23ce6ca7da..8a10f651aa 100644 --- a/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/abs2code_trans.py @@ -13,8 +13,8 @@ ''' import warnings -from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( - Intrinsic2CodeTrans) +from psyclone.psyir.transformations.intrinsics.intrinsic2code_basetrans \ + import Intrinsic2CodeBaseTrans from psyclone.psyir.nodes import ( BinaryOperation, Assignment, Reference, Literal, IfBlock, IntrinsicCall) from psyclone.psyir.symbols import DataSymbol @@ -22,7 +22,7 @@ @transformation_documentation_wrapper -class Abs2CodeTrans(Intrinsic2CodeTrans): +class Abs2CodeTrans(Intrinsic2CodeBaseTrans): '''Provides a transformation from a PSyIR ABS Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. diff --git a/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py index ac9ac0d8c2..a85416c0d2 100644 --- a/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/dotproduct2code_trans.py @@ -14,18 +14,19 @@ # pylint: disable=too-many-locals -from psyclone.psyir.nodes import BinaryOperation, Assignment, Reference, \ - Loop, Literal, ArrayReference, Range, Routine, IntrinsicCall +from psyclone.psyir.nodes import ( + BinaryOperation, Assignment, Reference, Loop, Literal, ArrayReference, + Range, Routine, IntrinsicCall) from psyclone.psyir.symbols import DataSymbol, ScalarType from psyclone.psyir.transformations.transformation_error \ import TransformationError -from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import \ - Intrinsic2CodeTrans +from psyclone.psyir.transformations.intrinsics.intrinsic2code_basetrans \ + import Intrinsic2CodeBaseTrans from psyclone.utils import transformation_documentation_wrapper @transformation_documentation_wrapper -class DotProduct2CodeTrans(Intrinsic2CodeTrans): +class DotProduct2CodeTrans(Intrinsic2CodeBaseTrans): '''Provides a transformation from a PSyIR DOT_PRODUCT Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. diff --git a/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_basetrans.py similarity index 97% rename from src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py rename to src/psyclone/psyir/transformations/intrinsics/intrinsic2code_basetrans.py index 5250628a55..d2bf5fa2ed 100644 --- a/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/intrinsic2code_basetrans.py @@ -20,7 +20,7 @@ @transformation_documentation_wrapper -class Intrinsic2CodeTrans(Transformation, metaclass=abc.ABCMeta): +class Intrinsic2CodeBaseTrans(Transformation, metaclass=abc.ABCMeta): '''Provides support for transformations from PSyIR IntrinsicCall nodes to equivalent PSyIR code in a PSyIR tree. Such transformations can be useful when the intrinsic is not supported @@ -114,4 +114,4 @@ def apply(self, node, options=None, **kwargs): # For AutoAPI auto-documentation generation. -__all__ = ["Intrinsic2CodeTrans"] +__all__ = ["Intrinsic2CodeBaseTrans"] diff --git a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py index e485c88293..e5f518438a 100644 --- a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py @@ -20,8 +20,8 @@ Loop, Literal, ArrayReference, Range, IntrinsicCall) from psyclone.psyir.symbols import ( DataSymbol, ScalarType, TypedSymbol, UnsupportedType) -from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( - Intrinsic2CodeTrans) +from psyclone.psyir.transformations.intrinsics.intrinsic2code_basetrans \ + import Intrinsic2CodeBaseTrans from psyclone.utils import transformation_documentation_wrapper @@ -65,7 +65,7 @@ def _create_array_ref(array_symbol, loop_idx_symbols, other_dims, @transformation_documentation_wrapper -class Matmul2CodeTrans(Intrinsic2CodeTrans): +class Matmul2CodeTrans(Intrinsic2CodeBaseTrans): '''Provides a transformation from a PSyIR MATMUL Operator node to equivalent code in a PSyIR tree. Validity checks are also performed. diff --git a/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py index baa4bdd2d4..34335b8662 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minormax2code_trans.py @@ -21,14 +21,13 @@ BinaryOperation, Assignment, Reference, IfBlock, IntrinsicCall ) from psyclone.psyir.symbols import DataSymbol -from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( - Intrinsic2CodeTrans -) +from psyclone.psyir.transformations.intrinsics.intrinsic2code_basetrans \ + import Intrinsic2CodeBaseTrans from psyclone.utils import transformation_documentation_wrapper @transformation_documentation_wrapper -class MinOrMax2CodeTrans(Intrinsic2CodeTrans, ABC): +class MinOrMax2CodeTrans(Intrinsic2CodeBaseTrans, ABC): '''Provides a utility transformation from a PSyIR MIN or MAX Intrinsic node to equivalent code in a PSyIR tree. Validity checks are also performed (by the parent class). This utility transformation is diff --git a/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py index 518e2ce750..9bb33b800c 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sign2code_trans.py @@ -13,8 +13,8 @@ ''' import warnings -from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( - Intrinsic2CodeTrans) +from psyclone.psyir.transformations.intrinsics.intrinsic2code_basetrans \ + import Intrinsic2CodeBaseTrans from psyclone.psyir.transformations import Abs2CodeTrans from psyclone.psyir.nodes import ( BinaryOperation, Assignment, Reference, Literal, IfBlock, IntrinsicCall) @@ -23,7 +23,7 @@ @transformation_documentation_wrapper -class Sign2CodeTrans(Intrinsic2CodeTrans): +class Sign2CodeTrans(Intrinsic2CodeBaseTrans): '''Provides a transformation from a PSyIR SIGN intrinsic node to equivalent code in a PSyIR tree. Validity checks are also performed. diff --git a/src/psyclone/psyir/transformations/metatransformations/arrayintrinsic2loop_trans.py b/src/psyclone/psyir/transformations/metatransformations/arrayintrinsic2loop_trans.py new file mode 100644 index 0000000000..c7b149cf40 --- /dev/null +++ b/src/psyclone/psyir/transformations/metatransformations/arrayintrinsic2loop_trans.py @@ -0,0 +1,55 @@ +# ----------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +# Facilities Council +# SPDX-License-Identifier: BSD-3-Clause +# See the full LICENSE file in the project root for details. +# ----------------------------------------------------------------------------- + +'''This module contains the ArrayIntrinsic2LoopTrans metatransformation.''' + +from psyclone.psyir.nodes import IntrinsicCall +from psyclone.psyir.transformations.intrinsics.maxval2loop_trans\ + import Maxval2LoopTrans +from psyclone.psyir.transformations.intrinsics.minval2loop_trans\ + import Minval2LoopTrans +from psyclone.psyir.transformations.intrinsics.product2loop_trans\ + import Product2LoopTrans +from psyclone.psyir.transformations.intrinsics.sum2loop_trans\ + import Sum2LoopTrans +from psyclone.psyir.transformations.metatransformations.intrinsic2code_trans\ + import Intrinsic2CodeTrans +from psyclone.utils import transformation_documentation_wrapper + + +@transformation_documentation_wrapper +class ArrayIntrinsic2LoopTrans(Intrinsic2CodeTrans): + '''This metatransformation applies any of the Intrinsic2Loop + transformations to the provided input. The available transformations are + Maxval2LoopTrans, Sum2LoopTrans, Minval2LoopTrans, or Product2LoopTrans. + ''' + _SUB_TRANSFORMATIONS = [Maxval2LoopTrans, Sum2LoopTrans, + Minval2LoopTrans, Product2LoopTrans] + + # Create a map of intrinsic names to the appropriate Intrinsic2Code + # transformation. This should be in the same order as the + # _SUB_TRANSFORMATIONS else the _split_kwargs on this Transformation + # may not work correctly. + intrinsic_to_trans = { + IntrinsicCall.Intrinsic.MAXVAL: Maxval2LoopTrans, + IntrinsicCall.Intrinsic.SUM: Sum2LoopTrans, + IntrinsicCall.Intrinsic.MINVAL: Minval2LoopTrans, + IntrinsicCall.Intrinsic.PRODUCT: Product2LoopTrans} + + def apply(self, node: IntrinsicCall, **kwargs) -> None: + ''' + Applies the appropriate Intrinsic2Loop transformation to the provided + input node. + + :param node: the IntrinsicCall to be transformed. + ''' + # The apply function is required for the docstring wrapper to work + # correctly. + super().apply(node, **kwargs) + + +__all__ = ["ArrayIntrinsic2LoopTrans"] diff --git a/src/psyclone/psyir/transformations/metatransformations/intrinsic2code_trans.py b/src/psyclone/psyir/transformations/metatransformations/intrinsic2code_trans.py new file mode 100644 index 0000000000..b227475b6d --- /dev/null +++ b/src/psyclone/psyir/transformations/metatransformations/intrinsic2code_trans.py @@ -0,0 +1,134 @@ +# ----------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +# Facilities Council +# SPDX-License-Identifier: BSD-3-Clause +# See the full LICENSE file in the project root for details. +# ----------------------------------------------------------------------------- + +'''This module contains the Intrinsic2CodeTrans metatransformation.''' + +from typing import Any +import logging + +from psyclone.psyGen import Transformation +from psyclone.psyir.nodes import IntrinsicCall +from psyclone.psyir.transformations.intrinsics.abs2code_trans\ + import Abs2CodeTrans +from psyclone.psyir.transformations.intrinsics.dotproduct2code_trans\ + import DotProduct2CodeTrans +from psyclone.psyir.transformations.intrinsics.matmul2code_trans\ + import Matmul2CodeTrans +from psyclone.psyir.transformations.intrinsics.max2code_trans\ + import Max2CodeTrans +from psyclone.psyir.transformations.intrinsics.maxval2loop_trans\ + import Maxval2LoopTrans +from psyclone.psyir.transformations.intrinsics.min2code_trans\ + import Min2CodeTrans +from psyclone.psyir.transformations.intrinsics.minval2loop_trans\ + import Minval2LoopTrans +from psyclone.psyir.transformations.intrinsics.product2loop_trans\ + import Product2LoopTrans +from psyclone.psyir.transformations.intrinsics.sign2code_trans\ + import Sign2CodeTrans +from psyclone.psyir.transformations.intrinsics.sum2loop_trans\ + import Sum2LoopTrans +from psyclone.utils import transformation_documentation_wrapper + + +@transformation_documentation_wrapper +class Intrinsic2CodeTrans(Transformation): + '''This metatransformation applies any of the Intrinsic2Code + transformations to the provided input. + ''' + _SUB_TRANSFORMATIONS = [Maxval2LoopTrans, Sum2LoopTrans, + Minval2LoopTrans, Product2LoopTrans, + DotProduct2CodeTrans, Abs2CodeTrans, + Max2CodeTrans, Min2CodeTrans, + Sign2CodeTrans, Matmul2CodeTrans] + + # Create a map of intrinsic names to the appropriate Intrinsic2Code + # transformation. This should be in the same order as the + # _SUB_TRANSFORMATIONS else the _split_kwargs on this Transformation + # may not work correctly. + intrinsic_to_trans = { + IntrinsicCall.Intrinsic.MAXVAL: Maxval2LoopTrans, + IntrinsicCall.Intrinsic.SUM: Sum2LoopTrans, + IntrinsicCall.Intrinsic.MINVAL: Minval2LoopTrans, + IntrinsicCall.Intrinsic.PRODUCT: Product2LoopTrans, + IntrinsicCall.Intrinsic.DOT_PRODUCT: DotProduct2CodeTrans, + IntrinsicCall.Intrinsic.ABS: Abs2CodeTrans, + IntrinsicCall.Intrinsic.MAX: Max2CodeTrans, + IntrinsicCall.Intrinsic.MIN: Min2CodeTrans, + IntrinsicCall.Intrinsic.SIGN: Sign2CodeTrans, + IntrinsicCall.Intrinsic.MATMUL: Matmul2CodeTrans} + + def validate(self, node: IntrinsicCall, **kwargs) -> None: + ''' + Validates the input options. + + :param node: the IntrinsicCall to be transformed. + + :raises TypeError: if the input node is not an IntrinsicCall. + ''' + # Validate the provided options are allowed and typed correctly. + self.validate_options(**kwargs) + + if not isinstance(node, IntrinsicCall): + raise TypeError( + f"Input node to {self.name} must be an IntrinsicCall but " + f"received '{type(node).__name__}'." + ) + + def _split_kwargs(self, **kwargs) -> \ + tuple[dict[str, Any], + dict[IntrinsicCall.Intrinsic, dict[str, Any]]]: + ''' + :returns: the kwargs for this transformation and the kwargs dict for + the sub transformations indexed by appropriate Intrinsic. + ''' + # The split_kwargs function returns a tuple containing the + # kwargs for this transformation as the first entry and then + # the kwargs for the SUB_TRANSFORAMTIONS in order as the following + # entries. + split_kwargs = self.split_kwargs(**kwargs) + local_kwargs = split_kwargs[0] + sub_kwargs = {} + for i, intrinsic in enumerate(self.intrinsic_to_trans): + sub_kwargs[intrinsic] = split_kwargs[i+1] + return local_kwargs, sub_kwargs + + def apply(self, node: IntrinsicCall, **kwargs) -> None: + ''' + Applies the appropriate Intrinsic2Code transformation to the provided + input node. + + :param node: the IntrinsicCall to be transformed. + ''' + # Split the options for the subtransformations. The options are + # returned in the order of the _SUB_TRANSFORMATIONS list. + kwargs_dict = {} + local_kwargs, kwargs_dict = self._split_kwargs(**kwargs) + + self.validate(node, **local_kwargs) + + # If the intrinsic is one of the supported intrinsics then + # apply the relevant transformation. + if node.intrinsic in self.intrinsic_to_trans: + self.intrinsic_to_trans[node.intrinsic]().apply( + node, **kwargs_dict[node.intrinsic] + ) + else: + # Setup the logger. + logger = logging.getLogger(__name__) + supported_intrinsics = [] + if logger.isEnabledFor(logging.INFO): + supported_intrinsics = [intrinsic.name for intrinsic in + self.intrinsic_to_trans.keys()] + logger.info( + f"Input node was intrinsic of type '{node.intrinsic.name}' " + f"which is not transformed by {self.name}. Supported " + f"intrinsics are {supported_intrinsics}." + ) + + +__all__ = ["Intrinsic2CodeTrans"] diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_basetrans_test.py similarity index 85% rename from src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_trans_test.py rename to src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_basetrans_test.py index 319153cdad..c431330083 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/intrinsic2code_basetrans_test.py @@ -5,14 +5,14 @@ # See the full LICENSE file in the project root for details. # ----------------------------------------------------------------------------- -'''Module containing tests for the Intrinsic2CodeTrans abstract class which +'''Module containing tests for the Intrinsic2CodeBaseTrans abstract class which provides common functionality for the intrinsic transformations (such as MIN, ABS and SIGN).''' import pytest from psyclone.psyir.transformations import TransformationError -from psyclone.psyir.transformations.intrinsics.intrinsic2code_trans import ( - Intrinsic2CodeTrans) +from psyclone.psyir.transformations.intrinsics.intrinsic2code_basetrans \ + import Intrinsic2CodeBaseTrans from psyclone.psyir.symbols import DataSymbol, ScalarType from psyclone.psyir.nodes import ( Reference, Assignment, Literal, IntrinsicCall) @@ -20,21 +20,22 @@ def test_create(): # pylint: disable=abstract-class-instantiated - '''Check that Intrinsic2CodeTrans is abstract.''' + '''Check that Intrinsic2CodeBaseTrans is abstract.''' with pytest.raises(TypeError) as excinfo: - _ = Intrinsic2CodeTrans() + _ = Intrinsic2CodeBaseTrans() msg = str(excinfo.value) # Python >= 3.9 spots that 'method' should be singular. Prior to this it # was plural. Python >= 3.12 tweaks the error message yet again to mention # the lack of an implementation and to quote the method name. # We split the check to accommodate for this. - assert ("Can't instantiate abstract class Intrinsic2CodeTrans with" in msg) + assert ("Can't instantiate abstract class Intrinsic2CodeBaseTrans " + "with" in msg) assert ("abstract method" in msg) assert ("apply" in msg) -class DummyTrans(Intrinsic2CodeTrans): - '''Dummy transformation class used to test Intrinsic2CodeTrans +class DummyTrans(Intrinsic2CodeBaseTrans): + '''Dummy transformation class used to test Intrinsic2CodeBaseTrans methods.''' # pylint: disable=arguments-differ, no-method-argument def apply(): diff --git a/src/psyclone/tests/psyir/transformations/metatransformations/arrayintrinsic2loop_trans_test.py b/src/psyclone/tests/psyir/transformations/metatransformations/arrayintrinsic2loop_trans_test.py new file mode 100644 index 0000000000..cd030ee234 --- /dev/null +++ b/src/psyclone/tests/psyir/transformations/metatransformations/arrayintrinsic2loop_trans_test.py @@ -0,0 +1,71 @@ +# ----------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +# Facilities Council +# SPDX-License-Identifier: BSD-3-Clause +# See the full LICENSE file in the project root for details. +# ----------------------------------------------------------------------------- + +'''This module contains the tests for the ArrayIntrinsic2LoopTrans +metatransformation.''' + +import logging +import pytest +from psyclone.psyir.nodes import IntrinsicCall +from psyclone.psyir.transformations import ( + ArrayIntrinsic2LoopTrans, Maxval2LoopTrans, Minval2LoopTrans, + Product2LoopTrans, Sum2LoopTrans +) + +LOGGER_NAME = ("psyclone.psyir.transformations.metatransformations." + "intrinsic2code_trans") + + +@pytest.mark.parametrize("code, transformation", [ + ("j = MAXVAL(i)", Maxval2LoopTrans), + ("j = MINVAL(i)", Minval2LoopTrans), + ("j = PRODUCT(i)", Product2LoopTrans), + ("j = SUM(i)", Sum2LoopTrans), + ]) +def test_arrayintrinsic2loop_trans_apply(fortran_reader, monkeypatch, + code, transformation): + '''Test the apply function of the ArrayIntrinsic2LoopTrans + metatransformation. + ''' + code = f"""subroutine test + integer, dimension(:) :: i + integer :: j + + {code} + + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + def error_func(self, *args, **kwargs): + ''' + Monkeypatched apply function for the used transformation. + ''' + raise TypeError("Test function usage.") + monkeypatch.setattr(transformation, "apply", error_func) + intrinsic = psyir.walk(IntrinsicCall)[0] + with pytest.raises(TypeError) as err: + ArrayIntrinsic2LoopTrans().apply(intrinsic) + assert ("Test function usage." in str(err.value)) + + +def test_arrayintrinsic2loop_trans_apply_logging(fortran_reader, caplog): + ''' + Test the apply function of the ArrayIntrinsic2LoopTrans + metatransformation logs a message when an unsupported Intrinsic is found. + ''' + code = """subroutine test + real :: j + + j = matmul(j, j) + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + intrinsic = psyir.walk(IntrinsicCall)[0] + with caplog.at_level(logging.DEBUG, logger=LOGGER_NAME): + ArrayIntrinsic2LoopTrans().apply(intrinsic) + assert ("Input node was intrinsic of type 'MATMUL' which is not " + "transformed by ArrayIntrinsic2LoopTrans. Supported intrinsics " + "are " in caplog.text) diff --git a/src/psyclone/tests/psyir/transformations/metatransformations/intrinsic2code_trans_test.py b/src/psyclone/tests/psyir/transformations/metatransformations/intrinsic2code_trans_test.py new file mode 100644 index 0000000000..7de3c84640 --- /dev/null +++ b/src/psyclone/tests/psyir/transformations/metatransformations/intrinsic2code_trans_test.py @@ -0,0 +1,87 @@ +# ----------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +# Facilities Council +# SPDX-License-Identifier: BSD-3-Clause +# See the full LICENSE file in the project root for details. +# ----------------------------------------------------------------------------- + +'''This module contains the tests for the Intrinsic2CodeTrans +metatransformation.''' + +import pytest +import logging +from psyclone.psyir.nodes import IntrinsicCall +from psyclone.psyir.transformations import ( + Abs2CodeTrans, DotProduct2CodeTrans, Intrinsic2CodeTrans, + Matmul2CodeTrans, Max2CodeTrans, Maxval2LoopTrans, Min2CodeTrans, + Minval2LoopTrans, Product2LoopTrans, Sign2CodeTrans, Sum2LoopTrans) + + +LOGGER_NAME = ("psyclone.psyir.transformations.metatransformations." + "intrinsic2code_trans") + + +def test_intrinsic2code_trans_validate(fortran_reader): + ''' + Tests the validate method of the Intrinsic2CodeTrans + metatransformation. + ''' + with pytest.raises(TypeError) as err: + Intrinsic2CodeTrans().validate(123) + assert ("Input node to Intrinsic2CodeTrans must be an IntrinsicCall " + "but received 'int'." in str(err.value)) + + +@pytest.mark.parametrize("code, transformation", [ + ("j = MAXVAL(i)", Maxval2LoopTrans), + ("j = MINVAL(i)", Minval2LoopTrans), + ("j = PRODUCT(i)", Product2LoopTrans), + ("j = SUM(i)", Sum2LoopTrans), + ("j = DOT_PRODUCT(i, i)", DotProduct2CodeTrans), + ("j = ABS(i)", Abs2CodeTrans), + ("j = MAX(i(0), i(1))", Max2CodeTrans), + ("j = MIN(i(0), i(1))", Min2CodeTrans), + ("j = SIGN(i(0), +0.5)", Sign2CodeTrans), + ("j = MATMUL(i, i)", Matmul2CodeTrans), + ]) +def test_intrinsic2code_trans_apply(fortran_reader, monkeypatch, + code, transformation): + '''Test the apply function of the Intrinsic2CodeTrans + metatransformation. + ''' + code = f"""subroutine test + integer, dimension(:) :: i + integer :: j + + {code} + + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + def error_func(self, *args, **kwargs): + ''' + Monkeypatched apply function for the used transformation. + ''' + raise TypeError("Test function usage.") + monkeypatch.setattr(transformation, "apply", error_func) + intrinsic = psyir.walk(IntrinsicCall)[0] + with pytest.raises(TypeError) as err: + Intrinsic2CodeTrans().apply(intrinsic) + assert ("Test function usage." in str(err.value)) + + +def test_intrinsic2code_trans_apply_logging(fortran_reader, caplog): + '''Test the apply function of the Intrinsic2CodeTrans metatransformation + logs a message when an unsupported Intrinsic is found.''' + code = """subroutine test + real :: j + + j = log10(j) + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + intrinsic = psyir.walk(IntrinsicCall)[0] + with caplog.at_level(logging.DEBUG, logger=LOGGER_NAME): + Intrinsic2CodeTrans().apply(intrinsic) + assert ("Input node was intrinsic of type 'LOG10' which is not " + "transformed by Intrinsic2CodeTrans. Supported intrinsics are " + in caplog.text)