diff --git a/src/psyclone/domain/lfric/lfric_global_reduction.py b/src/psyclone/domain/lfric/lfric_global_reduction.py new file mode 100755 index 0000000000..1dd875b76a --- /dev/null +++ b/src/psyclone/domain/lfric/lfric_global_reduction.py @@ -0,0 +1,97 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2024, 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 R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified I. Kavcic, A. Coughtrie, L. Turner and O. Brunt, Met Office +# Modified J. Henrichs, Bureau of Meteorology +# Modified A. B. G. Chalk and N. Nobre, STFC Daresbury Lab + +''' This module contains the LFRic-specific Global Reduction +node implementation.''' + +from psyclone.core import AccessType +from psyclone.f2pygen import CommentGen +from psyclone.psyir.nodes.node import Node +from psyclone.psyir.nodes.statement import Statement + +class LFRicGlobalReduction(GlobalReduction): + ''' + LFRic-specific global reduction class which can be added to and + manipulated in a schedule. + + :param scalar: the kernel argument for which to perform a global reduction. + :type scalar: :py:class:`psyclone.dynamo0p3.DynKernelArgument` + :param parent: the parent node of this node in the PSyIR. + :type parent: :py:class:`psyclone.psyir.nodes.Node` + + :raises GenerationError: if distributed memory is not enabled. + :raises GenerationError: if the scalar is not of "real" intrinsic type. + + ''' + def __init__(self, scalar, parent=None): + # Check that distributed memory is enabled + if not Config.get().distributed_memory: + raise GenerationError( + "It makes no sense to create an LFRicGlobalReduction " + "object when distributed memory is not enabled (dm=False).") + # Check scalar intrinsic types that this class supports (only + # "real" for now) + if scalar.intrinsic_type != "real": + raise GenerationError( + f"LFRicGlobalReduction currently only supports real scalars, " + f"but argument '{scalar.name}' in Kernel '{scalar.call.name}'" + f" has '{scalar.intrinsic_type}' intrinsic type.") + # Initialise the parent class + super().__init__(scalar, parent=parent) + + def gen_code(self, parent): + ''' + LFRic-specific code generation for this class. + + :param parent: f2pygen node to which to add AST nodes. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + + ''' + name = self._scalar.name + # Use InvokeSchedule SymbolTable to share the same symbol for all + # GlobalSums in the Invoke. + sum_name = self.ancestor(InvokeSchedule).symbol_table.\ + find_or_create_tag("global_sum").name + sum_type = self._scalar.data_type + sum_mod = self._scalar.module_name + parent.add(UseGen(parent, name=sum_mod, only=True, + funcnames=[sum_type])) + parent.add(TypeDeclGen(parent, datatype=sum_type, + entity_decls=[sum_name])) + parent.add(AssignGen(parent, lhs=sum_name+"%value", rhs=name)) + parent.add(AssignGen(parent, lhs=name, rhs=sum_name+"%get_sum()")) \ No newline at end of file diff --git a/src/psyclone/domain/lfric/lfric_global_sum.py b/src/psyclone/domain/lfric/lfric_global_sum.py new file mode 100755 index 0000000000..f3408fff2b --- /dev/null +++ b/src/psyclone/domain/lfric/lfric_global_sum.py @@ -0,0 +1,82 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2024, 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 R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified I. Kavcic, A. Coughtrie, L. Turner and O. Brunt, Met Office +# Modified J. Henrichs, Bureau of Meteorology +# Modified A. B. G. Chalk and N. Nobre, STFC Daresbury Lab + +''' This module contains the LFRic-specific Global Reduction +node implementation.''' + +from psyclone.core import AccessType +from psyclone.f2pygen import CommentGen +from psyclone.psyir.nodes.node import Node +from psyclone.psyir.nodes.statement import Statement + +class LFRicGlobalSum(LFRicGlobalReduction): + ''' + LFRic-specific global sum class which can be added to and + manipulated in a schedule. + + :param scalar: the kernel argument for which to perform a global sum. + :type scalar: :py:class:`psyclone.dynamo0p3.DynKernelArgument` + :param parent: the parent node of this node in the PSyIR. + :type parent: :py:class:`psyclone.psyir.nodes.Node` + + ''' + def __init__(self, scalar, parent=None): + # Initialise the parent class + super().__init__(scalar, parent=parent) + + def gen_code(self, parent): + ''' + LFRic-specific code generation for this class. + + :param parent: f2pygen node to which to add AST nodes. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + + ''' + name = self._scalar.name + # Use InvokeSchedule SymbolTable to share the same symbol for all + # GlobalSums in the Invoke. + sum_name = self.ancestor(InvokeSchedule).symbol_table.\ + find_or_create_tag("global_sum").name + sum_type = self._scalar.data_type + sum_mod = self._scalar.module_name + parent.add(UseGen(parent, name=sum_mod, only=True, + funcnames=[sum_type])) + parent.add(TypeDeclGen(parent, datatype=sum_type, + entity_decls=[sum_name])) + parent.add(AssignGen(parent, lhs=sum_name+"%value", rhs=name)) + parent.add(AssignGen(parent, lhs=name, rhs=sum_name+"%get_sum()")) \ No newline at end of file diff --git a/src/psyclone/dynamo0p3.py b/src/psyclone/dynamo0p3.py index 98beda5b6a..48c623f1a4 100644 --- a/src/psyclone/dynamo0p3.py +++ b/src/psyclone/dynamo0p3.py @@ -67,10 +67,10 @@ from psyclone.parse.kernel import getkerneldescriptors from psyclone.parse.utils import ParseError from psyclone.psyGen import (PSy, InvokeSchedule, Arguments, - KernelArgument, HaloExchange, GlobalSum, - DataAccess) + KernelArgument, HaloExchange, DataAccess) from psyclone.psyir.frontend.fortran import FortranReader -from psyclone.psyir.nodes import Reference, ACCEnterDataDirective, ScopingNode +from psyclone.psyir.nodes import (ACCEnterDataDirective, GlobalReduction, + Reference, ScopingNode) from psyclone.psyir.symbols import (INTEGER_TYPE, DataSymbol, ScalarType, UnresolvedType, DataTypeSymbol, ContainerSymbol, ImportInterface, @@ -4505,65 +4505,6 @@ def node_str(self, colour=True): "', dm=" + str(Config.get().distributed_memory)+"]") -class DynGlobalSum(GlobalSum): - ''' - Dynamo specific global sum class which can be added to and - manipulated in a schedule. - - :param scalar: the kernel argument for which to perform a global sum. - :type scalar: :py:class:`psyclone.dynamo0p3.DynKernelArgument` - :param parent: the parent node of this node in the PSyIR. - :type parent: :py:class:`psyclone.psyir.nodes.Node` - - :raises GenerationError: if distributed memory is not enabled. - :raises InternalError: if the supplied argument is not a scalar. - :raises GenerationError: if the scalar is not of "real" intrinsic type. - - ''' - def __init__(self, scalar, parent=None): - # Check that distributed memory is enabled - if not Config.get().distributed_memory: - raise GenerationError( - "It makes no sense to create a DynGlobalSum object when " - "distributed memory is not enabled (dm=False).") - # Check that the global sum argument is indeed a scalar - if not scalar.is_scalar: - raise InternalError( - f"DynGlobalSum.init(): A global sum argument should be a " - f"scalar but found argument of type '{scalar.argument_type}'.") - # Check scalar intrinsic types that this class supports (only - # "real" for now) - if scalar.intrinsic_type != "real": - raise GenerationError( - f"DynGlobalSum currently only supports real scalars, but " - f"argument '{scalar.name}' in Kernel '{scalar.call.name}' has " - f"'{scalar.intrinsic_type}' intrinsic type.") - # Initialise the parent class - super().__init__(scalar, parent=parent) - - def gen_code(self, parent): - ''' - Dynamo-specific code generation for this class. - - :param parent: f2pygen node to which to add AST nodes. - :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` - - ''' - name = self._scalar.name - # Use InvokeSchedule SymbolTable to share the same symbol for all - # GlobalSums in the Invoke. - sum_name = self.ancestor(InvokeSchedule).symbol_table.\ - find_or_create_tag("global_sum").name - sum_type = self._scalar.data_type - sum_mod = self._scalar.module_name - parent.add(UseGen(parent, name=sum_mod, only=True, - funcnames=[sum_type])) - parent.add(TypeDeclGen(parent, datatype=sum_type, - entity_decls=[sum_name])) - parent.add(AssignGen(parent, lhs=sum_name+"%value", rhs=name)) - parent.add(AssignGen(parent, lhs=name, rhs=sum_name+"%get_sum()")) - - def _create_depth_list(halo_info_list, sym_table): '''Halo exchanges may have more than one dependency. This method simplifies multiple dependencies to remove duplicates and any diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index c54a078991..ef174afd88 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -48,12 +48,12 @@ from psyclone.configuration import Config from psyclone.core import AccessType from psyclone.errors import GenerationError, InternalError, FieldNotFoundError -from psyclone.f2pygen import (AllocateGen, AssignGen, CallGen, CommentGen, - DeclGen, DeallocateGen, DoGen, UseGen) +from psyclone.f2pygen import (AllocateGen, AssignGen, CallGen, DeclGen, + DeallocateGen, DoGen, UseGen) from psyclone.parse.algorithm import BuiltInCall from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.nodes import (ArrayReference, Call, Container, Literal, - Loop, Node, OMPDoDirective, Reference, +from psyclone.psyir.nodes import (ArrayReference, Call, Container, GlobalReduction, + Literal, Loop, Node, OMPDoDirective, Reference, Routine, Schedule, Statement) from psyclone.psyir.symbols import (ArgumentInterface, ArrayType, ContainerSymbol, DataSymbol, @@ -113,18 +113,6 @@ def get_api(api): return api -def zero_reduction_variables(red_call_list, parent): - '''zero all reduction variables associated with the calls in the call - list''' - if red_call_list: - parent.add(CommentGen(parent, "")) - parent.add(CommentGen(parent, " Zero summation variables")) - parent.add(CommentGen(parent, "")) - for call in red_call_list: - call.zero_reduction_variable(parent) - parent.add(CommentGen(parent, "")) - - def args_filter(arg_list, arg_types=None, arg_accesses=None, arg_meshes=None, include_literals=True): ''' @@ -633,11 +621,11 @@ def unique_declns_by_intent(self, argument_types, intrinsic_type=None): if first_arg.access in [AccessType.WRITE, AccessType.SUM]: # If the first access is a write then the intent is # out irrespective of any other accesses. Note, - # sum_args behave as if they are write_args from the + # reduction args behave as if they are write args from the # PSy-layer's perspective. declns["out"].append(arg) continue - # if all accesses are read, then the intent is in, + # If all accesses are read, then the intent is in, # otherwise the intent is inout (as we have already # dealt with intent out). read_only = True @@ -795,65 +783,6 @@ def gen_code(self, parent): entity.gen_code(parent) -class GlobalSum(Statement): - ''' - Generic Global Sum class which can be added to and manipulated - in, a schedule. - - :param scalar: the scalar that the global sum is stored into - :type scalar: :py:class:`psyclone.dynamo0p3.DynKernelArgument` - :param parent: optional parent (default None) of this object - :type parent: :py:class:`psyclone.psyir.nodes.Node` - - ''' - # Textual description of the node. - _children_valid_format = "" - _text_name = "GlobalSum" - _colour = "cyan" - - def __init__(self, scalar, parent=None): - Node.__init__(self, children=[], parent=parent) - import copy - self._scalar = copy.copy(scalar) - if scalar: - # Update scalar values appropriately - # Here "readwrite" denotes how the class GlobalSum - # accesses/updates a scalar - self._scalar.access = AccessType.READWRITE - self._scalar.call = self - - @property - def scalar(self): - ''' Return the scalar field that this global sum acts on ''' - return self._scalar - - @property - def dag_name(self): - ''' - :returns: the name to use in the DAG for this node. - :rtype: str - ''' - return f"globalsum({self._scalar.name})_{self.position}" - - @property - def args(self): - ''' Return the list of arguments associated with this node. Override - the base method and simply return our argument.''' - return [self._scalar] - - def node_str(self, colour=True): - ''' - Returns a text description of this node with (optional) control codes - to generate coloured output in a terminal that supports it. - - :param bool colour: whether or not to include colour control codes. - - :returns: description of this node, possibly coloured. - :rtype: str - ''' - return f"{self.coloured_name(colour)}[scalar='{self._scalar.name}']" - - class HaloExchange(Statement): ''' Generic Halo Exchange class which can be added to and @@ -1013,7 +942,8 @@ def node_str(self, colour=True): class Kern(Statement): - '''Base class representing a call to a sub-program unit from within the + ''' + Base class representing a call to a sub-program unit from within the PSy layer. It is possible for this unit to be in-lined within the PSy layer. @@ -1078,24 +1008,31 @@ def __init__(self, parent, call, name, ArgumentsClass, check=True): @property def args(self): - '''Return the list of arguments associated with this node. Overide the - base method and simply return our arguments. ''' + ''' + :returns: the list of arguments associated with this node. + Overide the base method and simply return our arguments. + :rtype: list of :py:class:`psyclone.psyGen.KernelArgument` + + ''' return self.arguments.args def node_str(self, colour=True): - ''' Returns the name of this node with (optional) control codes + ''' + Returns the name of this node with (optional) control codes to generate coloured output in a terminal that supports it. :param bool colour: whether or not to include colour control codes. :returns: description of this node, possibly coloured. :rtype: str + ''' return (self.coloured_name(colour) + " " + self.name + "(" + self.arguments.names + ")") def reference_accesses(self, var_accesses): - '''Get all variable access information. The API specific classes + ''' + Get all variable access information. The API specific classes add the accesses to the arguments. So the code here only calls the baseclass, and increases the location. @@ -1103,6 +1040,7 @@ def reference_accesses(self, var_accesses): information about variable accesses. :type var_accesses: \ :py:class:`psyclone.core.VariablesAccessInfo` + ''' super().reference_accesses(var_accesses) var_accesses.next_location() @@ -1155,7 +1093,7 @@ def local_reduction_name(self): # with the PSy-layer generation or relevant transformation. return "l_" + self.reduction_arg.name - def zero_reduction_variable(self, parent, position=None): + def initialise_reduction_variable(self, parent, position=None): ''' Generate code to zero the reduction variable and to zero the local reduction variable if one exists. The latter is used for reproducible @@ -1172,6 +1110,11 @@ def zero_reduction_variable(self, parent, position=None): neither 'real' nor 'integer'. ''' + # TODO #2381: It makes sense to zero the global sum variable, + # however not the global min and global max variables. Depending + # on the implementation of global min and max, this function may + # need to be renamed and separate initialisation methods for min + # and max added. if not position: position = ["auto"] var_name = self._reduction_arg.name @@ -1180,7 +1123,7 @@ def zero_reduction_variable(self, parent, position=None): # Check for a non-scalar argument if not var_arg.is_scalar: raise GenerationError( - f"Kern.zero_reduction_variable() should be a scalar but " + f"Kern.initialise_reduction_variable() should be a scalar but " f"found '{var_arg.argument_type}'.") # Generate the reduction variable var_data_type = var_arg.intrinsic_type @@ -1190,7 +1133,7 @@ def zero_reduction_variable(self, parent, position=None): data_value = "0" else: raise GenerationError( - f"Kern.zero_reduction_variable() should be either a 'real' or " + f"Kern.initialise_reduction_variable() should be either a 'real' or " f"an 'integer' scalar but found scalar of type " f"'{var_arg.intrinsic_type}'.") # Retrieve the precision information (if set) and append it @@ -1299,14 +1242,33 @@ def _reduction_reference(self): @property def arg_descriptors(self): + ''' + :returns: a list of kernel argument descriptors. + :rtype: list of API-specific specialisation of + :py:class:`psyclone.kernel.Descriptor` + + ''' return self._arg_descriptors @arg_descriptors.setter def arg_descriptors(self, obj): + ''' + Set the argument descriptors for this kernel. + + :param obj: Argument descriptors populated from the kernel metadata. + :type obj: list of API-specific specialisation of + :py:class:`psyclone.kernel.Descriptor` + + ''' self._arg_descriptors = obj @property def arguments(self): + ''' + :returns: object that holds all information on the kernel arguments. + :rtype: :py:class:`psyclone.psyGen.Arguments` + + ''' return self._arguments @property @@ -1314,6 +1276,7 @@ def name(self): ''' :returns: the name of the kernel. :rtype: str + ''' return self._name @@ -1323,14 +1286,16 @@ def name(self, value): Set the name of the kernel. :param str value: The name of the kernel. + ''' self._name = value def is_coloured(self): ''' - :returns: True if this kernel is being called from within a \ + :returns: True if this kernel is being called from within a coloured loop. :rtype: bool + ''' parent_loop = self.ancestor(Loop) while parent_loop: @@ -1341,6 +1306,11 @@ def is_coloured(self): @property def iterates_over(self): + ''' + :returns: the name of the iteration space supported by this kernel. + :rtype: str + + ''' return self._iterates_over def local_vars(self): @@ -2027,22 +1997,23 @@ class DataAccess(): ''' def __init__(self, arg): - '''Store the argument associated with the instance of this class and - the Call, HaloExchange or GlobalSum (or a subclass thereof) + ''' + Store the argument associated with the instance of this class and + the Call, HaloExchange or GlobalReduction (or a subclass thereof) instance with which the argument is associated. - :param arg: the argument that we are concerned with. An \ - argument can be found in a `Kern` a `HaloExchange` or a \ - `GlobalSum` (or a subclass thereof) + :param arg: the argument that we are concerned with. An argument + can be found in a `Kern` a `HaloExchange` or a + `GlobalReduction` (or a subclass thereof). :type arg: :py:class:`psyclone.psyGen.Argument` ''' # the `psyclone.psyGen.Argument` we are concerned with self._arg = arg - # The call (Kern, HaloExchange, GlobalSum or subclass) + # The call (Kern, HaloExchange, GlobalReduction or subclass) # instance with which the argument is associated self._call = arg.call - # initialise _covered and _vector_index_access to keep pylint + # Initialise _covered and _vector_index_access to keep pylint # happy self._covered = None self._vector_index_access = None @@ -2179,12 +2150,12 @@ class Argument(): :param call: the kernel call that this argument is associated with. :type call: :py:class:`psyclone.psyGen.Kern` - :param arg_info: Information about this argument collected by \ + :param arg_info: information about this argument collected by the parser. :type arg_info: :py:class:`psyclone.parse.algorithm.Arg` - :param access: the way in which this argument is accessed in \ - the 'Kern'. Valid values are specified in the config \ - object of the current API. + :param access: the way in which this argument is accessed in + the `Kern`. Valid values are specified in the + configuration object of the current API. :type access: str ''' @@ -2214,20 +2185,21 @@ def __init__(self, call, arg_info, access): self._name = self._orig_name def _complete_init(self, arg_info): - '''Provides the initialisation of name, text and the declaration of + ''' + Provides the initialisation of name, text and the declaration of symbols in the symbol table if required. This initialisation is not performed in the constructor as subclasses may need to - perform additional initialisation before infer_datatype is - called (in order to determine the values of precision, - data_type and module_name). + perform additional initialisation before 'infer_datatype' is + called (in order to determine the values of 'precision', + 'data_type' and 'module_name'). - :param arg_info: Information about this argument collected by \ - the parser. + :param arg_info: information about this argument collected by + the parser. :type arg_info: :py:class:`psyclone.parse.algorithm.Arg` ''' if self._orig_name is None: - # this is an infrastructure call literal argument. Therefore + # This is an infrastructure call literal argument. Therefore # we do not want an argument (_text=None) but we do want to # keep the value (_name) self._name = arg_info.text @@ -2278,7 +2250,8 @@ def psyir_expression(self): ''' def infer_datatype(self): - ''' Infer the datatype of this argument using the API rules. If no + ''' + Infer the datatype of this argument using the API rules. If no specialisation of this method has been provided make the type UnresolvedType for now (it may be provided later in the execution). @@ -2289,34 +2262,65 @@ def infer_datatype(self): return UnresolvedType() def __str__(self): + ''' + :returns: ??. + :rtype: str + + ''' return self._name @property def name(self): + ''' + :returns: the name of this argument. + :rtype: str + + ''' return self._name @property def text(self): + ''' + :returns: ???. + :rtype: str + + ''' return self._text @property def form(self): + ''' + :returns: ???. + :rtype: str + + ''' return self._form @property def is_literal(self): + ''' + :returns: whether this argument is a Literal constant. + :rtype: bool + + ''' return self._is_literal @property def access(self): + ''' + :returns: the access type for this argument. + :rtype: :py:class:`psyclone.core.access_type.AccessType` + + ''' return self._access @access.setter def access(self, value): - '''Set the access type for this argument. + ''' + Set the access type for this argument. :param value: new access type. - :type value: :py:class:`psyclone.core.access_type.AccessType`. + :type value: :py:class:`psyclone.core.access_type.AccessType` :raises InternalError: if value is not an AccessType. @@ -2355,9 +2359,9 @@ def intrinsic_type(self): @property def precision(self): ''' - :returns: the precision of this argument. Default value is None, \ + :returns: the precision of this argument. Default value is `None`, explicit implementation is left to a specific API. - :rtype: str or NoneType + :rtype: str or `NoneType` ''' return self._precision @@ -2365,9 +2369,9 @@ def precision(self): @property def data_type(self): ''' - :returns: the data type of this argument. Default value is None, \ + :returns: the data type of this argument. Default value is `None`, explicit implementation is left to a specific API. - :rtype: str or NoneType + :rtype: str or `NoneType` ''' return self._data_type @@ -2375,32 +2379,43 @@ def data_type(self): @property def module_name(self): ''' - :returns: the name of the Fortran module that contains definitions \ - for the argument data type. Default value is None, \ + :returns: the name of the Fortran module that contains definitions + for the argument data type. Default value is `None`, explicit implementation is left to a specific API. - :rtype: str or NoneType - + :rtype: str or `NoneType` ''' return self._module_name @property def call(self): - ''' Return the call that this argument is associated with ''' + ''' + + :returns: the call that this argument is associated with. + :rtype: :py:class:`psyclone.psyir.nodes.Node` + + ''' return self._call @call.setter def call(self, value): - ''' set the node that this argument is associated with ''' + ''' + Set the node that this argument is associated with. + + :param value: the node this argument belongs to. + :type value: :py:class:`psyclone.psyir.nodes.Node` + + ''' self._call = value def backward_dependence(self): - '''Returns the preceding argument that this argument has a direct - dependence with, or None if there is not one. The argument may - exist in a call, a haloexchange, or a globalsum. + ''' + Returns the preceding argument that this argument has a direct + dependence with, or `None` if there is not one. The argument may + exist in a call, a halo exchange, or a global reduction. - :returns: the first preceding argument that has a dependence \ - on this argument. + :returns: the first preceding argument that has a dependence + on this argument. :rtype: :py:class:`psyclone.psyGen.Argument` ''' @@ -2408,17 +2423,20 @@ def backward_dependence(self): return self._find_argument(nodes) def forward_write_dependencies(self, ignore_halos=False): - '''Returns a list of following write arguments that this argument has - dependencies with. The arguments may exist in a call, a - haloexchange (unless `ignore_halos` is `True`), or a globalsum. If - none are found then return an empty list. If self is not a - reader then return an empty list. + ''' + Returns a list of following write arguments that this argument + has dependencies with. The arguments may exist in a call, a + halo exchange (unless `ignore_halos` is `True`), or a global + reduction. If none are found then return an empty list. If self + is not a reader then return an empty list. - :param bool ignore_halos: if `True` then any write dependencies \ - involving a halo exchange are ignored. Defaults to `False`. + :param ignore_halos: if `True` then any write dependencies + involving a halo exchange are ignored. + Defaults to `False`. + :type ignore_halos: bool - :returns: a list of arguments that have a following write \ - dependence on this argument. + :returns: a list of arguments that have a following write + dependence on this argument. :rtype: list of :py:class:`psyclone.psyGen.Argument` ''' @@ -2427,18 +2445,20 @@ def forward_write_dependencies(self, ignore_halos=False): return results def backward_write_dependencies(self, ignore_halos=False): - '''Returns a list of previous write arguments that this argument has - dependencies with. The arguments may exist in a call, a - haloexchange (unless `ignore_halos` is `True`), or a globalsum. If - none are found then return an empty list. If self is not a - reader then return an empty list. - - :param ignore_halos: if `True` then any write dependencies \ - involving a halo exchange are ignored. Defaults to `False`. + ''' + Returns a list of previous write arguments that this argument + has dependencies with. The arguments may exist in a call, a + halo exchange (unless `ignore_halos` is `True`), or a global + reduction. If none are found then return an empty list. If self + is not a reader then return an empty list. + + :param ignore_halos: if `True` then any write dependencies + involving a halo exchange are ignored. + Defaults to `False`. :type ignore_halos: bool - :returns: a list of arguments that have a preceding write \ - dependence on this argument. + :returns: a list of arguments that have a preceding write + dependence on this argument. :rtype: list of :py:class:`psyclone.psyGen.Argument` ''' @@ -2447,12 +2467,13 @@ def backward_write_dependencies(self, ignore_halos=False): return results def forward_dependence(self): - '''Returns the following argument that this argument has a direct + ''' + Returns the following argument that this argument has a direct dependence on, or `None` if there is not one. The argument may - exist in a call, a haloexchange, or a globalsum. + exist in a call, a halo exchange, or a global reduction. - :returns: the first following argument that has a dependence \ - on this argument. + :returns: the first following argument that has a dependence + on this argument. :rtype: :py:class:`psyclone.psyGen.Argument` ''' @@ -2460,14 +2481,15 @@ def forward_dependence(self): return self._find_argument(nodes) def forward_read_dependencies(self): - '''Returns a list of following read arguments that this argument has - dependencies with. The arguments may exist in a call, a - haloexchange, or a globalsum. If none are found then + ''' + Returns a list of following read arguments that this argument + has dependencies with. The arguments may exist in a call, a + halo exchange, or a global reduction. If none are found then return an empty list. If self is not a writer then return an empty list. - :returns: a list of following arguments that have a read \ - dependence on this argument. + :returns: a list of following arguments that have a read + dependence on this argument. :rtype: list of :py:class:`psyclone.psyGen.Argument` ''' @@ -2475,18 +2497,20 @@ def forward_read_dependencies(self): return self._find_read_arguments(nodes) def _find_argument(self, nodes): - '''Return the first argument in the list of nodes that has a - dependency with self. If one is not found return None + ''' + Returns the first argument in the list of nodes that has + a dependency with self. If one is not found return `None`. :param nodes: the list of nodes that this method examines. :type nodes: list of :py:class:`psyclone.psyir.nodes.Node` - :returns: An argument object or None. - :rtype: :py:class:`psyclone.psyGen.Argument` + :returns: An argument object or `None`. + :rtype: :py:class:`psyclone.psyGen.Argument` or `NoneType` ''' - nodes_with_args = [x for x in nodes if - isinstance(x, (Kern, HaloExchange, GlobalSum))] + nodes_with_args = [ + x for x in nodes if + isinstance(x, (Kern, HaloExchange, GlobalReduction))] for node in nodes_with_args: for argument in node.args: if self._depends_on(argument): @@ -2494,15 +2518,16 @@ def _find_argument(self, nodes): return None def _find_read_arguments(self, nodes): - '''Return a list of arguments from the list of nodes that have a read + ''' + Return a list of arguments from the list of nodes that have a read dependency with self. If none are found then return an empty list. If self is not a writer then return an empty list. :param nodes: the list of nodes that this method examines. :type nodes: list of :py:class:`psyclone.psyir.nodes.Node` - :returns: a list of arguments that have a read dependence on \ - this argument. + :returns: a list of arguments that have a read dependence on + this argument. :rtype: list of :py:class:`psyclone.psyGen.Argument` ''' @@ -2511,13 +2536,14 @@ def _find_read_arguments(self, nodes): return [] # We only need consider nodes that have arguments - nodes_with_args = [x for x in nodes if - isinstance(x, (Kern, HaloExchange, GlobalSum))] + nodes_with_args = [ + x for x in nodes if + isinstance(x, (Kern, HaloExchange, GlobalReduction))] access = DataAccess(self) arguments = [] for node in nodes_with_args: for argument in node.args: - # look at all arguments in our nodes + # Look at all arguments in our nodes if argument.access in AccessType.all_read_accesses() and \ access.overlaps(argument): arguments.append(argument) @@ -2528,24 +2554,30 @@ def _find_read_arguments(self, nodes): # this argument depends so return the list. return arguments - # we did not find a terminating write dependence in the list + # We did not find a terminating write dependence in the list # of nodes so we return any read dependencies that were found return arguments def _find_write_arguments(self, nodes, ignore_halos=False): - '''Return a list of arguments from the list of nodes that have a write + ''' + Return a list of arguments from the list of nodes that have a write dependency with self. If none are found then return an empty list. If self is not a reader then return an empty list. :param nodes: the list of nodes that this method examines. :type nodes: list of :py:class:`psyclone.psyir.nodes.Node` - :param bool ignore_halos: if `True` then any write dependencies \ - involving a halo exchange are ignored. Defaults to `False`. - :returns: a list of arguments that have a write dependence with \ - this argument. + :param bool ignore_halos: if `True` then any write dependencies + involving a halo exchange are ignored. + Defaults to `False`. + + :returns: a list of arguments that have a write dependence with + this argument. :rtype: list of :py:class:`psyclone.psyGen.Argument` + :raises InternalError: there are dependencies that are not + associated with nodes. + ''' if self.access not in AccessType.all_read_accesses(): # I am not a reader so there will be no write dependencies @@ -2553,15 +2585,15 @@ def _find_write_arguments(self, nodes, ignore_halos=False): # We only need consider nodes that have arguments nodes_with_args = [x for x in nodes if - isinstance(x, (Kern, GlobalSum)) or + isinstance(x, (Kern, GlobalReduction)) or (isinstance(x, HaloExchange) and not ignore_halos)] access = DataAccess(self) arguments = [] for node in nodes_with_args: for argument in node.args: - # look at all arguments in our nodes + # Look at all arguments in our nodes if argument.access not in AccessType.all_write_accesses(): - # no dependence if not a writer + # No dependence if not a writer continue if not access.overlaps(argument): # Accesses are independent of each other @@ -2569,7 +2601,7 @@ def _find_write_arguments(self, nodes, ignore_halos=False): arguments.append(argument) access.update_coverage(argument) if access.covered: - # sanity check + # Sanity check if not isinstance(node, HaloExchange) and \ len(arguments) > 1: raise InternalError( @@ -2582,12 +2614,13 @@ def _find_write_arguments(self, nodes, ignore_halos=False): raise InternalError( "Argument()._field_write_arguments() There are no more nodes " "but there are already dependencies. This should not happen.") - # no dependencies have been found + # No dependencies have been found return [] def _depends_on(self, argument): - '''If there is a dependency between the argument and self then return - True, otherwise return False. We consider there to be a + ''' + If there is a dependency between the argument and self then return + `True`, otherwise return `False`. We consider there to be a dependency between two arguments if the names are the same and if one reads and one writes, or if both write. Dependencies are often defined as being read-after-write (RAW), @@ -2595,7 +2628,7 @@ def _depends_on(self, argument): dependencies can be considered to be forward dependencies, in the sense that RAW means that the read is after the write in the schedule. Similarly for WAR and WAW. We capture these - dependencies in this method. However we also capture + dependencies in this method. However, we also capture dependencies in the opposite direction (backward dependencies). These are the same dependencies as forward dependencies but are reversed. One could consider these to be @@ -2607,18 +2640,18 @@ def _depends_on(self, argument): loop iteration and a backward dependence indicates a dependence on a previous loop iteration. Note, we currently assume that any read or write to an argument results in a - dependence i.e. we do not consider the internal structure of + dependence, i.e. we do not consider the internal structure of the argument (e.g. it may be an array). However, this assumption is OK as all elements of an array are typically accessed. However, we may need to revisit this when we change - the iteration spaces of loops e.g. for overlapping + the iteration spaces of loops, e.g. for overlapping communication and computation. - :param argument: the argument we will check to see whether \ - there is a dependence on this argument instance (self). + :param argument: the argument we will check to see whether there + is a dependence on this argument instance (self). :type argument: :py:class:`psyclone.psyGen.Argument` - :returns: True if there is a dependence and False if not. + :returns: `True` if there is a dependence and `False` if not. :rtype: bool ''' @@ -2872,7 +2905,21 @@ def apply(self, node, options=None): # For Sphinx AutoAPI documentation generation -__all__ = ['PSyFactory', 'PSy', 'Invokes', 'Invoke', 'InvokeSchedule', - 'GlobalSum', 'HaloExchange', 'Kern', 'CodedKern', 'InlinedKern', - 'BuiltIn', 'Arguments', 'DataAccess', 'Argument', 'KernelArgument', - 'TransInfo', 'Transformation', 'DummyTransformation'] +__all__ = [ + 'PSyFactory', + 'PSy', + 'Invokes', + 'Invoke', + 'InvokeSchedule', + 'HaloExchange', + 'Kern', + 'CodedKern', + 'InlinedKern', + 'BuiltIn', + 'Arguments', + 'DataAccess', + 'Argument', + 'KernelArgument', + 'TransInfo', + 'Transformation', + 'DummyTransformation'] diff --git a/src/psyclone/psyir/nodes/__init__.py b/src/psyclone/psyir/nodes/__init__.py index 6390758dcb..8c505515c6 100644 --- a/src/psyclone/psyir/nodes/__init__.py +++ b/src/psyclone/psyir/nodes/__init__.py @@ -35,6 +35,7 @@ # Modified: A. R. Porter, R. W. Ford and N. Nobre, STFC Daresbury Lab # Modified: J. Henrichs, Bureau of Meteorology # Modified: A. B. G. Chalk, STFC Daresbury Lab +# Modified: I. Kavcic, Met Office # ----------------------------------------------------------------------------- ''' PSyIR nodes package module ''' @@ -62,6 +63,7 @@ from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.loop import Loop from psyclone.psyir.nodes.extract_node import ExtractNode +from psyclone.psyir.nodes.global_reduction import GlobalReduction from psyclone.psyir.nodes.kernel_schedule import KernelSchedule from psyclone.psyir.nodes.member import Member from psyclone.psyir.nodes.nan_test_node import NanTestNode @@ -117,6 +119,7 @@ 'Container', 'DataNode', 'FileContainer', + 'GlobalReduction', 'IfBlock', 'IntrinsicCall', 'Literal', diff --git a/src/psyclone/psyir/nodes/global_reduction.py b/src/psyclone/psyir/nodes/global_reduction.py new file mode 100644 index 0000000000..5f79400f6d --- /dev/null +++ b/src/psyclone/psyir/nodes/global_reduction.py @@ -0,0 +1,165 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2024, 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 R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# I. Kavcic, Met Office +# J. Henrichs, Bureau of Meteorology +# ----------------------------------------------------------------------------- + +''' This module contains the Global Reduction node implementation.''' + +from psyclone.core import AccessType +from psyclone.f2pygen import CommentGen +from psyclone.psyir.nodes.node import Node +from psyclone.psyir.nodes.statement import Statement + + +class GlobalReduction(Statement): + ''' + Generic Global Reduction class which can be added to and manipulated + in a schedule. + + :param scalar: the scalar that the global reduction is stored into. + :type scalar: :py:class:`psyclone.psyGen.KernelArgument` + :param parent: optional parent (default None) of this object. + :type parent: :py:class:`psyclone.psyir.nodes.Node` + + :raises InternalError: if the supplied argument is not a scalar. + + ''' + # Textual description of the node. + _children_valid_format = "" + _text_name = "GlobalReduction" + _colour = "cyan" + + def __init__(self, scalar, parent=None): + print("init GlobalReduction\n", scalar, "\n", parent) + print("\npar parent ", parent.parent, parent.ancestor) + Node.__init__(self, children=[], parent=parent) + import copy + self._scalar = copy.copy(scalar) + # Check that the global sum argument is indeed a scalar + if not scalar.is_scalar: + raise InternalError( + f"GlobalReduction.init(): A global reduction argument " + f"should be a scalar but found argument of type " + f"'{scalar.argument_type}'.") + if scalar: + # Update scalar values appropriately + # Here "readwrite" denotes how the class GlobalReduction + # accesses/updates a scalar + self._scalar.access = AccessType.READWRITE + self._scalar.call = self + + @property + def scalar(self): + ''' + :returns: the scalar that this class reduces values + of a field or array to. + :rtype: str + + ''' + return self._scalar + + @property + def dag_name(self): + ''' + :returns: the name to use in the DAG for this node. + :rtype: str + + ''' + return f"globalreduction({self._scalar.name})_{self.position}" + + @property + def args(self): + ''' + Return the list of arguments associated with this node. Override + the base method and simply return our argument. + + :returns: the list of scalar reduction arguments. + :rtype: list of :py:class:`psyclone.psyGen.KernelArgument` + + ''' + return [self._scalar] + + def node_str(self, colour=True): + ''' + Returns a text description of this node with (optional) control codes + to generate coloured output in a terminal that supports it. + + :param bool colour: whether or not to include colour control codes. + + :returns: description of this node, possibly coloured. + :rtype: str + + ''' + return f"{self.coloured_name(colour)}[scalar='{self._scalar.name}']" + + @staticmethod + def initialise_reduction_variables(red_call_list, parent): + ''' + Initialise all reduction variables associated with the calls in the call + list. + :param red_call_list: list of kernel calls that contain + a reduction variable. + :type red_call_list: list of `psyGen.Kern` + :param parent: the node in the f2pygen AST to which to add content. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + ''' + # TODO #2381: It is not clear where this function belongs. For now + # it is used in 'Loop.gen_code' and 'OMPParallelDoDirective.gen_code + # methods. Also, it does not make sense to zero global min and max + # variables before calculating them so this function may be renamed. + print("initialise_reduction_variables\n", red_call_list, "\n", parent) + print("\npar parent", parent.parent) + if red_call_list: + parent.add(CommentGen(parent, "")) + parent.add(CommentGen(parent, " Initialise reduction variables")) + parent.add(CommentGen(parent, "")) + for call in red_call_list: + call.initialise_reduction_variable(parent) + parent.add(CommentGen(parent, "")) + + @abc.abstractmethod + def gen_code(self, parent): + ''' + Generates global reduction code. This consists of declarations + and arguments for the relevant PSy-layer invocation. + + :param parent: f2pygen node to which to add AST nodes. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + + ''' + +# For AutoAPI documentation generation +__all__ = ['GlobalReduction'] diff --git a/src/psyclone/psyir/nodes/loop.py b/src/psyclone/psyir/nodes/loop.py index 7e4bd8fdff..280053717e 100644 --- a/src/psyclone/psyir/nodes/loop.py +++ b/src/psyclone/psyir/nodes/loop.py @@ -39,10 +39,11 @@ ''' This module contains the Loop node implementation.''' +from psyclone.psyir.nodes.global_reduction import GlobalReduction from psyclone.psyir.nodes.datanode import DataNode from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.nodes.routine import Routine -from psyclone.psyir.nodes import Schedule, Literal +from psyclone.psyir.nodes import Literal, Schedule from psyclone.psyir.symbols import ScalarType, DataSymbol from psyclone.core import AccessType, Signature from psyclone.errors import InternalError, GenerationError @@ -433,10 +434,6 @@ def gen_code(self, parent): :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` ''' - # Avoid circular dependency - # pylint: disable=import-outside-toplevel - from psyclone.psyGen import zero_reduction_variables - def is_unit_literal(expr): ''' Check if the given expression is equal to the literal '1'. @@ -449,7 +446,7 @@ def is_unit_literal(expr): if not self.is_openmp_parallel(): calls = self.reductions() - zero_reduction_variables(calls, parent) + GlobalReduction.initialise_reduction_variables(calls, parent) # Avoid circular dependency # pylint: disable=import-outside-toplevel @@ -466,7 +463,7 @@ def is_unit_literal(expr): fwriter(self.start_expr), fwriter(self.stop_expr), step_str) - # need to add do loop before children as children may want to add + # Need to add do loop before children as children may want to add # info outside of do loop parent.add(do_stmt) for child in self.loop_body: diff --git a/src/psyclone/psyir/nodes/omp_directives.py b/src/psyclone/psyir/nodes/omp_directives.py index 72f7ba58fc..6e492a32b3 100644 --- a/src/psyclone/psyir/nodes/omp_directives.py +++ b/src/psyclone/psyir/nodes/omp_directives.py @@ -58,6 +58,7 @@ from psyclone.psyir.nodes.call import Call from psyclone.psyir.nodes.directive import StandaloneDirective, \ RegionDirective +from psyclone.psyir.nodes.global_reduction import GlobalReduction from psyclone.psyir.nodes.if_block import IfBlock from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall from psyclone.psyir.nodes.literal import Literal @@ -1344,9 +1345,6 @@ def gen_code(self, parent): implemented yet. ''' - # pylint: disable=import-outside-toplevel - from psyclone.psyGen import zero_reduction_variables - # We're not doing nested parallelism so make sure that this # omp parallel region is not already within some parallel region self.validate_global_constraints() @@ -1370,7 +1368,7 @@ def gen_code(self, parent): reprod_red_call_list = self.reductions(reprod=True) if reprod_red_call_list: - # we will use a private thread index variable + # We will use a private thread index variable thread_idx = self.scope.symbol_table.\ lookup_with_tag("omp_thread_index") private_clause.addchild(Reference(thread_idx)) @@ -1381,7 +1379,7 @@ def gen_code(self, parent): calls = self.reductions() - # first check whether we have more than one reduction with the same + # First check whether we have more than one reduction with the same # name in this Schedule. If so, raise an error as this is not # supported for a parallel region. names = [] @@ -1394,7 +1392,7 @@ def gen_code(self, parent): f"reduction variable") names.append(name) - zero_reduction_variables(calls, parent) + GlobalReduction.initialise_reduction_variables(calls, parent) # pylint: disable=protected-access clauses_str = self.default_clause._clause_string @@ -1410,7 +1408,7 @@ def gen_code(self, parent): f"{clauses_str}")) if reprod_red_call_list: - # add in a local thread index + # Add in a local thread index parent.add(UseGen(parent, name="omp_lib", only=True, funcnames=["omp_get_thread_num"])) parent.add(AssignGen(parent, lhs=thread_idx, @@ -2237,11 +2235,11 @@ def gen_code(self, parent): # We're not doing nested parallelism so make sure that this # omp parallel do is not already within some parallel region # pylint: disable=import-outside-toplevel - from psyclone.psyGen import zero_reduction_variables + from psyclone.psyGen import initialise_reduction_variables self.validate_global_constraints() calls = self.reductions() - zero_reduction_variables(calls, parent) + initialise_reduction_variables(calls, parent) # Set default() private() and firstprivate() clauses # pylint: disable=protected-access diff --git a/src/psyclone/psyir/transformations/extract_trans.py b/src/psyclone/psyir/transformations/extract_trans.py index 5ec329709d..89e9f01a66 100644 --- a/src/psyclone/psyir/transformations/extract_trans.py +++ b/src/psyclone/psyir/transformations/extract_trans.py @@ -40,10 +40,10 @@ ''' from psyclone.configuration import Config -from psyclone.psyGen import BuiltIn, Kern, HaloExchange, GlobalSum +from psyclone.psyGen import BuiltIn, Kern, HaloExchange from psyclone.psyir.nodes import (CodeBlock, ExtractNode, Loop, Schedule, Directive, OMPParallelDirective, - ACCParallelDirective) + ACCParallelDirective, GlobalReduction) from psyclone.psyir.transformations.psy_data_trans import PSyDataTrans from psyclone.psyir.transformations.transformation_error \ import TransformationError @@ -73,7 +73,7 @@ class ExtractTrans(PSyDataTrans): ''' # The types of node that this transformation cannot enclose excluded_node_types = (CodeBlock, ExtractNode, - HaloExchange, GlobalSum) + HaloExchange, GlobalReduction) def __init__(self, node_class=ExtractNode): # This function is required to provide the appropriate default @@ -162,7 +162,7 @@ def validate(self, node_list, options=None): # Extracting distributed memory code is not supported due to # generation of infrastructure calls to set halos dirty or clean. # This constraint covers the presence of HaloExchange and - # GlobalSum classes as they are only generated when distributed + # GlobalReduction classes as they are only generated when distributed # memory is enabled. But in case of the Nemo API, we don't even # support distributed memory, so ignore the setting of distributed # memory in this case: diff --git a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py index 814472d4ab..ef6e23e68a 100644 --- a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py +++ b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py @@ -51,9 +51,10 @@ from psyclone.dynamo0p3 import (LFRicHaloExchangeStart, LFRicHaloExchangeEnd, LFRicHaloExchange) from psyclone.errors import GenerationError, InternalError -from psyclone.psyGen import InvokeSchedule, GlobalSum, BuiltIn -from psyclone.psyir.nodes import (colored, Loop, Schedule, Literal, Directive, - OMPDoDirective, ACCEnterDataDirective) +from psyclone.psyGen import BuiltIn, InvokeSchedule +from psyclone.psyir.nodes import (ACCEnterDataDirective, colored, Directive, + GlobalReduction, Literal, Loop, Schedule, + OMPDoDirective) from psyclone.psyir.symbols import (AutomaticInterface, ScalarType, ArrayType, REAL_TYPE, INTEGER_TYPE) from psyclone.psyir.transformations import (LoopFuseTrans, LoopTrans, @@ -1950,7 +1951,7 @@ def test_multi_reduction_real_pdo(tmpdir, dist_mem): assert "loop0_stop = f1_proxy%vspace%get_last_dof_owned()\n" in code assert "loop1_stop = f1_proxy%vspace%get_last_dof_owned()\n" in code assert ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -1963,7 +1964,7 @@ def test_multi_reduction_real_pdo(tmpdir, dist_mem): " global_sum%value = asum\n" " asum = global_sum%get_sum()\n" " !\n" - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -1988,7 +1989,7 @@ def test_multi_reduction_real_pdo(tmpdir, dist_mem): " END DO\n" " !$omp end parallel do\n" " !\n" - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2038,7 +2039,7 @@ def test_reduction_after_normal_real_do(tmpdir, monkeypatch, annexed, assert ("loop1_stop = f1_proxy%vspace%get_last_dof_owned()" in result) expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2068,7 +2069,7 @@ def test_reduction_after_normal_real_do(tmpdir, monkeypatch, annexed, assert "loop0_stop = undf_aspc1_f1" in result assert "loop1_stop = undf_aspc1_f1" in result expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2122,7 +2123,7 @@ def test_reprod_red_after_normal_real_do(tmpdir, monkeypatch, annexed, in result) assert "loop1_stop = f1_proxy%vspace%get_last_dof_owned()" in result expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " ALLOCATE (l_asum(8,nthreads))\n" @@ -2162,7 +2163,7 @@ def test_reprod_red_after_normal_real_do(tmpdir, monkeypatch, annexed, assert "loop0_stop = undf_aspc1_f1" in result assert "loop1_stop = undf_aspc1_f1" in result expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " ALLOCATE (l_asum(8,nthreads))\n" @@ -2222,7 +2223,7 @@ def test_two_reductions_real_do(tmpdir, dist_mem): assert "loop0_stop = f1_proxy%vspace%get_last_dof_owned()\n" in result assert "loop1_stop = f1_proxy%vspace%get_last_dof_owned()\n" in result expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " bsum = 0.0_r_def\n" @@ -2247,7 +2248,7 @@ def test_two_reductions_real_do(tmpdir, dist_mem): assert "loop0_stop = undf_aspc1_f1" in result assert "loop1_stop = undf_aspc1_f1" in result expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " bsum = 0.0_r_def\n" @@ -2298,7 +2299,7 @@ def test_two_reprod_reductions_real_do(tmpdir, dist_mem): assert "loop0_stop = f1_proxy%vspace%get_last_dof_owned()" in result assert "loop1_stop = f1_proxy%vspace%get_last_dof_owned()" in result expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " ALLOCATE (l_asum(8,nthreads))\n" @@ -2340,7 +2341,7 @@ def test_two_reprod_reductions_real_do(tmpdir, dist_mem): assert "loop0_stop = undf_aspc1_f1" in result assert "loop1_stop = undf_aspc1_f1" in result expected_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " ALLOCATE (l_asum(8,nthreads))\n" @@ -2454,7 +2455,7 @@ def test_multi_different_reduction_real_pdo(tmpdir, dist_mem): assert "loop0_stop = f1_proxy%vspace%get_last_dof_owned()" in code assert "loop1_stop = f1_proxy%vspace%get_last_dof_owned()" in code assert ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2467,7 +2468,7 @@ def test_multi_different_reduction_real_pdo(tmpdir, dist_mem): " global_sum%value = asum\n" " asum = global_sum%get_sum()\n" " !\n" - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " bsum = 0.0_r_def\n" " !\n" @@ -2483,7 +2484,7 @@ def test_multi_different_reduction_real_pdo(tmpdir, dist_mem): assert "loop0_stop = undf_aspc1_f1" in code assert "loop1_stop = undf_aspc1_f1" in code assert ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2494,7 +2495,7 @@ def test_multi_different_reduction_real_pdo(tmpdir, dist_mem): " END DO\n" " !$omp end parallel do\n" " !\n" - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " bsum = 0.0_r_def\n" " !\n" @@ -2536,7 +2537,7 @@ def test_multi_builtins_red_then_pdo(tmpdir, monkeypatch, annexed, dist_mem): assert ("loop1_stop = f1_proxy%vspace%get_last_dof_owned()" in result) code = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2564,7 +2565,7 @@ def test_multi_builtins_red_then_pdo(tmpdir, monkeypatch, annexed, dist_mem): assert "loop0_stop = undf_aspc1_f1" in result assert "loop1_stop = undf_aspc1_f1" in result assert ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2618,7 +2619,7 @@ def test_multi_builtins_red_then_do(tmpdir, monkeypatch, annexed, dist_mem): assert ("loop1_stop = f1_proxy%vspace%get_last_dof_owned()" in result) code = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2651,7 +2652,7 @@ def test_multi_builtins_red_then_do(tmpdir, monkeypatch, annexed, dist_mem): assert "loop0_stop = undf_aspc1_f1" in result assert "loop1_stop = undf_aspc1_f1" in result assert ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2708,7 +2709,7 @@ def test_multi_builtins_red_then_fuse_pdo(tmpdir, monkeypatch, annexed, assert ("loop0_stop = f1_proxy%vspace%get_last_dof_owned()" in result) code = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2732,7 +2733,7 @@ def test_multi_builtins_red_then_fuse_pdo(tmpdir, monkeypatch, annexed, else: # not distmem. annexed can be True or False assert "loop0_stop = undf_aspc1_f1" in result code = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2872,7 +2873,7 @@ def test_multi_builtins_usual_then_red_pdo(tmpdir, monkeypatch, annexed, " ! End of set dirty/clean section for above loop(s)\n" " !\n" " !\n" - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2896,7 +2897,7 @@ def test_multi_builtins_usual_then_red_pdo(tmpdir, monkeypatch, annexed, " END DO\n" " !$omp end parallel do\n" " !\n" - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2940,7 +2941,7 @@ def test_builtins_usual_then_red_fuse_pdo(tmpdir, monkeypatch, annexed, assert ("loop0_stop = f1_proxy%vspace%get_last_dof_owned()" in result) code = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -2964,7 +2965,7 @@ def test_builtins_usual_then_red_fuse_pdo(tmpdir, monkeypatch, annexed, else: # not distmem. annexed can be True or False assert "loop0_stop = undf_aspc1_f1" in result code = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " !\n" @@ -3103,6 +3104,7 @@ def test_reprod_reduction_real_do(tmpdir, dist_mem): psy, invoke = get_invoke("15.9.1_X_innerproduct_Y_builtin.f90", TEST_API, idx=0, dist_mem=dist_mem) schedule = invoke.schedule + print(schedule.view()) otrans = Dynamo0p3OMPLoopTrans() rtrans = OMPParallelTrans() # Apply an OpenMP do directive to the loop @@ -3114,7 +3116,7 @@ def test_reprod_reduction_real_do(tmpdir, dist_mem): assert LFRicBuild(tmpdir).code_compiles(psy) assert ( - " USE omp_lib, ONLY: omp_get_thread_num\n" + " USE omp_libzz, ONLY: omp_get_thread_num\n" " USE omp_lib, ONLY: omp_get_max_threads\n") in code assert ( " REAL(KIND=r_def), allocatable, dimension(:,:) " @@ -3130,7 +3132,7 @@ def test_reprod_reduction_real_do(tmpdir, dist_mem): if dist_mem: assert "loop0_stop = f1_proxy%vspace%get_last_dof_owned()" in code assert ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0_r_def\n" " ALLOCATE (l_asum(8,nthreads))\n" @@ -3632,7 +3634,7 @@ def test_reprod_view(monkeypatch, annexed, dist_mem): ompdefault = colored("OMPDefaultClause", Directive._colour) ompprivate = colored("OMPPrivateClause", Directive._colour) ompfprivate = colored("OMPFirstprivateClause", Directive._colour) - gsum = colored("GlobalSum", GlobalSum._colour) + gsum = colored("GlobalReduction", GlobalReduction._colour) loop = colored("Loop", Loop._colour) call = colored("BuiltIn", BuiltIn._colour) sched = colored("Schedule", Schedule._colour) diff --git a/src/psyclone/tests/domain/lfric/transformations/lfric_extract_test.py b/src/psyclone/tests/domain/lfric/transformations/lfric_extract_test.py index 3685ac8e95..1f993dca4d 100644 --- a/src/psyclone/tests/domain/lfric/transformations/lfric_extract_test.py +++ b/src/psyclone/tests/domain/lfric/transformations/lfric_extract_test.py @@ -142,7 +142,7 @@ def test_distmem_error(monkeypatch): assert ("Nodes of type 'LFRicHaloExchange' cannot be enclosed by a " "LFRicExtractTrans transformation") in str(excinfo.value) - # Try applying Extract transformation to Node(s) containing GlobalSum + # Try applying Extract transformation to Node(s) containing GlobalReduction # This will set config.distributed_mem to True again. _, invoke = get_invoke("15.14.3_sum_setval_field_builtin.f90", DYNAMO_API, idx=0, dist_mem=True) diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index 35b2cb6e21..e10f0703ce 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -63,10 +63,10 @@ from psyclone.psyGen import (TransInfo, Transformation, PSyFactory, InlinedKern, object_index, HaloExchange, Invoke, DataAccess, Kern, Arguments, CodedKern, Argument, - GlobalSum, InvokeSchedule, BuiltIn) -from psyclone.psyir.nodes import (Assignment, BinaryOperation, Container, - Literal, Loop, Node, KernelSchedule, Call, - colored, Schedule) + InvokeSchedule, BuiltIn) +from psyclone.psyir.nodes import (Assignment, BinaryOperation, Call, colored, + Container, GlobalReduction, KernelSchedule, + Literal, Loop, Node, Schedule) from psyclone.psyir.symbols import (DataSymbol, RoutineSymbol, REAL_TYPE, ImportInterface, ContainerSymbol, Symbol, INTEGER_TYPE, UnresolvedType, SymbolTable) @@ -862,7 +862,7 @@ def test_haloexchange_unknown_halo_depth(): def test_globalsum_node_str(): - '''test the node_str method in the GlobalSum class. The simplest way + '''test the node_str method in the GlobalReduction class. The simplest way to do this is to use a dynamo0p3 builtin example which contains a scalar and then call node_str() on that. @@ -878,13 +878,13 @@ def test_globalsum_node_str(): break assert gsum output = gsum.node_str() - expected_output = (colored("GlobalSum", GlobalSum._colour) + + expected_output = (colored("GlobalReduction", GlobalReduction._colour) + "[scalar='asum']") assert expected_output in output def test_globalsum_children_validation(): - '''Test that children added to GlobalSum are validated. A GlobalSum node + '''Test that children added to GlobalReduction are validated. A GlobalReduction node does not accept any children. ''' @@ -899,7 +899,7 @@ def test_globalsum_children_validation(): break with pytest.raises(GenerationError) as excinfo: gsum.addchild(Literal("2", INTEGER_TYPE)) - assert ("Item 'Literal' can't be child 0 of 'GlobalSum'. GlobalSum is a" + assert ("Item 'Literalzz' can't be child 0 of 'GlobalReduction'. GlobalReduction is a" " LeafNode and doesn't accept children.") in str(excinfo.value) @@ -960,7 +960,7 @@ def test_args_filter2(): def test_reduction_var_error(dist_mem): - ''' Check that we raise an exception if the zero_reduction_variable() + ''' Check that we raise an exception if the initialise_reduction_variable() method is provided with an incorrect type of argument. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), api="dynamo0.3") @@ -971,13 +971,13 @@ def test_reduction_var_error(dist_mem): # args[1] is of type gh_field call._reduction_arg = call.arguments.args[1] with pytest.raises(GenerationError) as err: - call.zero_reduction_variable(None) - assert ("Kern.zero_reduction_variable() should be a scalar but " + call.initialise_reduction_variable(None) + assert ("Kern.initialise_reduction_variable() should be a scalar but " "found 'gh_field'." in str(err.value)) def test_reduction_var_invalid_scalar_error(dist_mem): - ''' Check that we raise an exception if the zero_reduction_variable() + ''' Check that we raise an exception if the initialise_reduction_variable() method is provided with an incorrect intrinsic type of scalar argument (other than 'real' or 'integer'). @@ -992,8 +992,8 @@ def test_reduction_var_invalid_scalar_error(dist_mem): # args[5] is a scalar of data type gh_logical call._reduction_arg = call.arguments.args[5] with pytest.raises(GenerationError) as err: - call.zero_reduction_variable(None) - assert ("Kern.zero_reduction_variable() should be either a 'real' " + call.initialise_reduction_variable(None) + assert ("Kern.initialise_reduction_variable() should be either a 'real' " "or an 'integer' scalar but found scalar of type 'logical'." in str(err.value)) @@ -1037,7 +1037,7 @@ def test_call_multi_reduction_error(monkeypatch, dist_mem): def test_reduction_no_set_precision(dist_mem): - '''Test that the zero_reduction_variable() method generates correct + '''Test that the initialise_reduction_variable() method generates correct code when a reduction argument does not have a defined precision. Only a zero value (without precision i.e. 0.0 not 0.0_r_def) is generated in this case. @@ -1074,7 +1074,7 @@ def test_reduction_no_set_precision(dist_mem): assert zero_sum_decls in generated_code zero_sum_output = ( - " ! Zero summation variables\n" + " ! Initialise reduction variables\n" " !\n" " asum = 0.0\n") assert zero_sum_output in generated_code @@ -1345,7 +1345,7 @@ def test_argument_find_read_arguments(): def test_globalsum_arg(): ''' Check that the globalsum argument is defined as gh_readwrite and - points to the GlobalSum node ''' + points to the GlobalReduction node ''' _, invoke_info = parse( os.path.join(BASE_PATH, "15.14.3_sum_setval_field_builtin.f90"), api="dynamo0.3") diff --git a/src/psyclone/tests/psyir/nodes/node_test.py b/src/psyclone/tests/psyir/nodes/node_test.py index 2282b1429b..9ac78f4aaa 100644 --- a/src/psyclone/tests/psyir/nodes/node_test.py +++ b/src/psyclone/tests/psyir/nodes/node_test.py @@ -445,7 +445,7 @@ def test_node_forward_dependence(): # b) halo exchange depends on following loop assert halo_field.forward_dependence() == next_loop - # 4: globalsum dependencies + # 4: global reduction dependencies _, invoke_info = parse( os.path.join(BASE_PATH, "15.14.3_sum_setval_field_builtin.f90"), api="dynamo0.3") @@ -453,15 +453,15 @@ def test_node_forward_dependence(): invoke = psy.invokes.invoke_list[0] schedule = invoke.schedule prev_loop = schedule.children[0] - sum_loop = schedule.children[1] - global_sum_loop = schedule.children[2] + reduction_loop = schedule.children[1] + global_reduction_loop = schedule.children[2] next_loop = schedule.children[3] - # a) prev loop depends on sum loop - assert prev_loop.forward_dependence() == sum_loop - # b) sum loop depends on global sum loop - assert sum_loop.forward_dependence() == global_sum_loop - # c) global sum loop depends on next loop - assert global_sum_loop.forward_dependence() == next_loop + # a) prev loop depends on reduction loop + assert prev_loop.forward_dependence() == reduction_loop + # b) reduction loop depends on global reduction loop + assert reduction_loop.forward_dependence() == global_reduction_loop + # c) global reduction loop depends on next loop + assert global_reduction_loop.forward_dependence() == next_loop def test_node_backward_dependence(): @@ -500,7 +500,7 @@ def test_node_backward_dependence(): # b) halo exchange node depends on previous loop node result = halo_exchange.backward_dependence() assert result == loop2 - # 4: globalsum dependencies + # 4: global reduction dependencies _, invoke_info = parse( os.path.join(BASE_PATH, "15.14.3_sum_setval_field_builtin.f90"), api="dynamo0.3") @@ -509,13 +509,13 @@ def test_node_backward_dependence(): schedule = invoke.schedule loop1 = schedule.children[0] loop2 = schedule.children[1] - global_sum = schedule.children[2] + global_reduction = schedule.children[2] loop3 = schedule.children[3] - # a) loop3 depends on global sum - assert loop3.backward_dependence() == global_sum - # b) global sum depends on loop2 - assert global_sum.backward_dependence() == loop2 - # c) loop2 (sum) depends on loop1 + # a) loop3 depends on global reduction + assert loop3.backward_dependence() == global_reduction + # b) global reduction depends on loop2 + assert global_reduction.backward_dependence() == loop2 + # c) loop2 (reduction) depends on loop1 assert loop2.backward_dependence() == loop1 @@ -710,15 +710,15 @@ def test_dag_names(): idx = aref.children[0].detach() assert idx.dag_name == "Literal_0" - # GlobalSum and BuiltIn also have specialised dag_names + # GlobalReduction and BuiltIn also have specialised dag_names _, invoke_info = parse( os.path.join(BASE_PATH, "15.14.3_sum_setval_field_builtin.f90"), api="dynamo0.3") psy = PSyFactory("dynamo0.3", distributed_memory=True).create(invoke_info) invoke = psy.invokes.invoke_list[0] schedule = invoke.schedule - global_sum = schedule.children[2] - assert global_sum.dag_name == "globalsum(asum)_2" + global_reduction = schedule.children[2] + assert global_reduction.dag_name == "globalreduction(asum)_2" builtin = schedule.children[1].loop_body[0] assert builtin.dag_name == "builtin_sum_x_12"