diff --git a/src/psyclone/psyir/transformations/omp_parallel_trans.py b/src/psyclone/psyir/transformations/omp_parallel_trans.py index c6fe3843d7..6f8550aeb8 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_trans.py @@ -40,6 +40,7 @@ # J. Dendy, Met Office '''This module provides the OMPParallelTrans transformation.''' +from collections.abc import Iterable from psyclone import psyGen from psyclone.psyir.nodes import ( ACCDirective, @@ -48,6 +49,7 @@ OMPParallelDirective, OMPDirective, Return, + RegionDirective, ) from psyclone.psyir.transformations.parallel_region_trans import ( ParallelRegionTrans) @@ -130,7 +132,10 @@ def validate(self, nodes: list[Node], options=None, **kwargs): # TODO #2668: Remove options. super().validate(nodes, options, **kwargs) - def apply(self, nodes: list[Node], options=None, **kwargs): + def apply( + self, nodes: list[Node], + options=None, force_private: Iterable[str] = (), + **kwargs): ''' Surrounds the provided node list with an OpenMP Parallel region. @@ -139,5 +144,17 @@ def apply(self, nodes: list[Node], options=None, **kwargs): # TODO #2668: Remove options. super().apply(nodes, options, **kwargs) + # Privatise the provided variables for the new RegionDirective, if they + # are found within the symbol table of the ancestor Routine. + if force_private: + new_region_directive = nodes[0].ancestor(RegionDirective) + if new_region_directive: + region_set = super()._check_symbol_table_vars( + new_region_directive, + force_private) + if region_set: + new_region_directive.explicitly_private_symbols.update( + region_set) + __all__ = ["OMPParallelTrans"] diff --git a/src/psyclone/psyir/transformations/parallel_region_trans.py b/src/psyclone/psyir/transformations/parallel_region_trans.py index 48b78482e5..f688880d7c 100644 --- a/src/psyclone/psyir/transformations/parallel_region_trans.py +++ b/src/psyclone/psyir/transformations/parallel_region_trans.py @@ -43,13 +43,15 @@ This module provides the implementation of ParallelRegionTrans ''' - +import logging +from collections.abc import Iterable from abc import ABC, abstractmethod from psyclone.psyir.transformations.transformation_error import ( TransformationError) from psyclone import psyGen from psyclone.psyir.transformations.region_trans import RegionTrans -from psyclone.psyir.nodes import CodeBlock, Node, Return +from psyclone.psyir.nodes import CodeBlock, Node, Return, RegionDirective +from psyclone.psyir.symbols import DataSymbol from psyclone.utils import transformation_documentation_wrapper @@ -76,6 +78,36 @@ def __str__(self) -> str: ''' + def _check_symbol_table_vars( + self, + region_node: RegionDirective, + force_private: Iterable[str] = ()) -> set[DataSymbol]: + ''' + Check that the symbol table of the provided region node contains the + variable variables in the provided list. Return a set of DataSymbols. + + This is intended to be used as part of privatising the variables + contained in the list for the provided region in the child classes. + ''' + explicitly_private_symbols = set() + + for symbol_name in force_private: + sym = None + try: + sym = region_node.scope.symbol_table.lookup( + symbol_name.lower()) + except KeyError as err: + # This is not an error, but we will log the missed string + logger = logging.getLogger(__name__) + logger.warning( + "%s has been provided with the '%s' symbol name in " + "the 'force_private' option, but there is no such " + "symbol in this scope.", err, symbol_name) + if sym: + explicitly_private_symbols.add(sym) + + return explicitly_private_symbols + def validate(self, nodes: list[Node], options=None, **kwargs): # pylint: disable=arguments-renamed ''' diff --git a/src/psyclone/tests/psyir/transformations/maximal_region_trans_test.py b/src/psyclone/tests/psyir/transformations/maximal_region_trans_test.py index 9f719eec45..af3271434f 100644 --- a/src/psyclone/tests/psyir/transformations/maximal_region_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/maximal_region_trans_test.py @@ -42,12 +42,14 @@ Assignment, IfBlock, Routine, + Loop, OMPParallelDirective, ) from psyclone.psyir.transformations import ( MaximalRegionTrans, TransformationError, - OMPParallelTrans + OMPParallelTrans, + OMPLoopTrans, ) @@ -345,3 +347,43 @@ class OneParTrans(MaximalRegionTrans): assert isinstance(routine.children[0], OMPParallelDirective) assert isinstance(routine.children[1], Assignment) assert isinstance(routine.children[2], OMPParallelDirective) + + +def test_apply_force_private(fortran_reader): + '''Test the apply function of MaxParallelRegionTrans + with force privates.''' + code = """subroutine x + integer :: i, ii, k, l, block + integer :: array_l(8) + block = 2 + do ii = 1, 4 + do k = 4, 1, -1 + l = 0 + do i = ii, min(ii+block -1, 4) + l = l + 1 + array_l(l) = 1 + 2 + end do + end do + end do + end subroutine x + """ + psyir = fortran_reader.psyir_from_source(code) + # Apply loop_trans to all the loops possible. + ltrans = OMPLoopTrans(omp_schedule="static") + for loop in psyir.walk(Loop): + if loop.variable.name == "ii": + ltrans.apply( + loop, + ignore_dependencies_for=["array_l"], + nowait=True) + # Apply maximum transformation to code + mtrans = MaxParTrans() + routine = psyir.walk(Routine) + # Note, i, ii, k, l seem to be set as first private + mtrans.apply(routine, force_private=["i", "ii", "k", "l", "array_l"]) + # assertions + assert len(psyir.walk(OMPParallelDirective)) == 1 + nodes = psyir.walk(Routine)[0].children[:] + assert isinstance(nodes[0], OMPParallelDirective) is True + pdir = psyir.walk(OMPParallelDirective) + assert len(pdir[0].explicitly_private_symbols) == 5 diff --git a/src/psyclone/tests/psyir/transformations/parallel_region_trans_test.py b/src/psyclone/tests/psyir/transformations/parallel_region_trans_test.py index 6195bdce63..17a0cb6515 100644 --- a/src/psyclone/tests/psyir/transformations/parallel_region_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/parallel_region_trans_test.py @@ -44,11 +44,11 @@ ''' +import logging import pytest from psyclone.psyir.transformations.transformation_error import ( TransformationError) -from psyclone.psyir.nodes import CodeBlock -from psyclone.psyir.nodes import (Literal, Loop) +from psyclone.psyir.nodes import (CodeBlock, Literal, Loop) from psyclone.psyir.transformations import OMPParallelTrans from psyclone.psyir.symbols import (DataSymbol, INTEGER_TYPE) @@ -69,3 +69,32 @@ def test_parallelregion_refuse_codeblock(): otrans.validate([parent]) assert ("Nodes of type 'CodeBlock' cannot be enclosed by a " "OMPParallelTrans transformation" in str(err.value)) + + +def test_parallelregion_check_symtab_var(fortran_reader, caplog): + ''' + Check ParallelRegionTrans._check_symbol_table_vars try and except, + if the logging message produces a warning when a variable is not + in the routine scope.We use OMPParallelTrans as ParallelRegionTrans + is abstract. + ''' + otrans = OMPParallelTrans() + code = """subroutine test + integer :: i + do i = 1, 100 + + end do + end subroutine""" + psyir = fortran_reader.psyir_from_source(code) + otrans.apply(psyir.children[0].children[0]) + parallel = psyir.children[0].children[0] + caplog.clear() + with caplog.at_level(logging.WARNING, + logger="psyclone.psyir.transformations"): + otrans._check_symbol_table_vars(parallel, ("j")) + + long_string = \ + "\"Could not find 'j' in the Symbol Table.\" has been "\ + "provided with the 'j' symbol name in the 'force_private' option, "\ + "but there is no such symbol in this scope." + assert (long_string in caplog.text)