From 5b8e9876a352b87ae73db7160186833effcbcbfa Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 23 Feb 2026 15:40:00 +0000 Subject: [PATCH 1/5] Initial version --- .../metatransformations/__init__.py | 0 .../omp_cpu_routine_trans.py | 109 ++++++++++++++++++ .../omp_minimise_sync_trans.py | 16 ++- 3 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 src/psyclone/psyir/transformations/metatransformations/__init__.py create mode 100644 src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py diff --git a/src/psyclone/psyir/transformations/metatransformations/__init__.py b/src/psyclone/psyir/transformations/metatransformations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py new file mode 100644 index 0000000000..43e19c0c67 --- /dev/null +++ b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py @@ -0,0 +1,109 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2026, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Authors A. B. G. Chalk, STFC Daresbury Lab + +'''This module contains the OpenMPCPURoutineTrans metatransformation.''' + +from psyclone.psyir.nodes import ( + Directive, Loop, Routine +) +from psyclone.psyir.transformations.transformation_error import ( + TransformationError) +from psyclone.psyir.transformations.omp_loop_trans import OMPLoopTrans +from psyclone.psyir.transformations.maximal_omp_parallel_region_trans import ( + MaximalOMPParallelRegionTrans) +from psyclone.psyir.transformations.omp_minimise_sync_trans import ( + OMPMinimiseSyncTrans) +from psyclone.utils import transformation_documentation_wrapper + + +class OpenMPCPURoutineTrans(OMPLoopTrans, MaximalOMPParallelRegionTrans, + OMPMinimiseSyncTrans): + '''FIXME Docstring''' + + def validate(self, node: Routine, **kwargs): + ''' + Validates the input options of the OpenMPCPURoutineTrans. + + :param node: The Routine node to validate. + ''' + # Validate the provided options are allowed and typed correctly. + self.validate_options(**kwargs) + + if not isinstance(node, Routine): + raise TypeError( + f"{self.name} expected a Routine input node but got a node " + f"of type {type(node).__name__}." + ) + + def apply(self, node: Routine, **kwargs): + ''' + Applies the OMPLoopTrans, MaximalOMPParallelRegionTrans and + OMPMinimiseSyncTrans to the relevant parts of the input + node. + + :param node: The Routine node to transform + ''' + self.validate(node, **kwargs) + + # Find all of the loops. + loops = node.walk(Loop) + for loop in loops: + if loop.ancestor(Directive): + continue # Skip if an outer loop is already parallelised + try: + # Validate that this loop can be parallelised. + OMPLoopTrans.validate(self, loop, **kwargs) + # If it is, then apply the OMPLoopTrans + OMPLoopTrans.apply(self, loop, **kwargs) + except TransformationError: + # If we fail to parallelise a loop we just skip it. + continue + + # Apply the maximal openMP parallel region transformation to the + # routine. + MaximalOMPParallelRegionTrans.apply(self, node.children[:]) + + nowait = self.get_option("nowait", **kwargs) + # If the asynchronous option was specified, then we need to apply the + # OMPMinimiseSyncTrans as well. + if nowait: + OMPMinimiseSyncTrans.apply(self, node) + + +# Multiple inheritance doesn't work with the decorator it seems. +transformation_documentation_wrapper(OpenMPCPURoutineTrans, + inherit=[OMPLoopTrans, + MaximalOMPParallelRegionTrans, + OMPMinimiseSyncTrans]) diff --git a/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py b/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py index 5ce267e542..dacf49d13e 100644 --- a/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py +++ b/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py @@ -35,9 +35,7 @@ '''Contains the OMPMinimiseSyncTrans.''' -# TODO #2837: Once we leave python 3.8 we can use list instead of List for -# type hints. -from typing import List, Union +from typing import Union from psyclone.psyGen import Transformation from psyclone.psyir.nodes import ( @@ -210,8 +208,8 @@ def _eliminate_adjacent_barriers(self, routine: Routine, if barrier.immediately_follows(barriers[i-1]): barrier.detach() - def _find_dependencies(self, directives: List[Directive]) \ - -> List[Union[Node, bool]]: + def _find_dependencies(self, directives: list[Directive]) \ + -> list[Union[Node, bool]]: ''' Finds the next dependencies for each of the directives provided. @@ -244,8 +242,8 @@ def _find_dependencies(self, directives: List[Directive]) \ return dependencies @staticmethod - def _reduce_barrier_set(required_barriers: List[Node], - depending_barriers: List[List[Node]]) -> None: + def _reduce_barrier_set(required_barriers: list[Node], + depending_barriers: list[list[Node]]) -> None: ''' Reduces the depending_barriers set according to the list of required_barriers, i.e. if a required_barrier is present in one of @@ -285,7 +283,7 @@ def _reduce_barrier_set(required_barriers: List[Node], @staticmethod def _get_max_barrier_dependency( - depending_barriers: List[List[Node]]) -> int: + depending_barriers: list[list[Node]]) -> int: ''' Returns the maximum size of a sublist in the depending_barriers input. @@ -299,7 +297,7 @@ def _get_max_barrier_dependency( ''' return len(max(depending_barriers, key=len)) - def _eliminate_barriers(self, node: Routine, directives: List[Directive], + def _eliminate_barriers(self, node: Routine, directives: list[Directive], barrier_type: type) -> None: ''' Eliminates barriers of the barrier_type in the input Routine node that From 4d909eedfd007f514c555fa2a426cdc732a917e3 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 23 Feb 2026 15:53:59 +0000 Subject: [PATCH 2/5] Updated initial functionality --- .../omp_cpu_routine_trans.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py index 43e19c0c67..a1ac18f8c7 100644 --- a/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py +++ b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py @@ -61,11 +61,11 @@ def validate(self, node: Routine, **kwargs): # Validate the provided options are allowed and typed correctly. self.validate_options(**kwargs) - if not isinstance(node, Routine): - raise TypeError( - f"{self.name} expected a Routine input node but got a node " - f"of type {type(node).__name__}." - ) +# if not isinstance(node, Routine): +# raise TypeError( +# f"{self.name} expected a Routine input node but got a node " +# f"of type {type(node).__name__}." +# ) def apply(self, node: Routine, **kwargs): ''' @@ -93,13 +93,16 @@ def apply(self, node: Routine, **kwargs): # Apply the maximal openMP parallel region transformation to the # routine. - MaximalOMPParallelRegionTrans.apply(self, node.children[:]) + MaximalOMPParallelRegionTrans.validate(self, node.children[:], + **kwargs) + MaximalOMPParallelRegionTrans.apply(self, node.children[:], **kwargs) nowait = self.get_option("nowait", **kwargs) # If the asynchronous option was specified, then we need to apply the # OMPMinimiseSyncTrans as well. if nowait: - OMPMinimiseSyncTrans.apply(self, node) + OMPMinimiseSyncTrans.validate(self, node, **kwargs) + OMPMinimiseSyncTrans.apply(self, node, **kwargs) # Multiple inheritance doesn't work with the decorator it seems. From 961d59afd5602be5d475c2097ed8efc784223622 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 23 Feb 2026 15:58:33 +0000 Subject: [PATCH 3/5] More changes --- .../metatransformations/omp_cpu_routine_trans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py index a1ac18f8c7..63b6751777 100644 --- a/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py +++ b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py @@ -105,7 +105,7 @@ def apply(self, node: Routine, **kwargs): OMPMinimiseSyncTrans.apply(self, node, **kwargs) -# Multiple inheritance doesn't work with the decorator it seems. +# TODO #3335: Multiple inheritance doesn't work with the decorator. transformation_documentation_wrapper(OpenMPCPURoutineTrans, inherit=[OMPLoopTrans, MaximalOMPParallelRegionTrans, From 3a7c00736498c9e00a0051ed7fa481e58ca107b8 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 23 Feb 2026 16:01:20 +0000 Subject: [PATCH 4/5] Some missing code --- src/psyclone/psyGen.py | 5 +++++ src/psyclone/tests/utils_test.py | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index dd7ed4f505..38d6d8f4c0 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -2797,6 +2797,11 @@ def validate_options(self, **kwargs): wrong_types = {} for option in kwargs: if option not in valid_options: + # This is needed to enable metatransformations where + # only some inherited classes have options set on + # superclasses + if option == "options": + continue invalid_options.append(option) continue if valid_options[option].type is not None: diff --git a/src/psyclone/tests/utils_test.py b/src/psyclone/tests/utils_test.py index 99ff13f289..b14ef9eb85 100644 --- a/src/psyclone/tests/utils_test.py +++ b/src/psyclone/tests/utils_test.py @@ -209,7 +209,6 @@ def apply(self, node, opt3: int = 1, **kwargs): InheritingTrans, inherit=[BaseTrans1, BaseTrans2] ) - print(InheritingTrans.apply.__doc__) assert "param bool opt1: opt1 docstring." in InheritingTrans.apply.__doc__ assert "param bool opt2: opt2 docstring." in InheritingTrans.apply.__doc__ assert ("param bool opt1: opt1 docstring." From 2bee4383e4c4da10af2554086f559aa1d59e7743 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 15 Jun 2026 15:41:11 +0100 Subject: [PATCH 5/5] Implementation using new subtransformation system --- .../omp_cpu_routine_trans.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py index 63b6751777..65cc41dde7 100644 --- a/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py +++ b/src/psyclone/psyir/transformations/metatransformations/omp_cpu_routine_trans.py @@ -35,6 +35,7 @@ '''This module contains the OpenMPCPURoutineTrans metatransformation.''' +from psyclone.psyGen import Transformation from psyclone.psyir.nodes import ( Directive, Loop, Routine ) @@ -48,9 +49,11 @@ from psyclone.utils import transformation_documentation_wrapper -class OpenMPCPURoutineTrans(OMPLoopTrans, MaximalOMPParallelRegionTrans, - OMPMinimiseSyncTrans): +@transformation_documentation_wrapper +class OMPCPURoutineTrans(Transformation): '''FIXME Docstring''' + _SUB_TRANSFORMATIONS = [OMPLoopTrans, MaximalOMPParallelRegionTrans, + OMPMinimiseSyncTrans] def validate(self, node: Routine, **kwargs): ''' @@ -61,12 +64,6 @@ def validate(self, node: Routine, **kwargs): # Validate the provided options are allowed and typed correctly. self.validate_options(**kwargs) -# if not isinstance(node, Routine): -# raise TypeError( -# f"{self.name} expected a Routine input node but got a node " -# f"of type {type(node).__name__}." -# ) - def apply(self, node: Routine, **kwargs): ''' Applies the OMPLoopTrans, MaximalOMPParallelRegionTrans and @@ -77,6 +74,11 @@ def apply(self, node: Routine, **kwargs): ''' self.validate(node, **kwargs) + # Split the options for the subtransformations. + _, loop_kwargs, maxpar_kwargs, minsync_kwargs = self.split_kwargs( + **kwargs + ) + # Find all of the loops. loops = node.walk(Loop) for loop in loops: @@ -84,9 +86,9 @@ def apply(self, node: Routine, **kwargs): continue # Skip if an outer loop is already parallelised try: # Validate that this loop can be parallelised. - OMPLoopTrans.validate(self, loop, **kwargs) + OMPLoopTrans.validate(self, loop, **loop_kwargs) # If it is, then apply the OMPLoopTrans - OMPLoopTrans.apply(self, loop, **kwargs) + OMPLoopTrans.apply(self, loop, **loop_kwargs) except TransformationError: # If we fail to parallelise a loop we just skip it. continue @@ -94,19 +96,17 @@ def apply(self, node: Routine, **kwargs): # Apply the maximal openMP parallel region transformation to the # routine. MaximalOMPParallelRegionTrans.validate(self, node.children[:], - **kwargs) - MaximalOMPParallelRegionTrans.apply(self, node.children[:], **kwargs) + **maxpar_kwargs) + MaximalOMPParallelRegionTrans.apply(self, node.children[:], + **maxpar_kwargs) nowait = self.get_option("nowait", **kwargs) # If the asynchronous option was specified, then we need to apply the # OMPMinimiseSyncTrans as well. if nowait: - OMPMinimiseSyncTrans.validate(self, node, **kwargs) - OMPMinimiseSyncTrans.apply(self, node, **kwargs) + OMPMinimiseSyncTrans.validate(self, node, **minsync_kwargs) + OMPMinimiseSyncTrans.apply(self, node, **minsync_kwargs) -# TODO #3335: Multiple inheritance doesn't work with the decorator. -transformation_documentation_wrapper(OpenMPCPURoutineTrans, - inherit=[OMPLoopTrans, - MaximalOMPParallelRegionTrans, - OMPMinimiseSyncTrans]) +# For Sphinx AutoAPI documentation generation +__all__ = ["OMPCPURoutineTrans"]