40 ''' This module provides generic support for PSyclone's PSy code optimisation
41 and generation. The classes in this method need to be specialised for a
42 particular API and implementation. '''
45 from collections
import OrderedDict
50 from psyclone.errors import GenerationError, InternalError, FieldNotFoundError
52 DeclGen, DeallocateGen, DoGen, UseGen)
56 Loop, Node, OMPDoDirective, Reference,
57 Routine, Schedule, Statement)
59 ContainerSymbol, DataSymbol,
61 ImportInterface, INTEGER_TYPE,
62 RoutineSymbol, Symbol)
67 FORTRAN_INTENT_NAMES = [
"inout",
"out",
"in"]
70 REDUCTION_OPERATOR_MAPPING = {AccessType.SUM:
"+"}
73 def object_index(alist, item):
75 A version of the `list.index()` method that checks object identity
76 rather that the content of the object.
78 TODO this is a workaround for the fact that fparser2 overrides the
79 comparison operator for all nodes in the parse tree. See fparser
82 :param alist: single object or list of objects to search.
83 :type alist: list or :py:class:`fparser.two.utils.Base`
84 :param obj item: object to search for in the list.
85 :returns: index of the item in the list.
87 :raises ValueError: if object is not in the list.
90 raise InternalError(
"Cannot search for None item in list.")
91 for idx, entry
in enumerate(alist):
94 raise ValueError(f
"Item '{item}' not found in list: {alist}")
98 ''' If no API is specified then return the default. Otherwise, check that
99 the supplied API is valid.
100 :param str api: The PSyclone API to check or an empty string.
101 :returns: The API that is in use.
103 :raises GenerationError: if the specified API is not supported.
107 api = Config.get().default_api
109 if api
not in Config.get().supported_apis:
110 raise GenerationError(f
"get_api: Unsupported API '{api}' "
111 f
"specified. Supported types are "
112 f
"{Config.get().supported_apis}.")
116 def zero_reduction_variables(red_call_list, parent):
117 '''zero all reduction variables associated with the calls in the call
120 parent.add(CommentGen(parent,
""))
121 parent.add(CommentGen(parent,
" Zero summation variables"))
122 parent.add(CommentGen(parent,
""))
123 for call
in red_call_list:
124 call.zero_reduction_variable(parent)
125 parent.add(CommentGen(parent,
""))
128 def args_filter(arg_list, arg_types=None, arg_accesses=None, arg_meshes=None,
129 include_literals=True):
131 Return all arguments in the supplied list that are of type
132 arg_types and with access in arg_accesses. If these are not set
133 then return all arguments.
135 :param arg_list: list of kernel arguments to filter.
136 :type arg_list: list of :py:class:`psyclone.parse.kernel.Descriptor`
137 :param arg_types: list of argument types (e.g. "GH_FIELD").
138 :type arg_types: list of str
139 :param arg_accesses: list of access types that arguments must have.
140 :type arg_accesses: list of \
141 :py:class:`psyclone.core.access_type.AccessType`
142 :param arg_meshes: list of meshes that arguments must be on.
143 :type arg_meshes: list of str
144 :param bool include_literals: whether or not to include literal arguments \
145 in the returned list.
147 :returns: list of kernel arguments matching the requirements.
148 :rtype: list of :py:class:`psyclone.parse.kernel.Descriptor`
152 for argument
in arg_list:
154 if argument.argument_type.lower()
not in arg_types:
157 if argument.access
not in arg_accesses:
160 if argument.mesh
not in arg_meshes:
162 if not include_literals:
165 if argument.is_literal:
167 arguments.append(argument)
173 Creates a specific version of the PSy. If a particular api is not
174 provided then the default api, as specified in the psyclone.cfg
177 :param str api: name of the PSyclone API (domain) for which to create \
179 :param bool distributed_memory: whether or not the PSy object created \
180 will include support for distributed-memory parallelism.
182 :raises TypeError: if the distributed_memory argument is not a bool.
185 def __init__(self, api="", distributed_memory=None):
187 if distributed_memory
is None:
188 _distributed_memory = Config.get().distributed_memory
190 _distributed_memory = distributed_memory
192 if _distributed_memory
not in [
True,
False]:
194 "The distributed_memory flag in PSyFactory must be set to"
195 " 'True' or 'False'")
196 Config.get().distributed_memory = _distributed_memory
197 self.
_type_type = get_api(api)
201 Create the API-specific PSy instance.
203 :param invoke_info: information on the invoke()s found by parsing \
204 the Algorithm layer or (for NEMO) the fparser2 \
205 parse tree of the source file.
206 :type invoke_info: :py:class:`psyclone.parse.algorithm.FileInfo` or \
207 :py:class:`fparser.two.Fortran2003.Program`
209 :returns: an instance of the API-specific sub-class of PSy.
210 :rtype: subclass of :py:class:`psyclone.psyGen.PSy`
212 :raises InternalError: if this factory is found to have an \
213 unsupported type (API).
218 if self.
_type_type ==
"dynamo0.3":
220 elif self.
_type_type ==
"gocean1.0":
222 elif self.
_type_type ==
"nemo":
228 f
"PSyFactory: Unsupported API type '{self._type}' found. "
229 f
"Expected one of {Config.get().supported_apis}.")
230 return PSyClass(invoke_info)
235 Base class to help manage and generate PSy code for a single
236 algorithm file. Takes the invocation information output from the
237 function :func:`parse.algorithm.parse` as its input and stores this in a
238 way suitable for optimisation and code generation.
240 :param FileInfo invoke_info: An object containing the required \
241 invocation information for code \
242 optimisation and generation. Produced \
243 by the function :func:`parse.algorithm.parse`.
244 :type invoke_info: :py:class:`psyclone.parse.algorithm.FileInfo`
248 >>> from psyclone.parse.algorithm import parse
249 >>> ast, info = parse("argspec.F90")
250 >>> from psyclone.psyGen import PSyFactory
252 >>> psy = PSyFactory(api).create(info)
256 def __init__(self, invoke_info):
257 self.
_name_name = invoke_info.name
266 :returns: the container associated with this PSy object
267 :rtype: :py:class:`psyclone.psyir.nodes.Container`
276 ''':returns: the list of invokes.
277 :rtype: :py:class:`psyclone.psyGen.Invokes` or derived class
283 ''':returns: the name of the PSy object.
286 return "psy_"+self.
_name_name
291 '''Abstract base class for code generation function.
293 :returns: root node of generated Fortran AST.
294 :rtype: :py:class:`psyclone.psyir.nodes.Node`
299 '''Manage the invoke calls.
301 :param alg_calls: a list of invoke metadata extracted by the \
303 :type alg_calls: list of \
304 :py:class:`psyclone.parse.algorithm.InvokeCall`
305 :param invoke_cls: an api-specific Invoke class.
306 :type invoke_cls: subclass of :py:class:`psyclone.psyGen.Invoke`
307 :param psy: the PSy instance containing this Invokes instance.
308 :type psy: subclass of :py:class`psyclone.psyGen.PSy`
315 for idx, alg_invocation
in enumerate(alg_calls):
316 my_invoke = invoke_cls(alg_invocation, idx, self)
317 self.
invoke_mapinvoke_map[my_invoke.name] = my_invoke
321 return "Invokes object containing "+str(self.
namesnames)
326 :returns: the PSy instance that contains this instance.
327 :rtype: subclass of :py:class:`psyclone.psyGen.PSy`
336 def get(self, invoke_name):
338 Gets the Invoke with the supplied name. If the name does not already
339 begin with ``invoke_`` then a new name with this prepended is included
340 in the search if no exact match is found initially.
342 :param str invoke_name: the name of the Invoke to get (not case-
345 :returns: the invoke with the specified name.
346 :rtype: :py:class:`psyclone.psyGen.Invoke`
348 :raises RuntimeError: if no Invoke with the supplied name (with or
349 without ``invoke_`` prepended) exists.
351 search_names = [invoke_name.lower()]
352 if not search_names[0].startswith(
"invoke_"):
353 search_names.append(
"invoke_"+search_names[0])
354 for name
in search_names:
360 search_list =
" or ".join(f
"'{name}'" for name
in search_names)
361 raise RuntimeError(f
"Cannot find an invoke named {search_list} "
362 f
"in {list(self.names)}")
366 Create the f2pygen AST for each Invoke in the PSy layer.
368 :param parent: the parent node in the AST to which to add content.
369 :type parent: `psyclone.f2pygen.ModuleGen`
371 :raises GenerationError: if an invoke_list schedule is not an \
375 if not isinstance(invoke.schedule, InvokeSchedule):
377 f
"An invoke.schedule element of the invoke_list is a "
378 f
"'{type(invoke.schedule).__name__}', but it should be an "
379 f
"'InvokeSchedule'.")
380 invoke.gen_code(parent)
384 r'''Manage an individual invoke call.
386 :param alg_invocation: metadata from the parsed code capturing \
387 information for this Invoke instance.
388 :type alg_invocation: :py:class:`psyclone.parse.algorithm.InvokeCall`
389 :param int idx: position/index of this invoke call in the subroutine. \
390 If not None, this number is added to the name ("invoke\_").
391 :param schedule_class: the schedule class to create for this invoke.
392 :type schedule_class: :py:class:`psyclone.psyGen.InvokeSchedule`
393 :param invokes: the Invokes instance that contains this Invoke \
395 :type invokes: :py:class:`psyclone.psyGen.Invokes`
396 :param reserved_names: optional list of reserved names, i.e. names that \
397 should not be used e.g. as a PSyclone-created \
399 :type reserved_names: list of str
402 def __init__(self, alg_invocation, idx, schedule_class, invokes,
403 reserved_names=None):
404 '''Construct an invoke object.'''
407 self.
_name_name =
"invoke"
410 if alg_invocation
is None and idx
is None:
414 if alg_invocation.name
is not None:
416 self.
_name_name = str(alg_invocation.name)
417 elif len(alg_invocation.kcalls) == 1
and \
418 alg_invocation.kcalls[0].type ==
"kernelCall":
422 self.
_name_name =
"invoke_" + str(idx) +
"_" + \
423 alg_invocation.kcalls[0].ktype.name
426 self.
_name_name =
"invoke_" + str(idx)
428 if not reserved_names:
434 container = self.
invokesinvokes.psy.container
437 self.
_schedule_schedule = schedule_class(self.
_name_name, alg_invocation.kcalls,
438 reserved_names, parent=container)
442 container.addchild(self.
_schedule_schedule)
453 for arg
in call.arguments.args:
454 if arg.text
is not None:
457 if arg.name
not in tmp_arg_names:
458 tmp_arg_names.append(arg.name)
466 for kern_call
in self.
_schedule_schedule.coded_kernels():
467 dofs = kern_call.arguments.dofs
469 if dof
not in self.
_dofs_dofs:
473 self.
_dofs_dofs[dof] = [kern_call, dofs[dof][0]]
476 return self.
_name_name+
"("+
", ".join([str(arg)
for arg
in
482 :returns: the Invokes instance that contains this instance.
483 :rtype: :py:class`psyclone.psyGen.Invokes`
490 return self.
_name_name
493 def alg_unique_args(self):
497 def psy_unique_vars(self):
501 def psy_unique_var_names(self):
504 names.append(var.name)
512 def schedule(self, obj):
516 intrinsic_type=None):
518 Returns a list of all required declarations for the specified
519 API argument types. If access is supplied (e.g. "write") then
520 only declarations with that access are returned. If an intrinsic
521 type is supplied then only declarations with that intrinsic type
524 :param argument_types: the types of the kernel argument for the \
526 :type argument_types: list of str
527 :param access: optional AccessType that the declaration should have.
528 :type access: :py:class:`psyclone.core.access_type.AccessType`
529 :param intrinsic_type: optional intrinsic type of argument data.
530 :type intrinsic_type: str
532 :returns: a list of all declared kernel arguments.
533 :rtype: list of :py:class:`psyclone.psyGen.KernelArgument`
535 :raises InternalError: if at least one kernel argument type is \
536 not valid for the particular API.
537 :raises InternalError: if an invalid access is specified.
538 :raises InternalError: if an invalid intrinsic type is specified.
542 const = Config.get().api_conf().get_constants()
543 if any(argtype
not in const.VALID_ARG_TYPE_NAMES
for
544 argtype
in argument_types):
546 f
"Invoke.unique_declarations() called with at least one "
547 f
"invalid argument type. Expected one of "
548 f
"{const.VALID_ARG_TYPE_NAMES} but found {argument_types}.")
550 if access
and not isinstance(access, AccessType):
552 f
"Invoke.unique_declarations() called with an invalid "
553 f
"access type. Type is '{access}' instead of AccessType.")
555 if (intrinsic_type
and intrinsic_type
not in
556 const.VALID_INTRINSIC_TYPES):
558 f
"Invoke.unique_declarations() called with an invalid "
559 f
"intrinsic argument data type. Expected one of "
560 f
"{const.VALID_INTRINSIC_TYPES} but found '{intrinsic_type}'.")
564 declarations = OrderedDict()
567 for arg
in call.arguments.args:
568 if not intrinsic_type
or arg.intrinsic_type == intrinsic_type:
569 if not access
or arg.access == access:
570 if arg.text
is not None:
571 if arg.argument_type
in argument_types:
572 test_name = arg.declaration_name
573 if test_name
not in declarations:
574 declarations[test_name] = arg
575 return list(declarations.values())
578 ''' Returns the first argument with the specified name passed to
579 a kernel in our schedule '''
581 for arg
in call.arguments.args:
582 if arg.text
is not None:
583 if arg.declaration_name == arg_name:
590 Returns a dictionary listing all required declarations for each
591 type of intent ('inout', 'out' and 'in').
593 :param argument_types: the types of the kernel argument for the \
594 particular API for which the intent is required.
595 :type argument_types: list of str
596 :param intrinsic_type: optional intrinsic type of argument data.
597 :type intrinsic_type: str
599 :returns: dictionary containing 'intent' keys holding the kernel \
600 arguments as values for each type of intent.
601 :rtype: dict of :py:class:`psyclone.psyGen.KernelArgument`
603 :raises InternalError: if at least one kernel argument type is \
604 not valid for the particular API.
605 :raises InternalError: if an invalid intrinsic type is specified.
609 const = Config.get().api_conf().get_constants()
610 if any(argtype
not in const.VALID_ARG_TYPE_NAMES
for
611 argtype
in argument_types):
613 f
"Invoke.unique_declns_by_intent() called with at least one "
614 f
"invalid argument type. Expected one of "
615 f
"{const.VALID_ARG_TYPE_NAMES} but found {argument_types}.")
617 if (intrinsic_type
and intrinsic_type
not in
618 const.VALID_INTRINSIC_TYPES):
620 f
"Invoke.unique_declns_by_intent() called with an invalid "
621 f
"intrinsic argument data type. Expected one of "
622 f
"{const.VALID_INTRINSIC_TYPES} but found '{intrinsic_type}'.")
627 for intent
in FORTRAN_INTENT_NAMES:
631 intrinsic_type=intrinsic_type):
632 first_arg = self.
first_accessfirst_access(arg.declaration_name)
633 if first_arg.access
in [AccessType.WRITE, AccessType.SUM]:
638 declns[
"out"].append(arg)
645 for tmp_arg
in call.arguments.args:
646 if tmp_arg.text
is not None and \
647 tmp_arg.declaration_name == arg.declaration_name:
648 if tmp_arg.access != AccessType.READ:
657 declns[
"in"].append(arg)
659 declns[
"inout"].append(arg)
664 module = ModuleGen(
"container")
671 Generates invocation code (the subroutine called by the associated
672 invoke call in the algorithm layer). This consists of the PSy
673 invocation subroutine and the declaration of its arguments.
675 :param parent: the node in the generated AST to which to add content.
676 :type parent: :py:class:`psyclone.f2pygen.ModuleGen`
681 class InvokeSchedule(Routine):
683 Stores schedule information for an invocation call. Schedules can be
684 optimised using transformations.
686 >>> from psyclone.parse.algorithm import parse
687 >>> ast, info = parse("algorithm.f90")
688 >>> from psyclone.psyGen import PSyFactory
690 >>> psy = PSyFactory(api).create(info)
691 >>> invokes = psy.invokes
693 >>> invoke = invokes.get("name")
694 >>> schedule = invoke.schedule
695 >>> print(schedule.view())
697 :param str name: name of the Invoke.
698 :param type KernFactory: class instance of the factory to use when \
699 creating Kernels. e.g. \
700 :py:class:`psyclone.domain.lfric.LFRicKernCallFactory`.
701 :param type BuiltInFactory: class instance of the factory to use when \
702 creating built-ins. e.g. \
703 :py:class:`psyclone.domain.lfric.lfric_builtins.LFRicBuiltInCallFactory`.
704 :param alg_calls: list of Kernel calls in the schedule.
705 :type alg_calls: list of :py:class:`psyclone.parse.algorithm.KernelCall`
706 :param kwargs: additional keyword arguments provided to the super class.
707 :type kwargs: unwrapped dict.
711 _text_name =
"InvokeSchedule"
713 def __init__(self, name, KernFactory, BuiltInFactory, alg_calls=None,
714 reserved_names=None, **kwargs):
715 super().__init__(name, **kwargs)
721 for reserved
in reserved_names:
722 self.symbol_table.add(Symbol(reserved))
727 if alg_calls
is None:
729 for call
in alg_calls:
730 if isinstance(call, BuiltInCall):
731 self.addchild(BuiltInFactory.create(call, parent=self))
733 self.addchild(KernFactory.create(call, parent=self))
738 :returns: Table containing symbol information for the schedule.
739 :rtype: :py:class:`psyclone.psyir.symbols.SymbolTable`
741 return self._symbol_table
748 def invoke(self, my_invoke):
749 self.
_invoke_invoke = my_invoke
753 Returns the name of this node with appropriate control codes
754 to generate coloured output in a terminal that supports it.
756 :param bool colour: whether or not to include colour control codes.
758 :returns: description of this node, possibly coloured.
761 return f
"{self.coloured_name(colour)}[invoke='{self.name}']"
764 result = self.coloured_name(
False) +
":\n"
765 for entity
in self._children:
766 result += str(entity) +
"\n"
767 result +=
"End " + self.coloured_name(
False) +
"\n"
772 Generate the Nodes in the f2pygen AST for this schedule.
774 :param parent: the parent Node (i.e. the enclosing subroutine) to \
775 which to add content.
776 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
782 for imported_var
in self.
symbol_tablesymbol_table.imported_symbols:
783 module_name = imported_var.interface.container_symbol.name
784 if module_name
in module_map:
785 module_map[module_name].append(imported_var.name)
787 module_map[module_name] = [imported_var.name]
790 for module_name, var_list
in module_map.items():
791 parent.add(
UseGen(parent, name=module_name, only=
True,
794 for entity
in self.children:
795 entity.gen_code(parent)
800 Generic Global Sum class which can be added to and manipulated
803 :param scalar: the scalar that the global sum is stored into
804 :type scalar: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
805 :param parent: optional parent (default None) of this object
806 :type parent: :py:class:`psyclone.psyir.nodes.Node`
810 _children_valid_format =
"<LeafNode>"
811 _text_name =
"GlobalSum"
814 def __init__(self, scalar, parent=None):
815 Node.__init__(self, children=[], parent=parent)
817 self.
_scalar_scalar = copy.copy(scalar)
822 self.
_scalar_scalar.access = AccessType.READWRITE
823 self.
_scalar_scalar.call = self
827 ''' Return the scalar field that this global sum acts on '''
833 :returns: the name to use in the DAG for this node.
836 return f
"globalsum({self._scalar.name})_{self.position}"
840 ''' Return the list of arguments associated with this node. Override
841 the base method and simply return our argument.'''
846 Returns a text description of this node with (optional) control codes
847 to generate coloured output in a terminal that supports it.
849 :param bool colour: whether or not to include colour control codes.
851 :returns: description of this node, possibly coloured.
854 return f
"{self.coloured_name(colour)}[scalar='{self._scalar.name}']"
859 Generic Halo Exchange class which can be added to and
860 manipulated in, a schedule.
862 :param field: the field that this halo exchange will act on
863 :type field: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
864 :param check_dirty: optional argument default True indicating whether \
865 this halo exchange should be subject to a run-time \
866 check for clean/dirty halos.
867 :type check_dirty: bool
868 :param vector_index: optional vector index (default None) to identify \
869 which index of a vector field this halo exchange is \
871 :type vector_index: int
872 :param parent: optional parent (default None) of this object
873 :type parent: :py:class:`psyclone.psyir.nodes.Node`
877 _children_valid_format =
"<LeafNode>"
878 _text_name =
"HaloExchange"
881 def __init__(self, field, check_dirty=True,
882 vector_index=None, parent=None):
883 Node.__init__(self, children=[], parent=parent)
885 self.
_field_field = copy.copy(field)
890 self.
_field_field.access = AccessType.READWRITE
891 self.
_field_field.call = self
899 isched = self.ancestor(InvokeSchedule)
904 def vector_index(self):
905 '''If the field is a vector then return the vector index associated
906 with this halo exchange. Otherwise return None'''
911 ''' Return the depth of the halo exchange '''
916 ''' Set the depth of the halo exchange '''
921 ''' Return the field that the halo exchange acts on '''
927 :returns: the name to use in a dag for this node.
930 name = f
"{self._text_name}({self._field.name})_{self.position}"
932 name =
"check" + name
937 '''Return the list of arguments associated with this node. Overide the
938 base method and simply return our argument. '''
939 return [self.
_field_field]
942 '''Helper method which checks that two halo exchange nodes (one being
943 self and the other being passed by argument) operating on the
944 same field, both have vector fields of the same size and use
945 different vector indices. If this is the case then the halo
946 exchange nodes do not depend on each other. If this is not the
947 case then an internal error will have occured and we raise an
948 appropriate exception.
950 :param node: a halo exchange which should exchange the same field as \
952 :type node: :py:class:`psyclone.psyGen.HaloExchange`
953 :raises GenerationError: if the argument passed is not a halo exchange.
954 :raises GenerationError: if the field name in the halo exchange \
955 passed in has a different name to the field \
956 in this halo exchange.
957 :raises GenerationError: if the field in this halo exchange is not a \
959 :raises GenerationError: if the vector size of the field in this halo \
960 exchange is different to vector size of the \
961 field in the halo exchange passed by argument.
962 :raises GenerationError: if the vector index of the field in this \
963 halo exchange is the same as the vector \
964 index of the field in the halo exchange \
969 if not isinstance(node, HaloExchange):
971 "Internal error, the argument passed to "
972 "HaloExchange.check_vector_halos_differ() is not "
973 "a halo exchange object")
975 if self.
fieldfield.name != node.field.name:
977 f
"Internal error, the halo exchange object passed to "
978 f
"HaloExchange.check_vector_halos_differ() has a different "
979 f
"field name '{node.field.name}' to self '{self.field.name}'")
981 if self.
fieldfield.vector_size <= 1:
983 "Internal error, HaloExchange.check_vector_halos_differ() "
984 "a halo exchange depends on another halo exchange but the "
985 f
"vector size of field '{self.field.name}' is 1")
987 if self.
fieldfield.vector_size != node.field.vector_size:
989 f
"Internal error, HaloExchange.check_vector_halos_differ() "
990 f
"a halo exchange depends on another halo exchange but the "
991 f
"vector sizes for field '{self.field.name}' differ")
995 f
"Internal error, HaloExchange.check_vector_halos_differ() "
996 f
"a halo exchange depends on another halo exchange but both "
997 f
"vector id's ('{self.vector_index}') of field "
998 f
"'{self.field.name}' are the same")
1002 Returns the name of this node with (optional) control codes
1003 to generate coloured output in a terminal that supports it.
1005 :param bool colour: whether or not to include colour control codes.
1007 :returns: description of this node, possibly coloured.
1010 return (f
"{self.coloured_name(colour)}[field='{self._field.name}', "
1011 f
"type='{self._halo_type}', depth={self._halo_depth}, "
1012 f
"check_dirty={self._check_dirty}]")
1016 '''Base class representing a call to a sub-program unit from within the
1017 PSy layer. It is possible for this unit to be in-lined within the
1020 :param parent: parent of this node in the PSyIR.
1021 :type parent: sub-class of :py:class:`psyclone.psyir.nodes.Node`
1022 :param call: information on the call itself, as obtained by parsing \
1023 the Algorithm layer code.
1024 :type call: :py:class:`psyclone.parse.algorithm.KernelCall`
1025 :param str name: the name of the routine being called.
1026 :param ArgumentsClass: class to create the object that holds all \
1027 information on the kernel arguments, as extracted from kernel \
1028 meta-data (and accessible here via call.ktype).
1029 :type ArgumentsClass: type of :py:class:`psyclone.psyGen.Arguments`
1030 :param bool check: whether to check for consistency between the \
1031 kernel metadata and the algorithm layer. Defaults to True.
1033 :raises GenerationError: if any of the arguments to the call are \
1038 _children_valid_format =
"<LeafNode>"
1040 def __init__(self, parent, call, name, ArgumentsClass, check=True):
1042 super().__init__(parent=parent)
1043 self.
_name_name = name
1045 self.
_arguments_arguments = ArgumentsClass(call, self, check=check)
1052 text = arg.text.lower().replace(
" ",
"")
1053 if text
in arg_names:
1055 f
"Argument '{arg.text}' is passed into kernel "
1056 f
"'{self._name}' code more than once from the "
1057 f
"algorithm layer. This is not allowed.")
1058 arg_names.append(text)
1063 reduction_modes = AccessType.get_valid_reduction_modes()
1064 const = Config.get().api_conf().get_constants()
1065 args = args_filter(self.
_arguments_arguments.args,
1066 arg_types=const.VALID_SCALAR_NAMES,
1067 arg_accesses=reduction_modes)
1072 "PSyclone currently only supports a single reduction "
1073 "in a kernel or builtin")
1081 '''Return the list of arguments associated with this node. Overide the
1082 base method and simply return our arguments. '''
1086 ''' Returns the name of this node with (optional) control codes
1087 to generate coloured output in a terminal that supports it.
1089 :param bool colour: whether or not to include colour control codes.
1091 :returns: description of this node, possibly coloured.
1094 return (self.coloured_name(colour) +
" " + self.
namenamename +
1095 "(" + self.
argumentsarguments.names +
")")
1098 '''Get all variable access information. The API specific classes
1099 add the accesses to the arguments. So the code here only calls
1100 the baseclass, and increases the location.
1102 :param var_accesses: VariablesAccessInfo instance that stores the \
1103 information about variable accesses.
1104 :type var_accesses: \
1105 :py:class:`psyclone.core.VariablesAccessInfo`
1108 var_accesses.next_location()
1113 :returns: whether this kernel/built-in contains a reduction variable.
1122 :returns: the reduction variable if this kernel/built-in
1123 contains one and `None` otherwise.
1124 :rtype: :py:class:`psyclone.psyGen.KernelArgument` or `NoneType`
1132 :returns: whether this kernel/built-in is enclosed within an OpenMP
1133 do loop. If so report whether it has the reproducible flag
1134 set. Note, this also catches OMPParallelDo Directives but
1135 they have reprod set to False so it is OK.
1139 ancestor = self.ancestor(OMPDoDirective)
1141 return ancestor.reprod
1147 :returns: a local reduction variable name that is unique for the
1148 current reduction argument name. This is used for
1149 thread-local reductions with reproducible reductions.
1160 Generate code to zero the reduction variable and to zero the local
1161 reduction variable if one exists. The latter is used for reproducible
1162 reductions, if specified.
1164 :param parent: the Node in the AST to which to add new code.
1165 :type parent: :py:class:`psyclone.psyir.nodes.Node`
1166 :param str position: where to position the new code in the AST.
1168 :raises GenerationError: if the variable to zero is not a scalar.
1169 :raises GenerationError: if the reprod_pad_size (read from the \
1170 configuration file) is less than 1.
1171 :raises GenerationError: for a reduction into a scalar that is \
1172 neither 'real' nor 'integer'.
1181 if not var_arg.is_scalar:
1183 f
"Kern.zero_reduction_variable() should be a scalar but "
1184 f
"found '{var_arg.argument_type}'.")
1186 var_data_type = var_arg.intrinsic_type
1187 if var_data_type ==
"real":
1189 elif var_data_type ==
"integer":
1193 f
"Kern.zero_reduction_variable() should be either a 'real' or "
1194 f
"an 'integer' scalar but found scalar of type "
1195 f
"'{var_arg.intrinsic_type}'.")
1198 if var_arg.precision:
1199 kind_type = var_arg.precision
1200 zero_sum_variable =
"_".join([data_value, kind_type])
1203 zero_sum_variable = data_value
1204 parent.add(
AssignGen(parent, lhs=var_name, rhs=zero_sum_variable),
1207 parent.add(
DeclGen(parent, datatype=var_data_type,
1208 entity_decls=[local_var_name],
1209 allocatable=
True, kind=kind_type,
1212 self.scope.symbol_table.lookup_with_tag(
"omp_num_threads").name
1213 if Config.get().reprod_pad_size < 1:
1215 f
"REPROD_PAD_SIZE in {Config.get().filename} should be a "
1216 f
"positive integer, but it is set to "
1217 f
"'{Config.get().reprod_pad_size}'.")
1218 pad_size = str(Config.get().reprod_pad_size)
1219 parent.add(
AllocateGen(parent, local_var_name +
"(" + pad_size +
1220 "," + nthreads +
")"), position=position)
1221 parent.add(
AssignGen(parent, lhs=local_var_name,
1222 rhs=zero_sum_variable), position=position)
1226 Generate the appropriate code to place after the end parallel
1229 :param parent: the Node in the f2pygen AST to which to add new code.
1230 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1232 :raises GenerationError: for an unsupported reduction access in \
1247 reduction_operator = REDUCTION_OPERATOR_MAPPING[reduction_access]
1248 except KeyError
as err:
1249 api_strings = [access.api_specific_name()
1250 for access
in REDUCTION_OPERATOR_MAPPING]
1252 f
"Unsupported reduction access "
1253 f
"'{reduction_access.api_specific_name()}' found in "
1254 f
"LFRicBuiltIn:reduction_sum_loop(). Expected one of "
1255 f
"{api_strings}.")
from err
1256 symtab = self.scope.symbol_table
1257 thread_idx = symtab.lookup_with_tag(
"omp_thread_index").name
1258 nthreads = symtab.lookup_with_tag(
"omp_num_threads").name
1259 do_loop =
DoGen(parent, thread_idx,
"1", nthreads)
1260 do_loop.add(
AssignGen(do_loop, lhs=var_name, rhs=var_name +
1261 reduction_operator + local_var_ref))
1265 def _reduction_reference(self):
1267 Return the reference to the reduction variable if OpenMP is set to
1268 be unreproducible, as we will be using the OpenMP reduction clause.
1269 Otherwise we will be computing the reduction ourselves and therefore
1270 need to store values into a (padded) array separately for each
1273 :returns: reference to the variable to be reduced.
1274 :rtype: :py:class:`psyclone.psyir.nodes.Reference` or
1275 :py:class:`psyclone.psyir.nodes.ArrayReference`
1281 symtab = self.scope.symbol_table
1286 Literal(
"1", INTEGER_TYPE),
1287 Reference(symtab.lookup_with_tag(
"omp_thread_index"))]
1288 reduction_array = ArrayType(
1289 symtab.lookup(reduction_name).datatype, array_dim)
1290 local_reduction = DataSymbol(
1292 symtab.find_or_create_tag(
1294 symbol_type=DataSymbol, datatype=reduction_array)
1295 return ArrayReference.create(
1296 local_reduction, array_dim)
1298 return Reference(symtab.lookup(reduction_name))
1301 def arg_descriptors(self):
1304 @arg_descriptors.setter
1305 def arg_descriptors(self, obj):
1309 def arguments(self):
1315 :returns: the name of the kernel.
1318 return self.
_name_name
1323 Set the name of the kernel.
1325 :param str value: The name of the kernel.
1327 self.
_name_name = value
1331 :returns: True if this kernel is being called from within a \
1335 parent_loop = self.ancestor(Loop)
1337 if parent_loop.loop_type ==
"colour":
1339 parent_loop = parent_loop.ancestor(Loop)
1343 def iterates_over(self):
1346 def local_vars(self):
1347 raise NotImplementedError(
"Kern.local_vars should be implemented")
1349 def gen_code(self, parent):
1350 raise NotImplementedError(
"Kern.gen_code should be implemented")
1355 Class representing a call to a PSyclone Kernel with a user-provided
1356 implementation. The kernel may or may not be in-lined.
1358 :param type KernelArguments: the API-specific sub-class of \
1359 :py:class:`psyclone.psyGen.Arguments` to \
1361 :param call: Details of the call to this kernel in the Algorithm layer.
1362 :type call: :py:class:`psyclone.parse.algorithm.KernelCall`.
1363 :param parent: the parent of this Node (kernel call) in the Schedule.
1364 :type parent: sub-class of :py:class:`psyclone.psyir.nodes.Node`.
1365 :param bool check: whether to check for consistency between the \
1366 kernel metadata and the algorithm layer. Defaults to True.
1370 _text_name =
"CodedKern"
1373 def __init__(self, KernelArguments, call, parent=None, check=True):
1378 super(CodedKern, self).__init__(parent, call,
1379 call.ktype.procedure.name,
1380 KernelArguments, check)
1390 self.
_opencl_options_opencl_options = {
'local_size': 64,
'queue_number': 1}
1395 Returns a PSyIR Schedule representing the kernel code. The Schedule
1396 is just generated on first invocation, this allows us to retain
1397 transformations that may subsequently be applied to the Schedule.
1399 :returns: Schedule representing the kernel code.
1400 :rtype: :py:class:`psyclone.psyir.nodes.KernelSchedule`
1404 astp = Fparser2Reader()
1412 :returns: dictionary of OpenCL options regarding the kernel.
1419 Validate and store a set of options associated with the Kernel to
1420 tune the OpenCL code generation.
1422 :param options: a set of options to tune the OpenCL code.
1423 :type options: dictionary of <string>:<value>
1426 valid_opencl_kernel_options = [
'local_size',
'queue_number']
1429 for key, value
in options.items():
1430 if key
in valid_opencl_kernel_options:
1431 if key ==
"local_size":
1432 if not isinstance(value, int):
1434 "CodedKern OpenCL option 'local_size' should be "
1436 if key ==
"queue_number":
1437 if not isinstance(value, int):
1439 "CodedKern OpenCL option 'queue_number' should be "
1442 raise AttributeError(
1443 f
"CodedKern does not support the OpenCL option '{key}'. "
1444 f
"The supported options are: "
1445 f
"{valid_opencl_kernel_options}.")
1450 return "kern call: " + self.
_name_name
1455 :returns: The name of the Fortran module that contains this kernel
1463 :returns: the name to use in the DAG for this node.
1466 _, position = self._find_position(self.ancestor(Routine))
1467 return f
"kernel_{self.name}_{position}"
1472 :returns: whether or not this kernel is being module-inlined.
1477 @module_inline.setter
1480 Setter for whether or not to module-inline this kernel.
1482 :param bool value: whether or not to module-inline this kernel.
1484 if value
is not True:
1486 f
"The module inline parameter only accepts the type boolean "
1487 f
"'True' since module-inlining is irreversible. But found:"
1496 my_schedule = self.ancestor(InvokeSchedule)
1497 for kernel
in my_schedule.walk(Kern):
1500 elif kernel.name == self.
namenamenamename
and kernel.module_inline != value:
1501 kernel.module_inline = value
1504 ''' Returns the name of this node with (optional) control codes
1505 to generate coloured output in a terminal that supports it.
1507 :param bool colour: whether or not to include colour control codes.
1509 :returns: description of this node, possibly coloured.
1512 return (self.coloured_name(colour) +
" " + self.
namenamenamename +
"(" +
1513 self.
argumentsarguments.names +
") " +
"[module_inline=" +
1518 In-place replacement of CodedKern concept into language level
1519 PSyIR constructs. The CodedKern is implemented as a Call to a
1520 routine with the appropriate arguments.
1522 :returns: the lowered version of this node.
1523 :rtype: :py:class:`psyclone.psyir.node.Node`
1526 symtab = self.ancestor(InvokeSchedule).symbol_table
1537 rsymbol = symtab.lookup(self.
_name_name, scope_limit=symtab.node)
1539 csymbol = symtab.find_or_create(
1541 symbol_type=ContainerSymbol)
1542 rsymbol = symtab.new_symbol(
1544 symbol_type=RoutineSymbol,
1548 interface=ImportInterface(csymbol))
1552 rsymbol = self.scope.symbol_table.lookup(self.
_name_name)
1553 except KeyError
as err:
1555 f
"Cannot generate this kernel call to '{self.name}' "
1556 f
"because it is marked as module-inlined but no such "
1557 f
"subroutine exists in this module.")
from err
1561 call_node = Call.create(rsymbol, self.
argumentsarguments.psyir_expressions())
1564 self.replace_with(call_node)
1568 ''' Returns the argument that has INC access. Raises a
1569 FieldNotFoundError if none is found.
1572 :raises FieldNotFoundError: if none is found.
1573 :returns: a Fortran argument name.
1575 for arg
in self.
argumentsarguments.args:
1576 if arg.access == AccessType.INC:
1581 f
"{AccessType.INC.api_specific_name()} "
1587 Generate and return the fparser2 AST of the kernel source.
1589 :returns: fparser2 AST of the Fortran file containing this kernel.
1590 :rtype: :py:class:`fparser.two.Fortran2003.Program`
1592 from fparser.common.readfortran
import FortranStringReader
1593 from fparser.two
import parser
1600 my_parser = parser.ParserFactory().create(std=
"f2008")
1602 reader = FortranStringReader(fortran)
1603 self.
_fp2_ast_fp2_ast = my_parser(reader)
1607 def _new_name(original, tag, suffix):
1609 Construct a new name given the original, a tag and a suffix (which
1610 may or may not terminate the original name). If suffix is present
1611 in the original name then the `tag` is inserted before it.
1613 :param str original: The original name
1614 :param str tag: Tag to insert into new name
1615 :param str suffix: Suffix with which to end new name.
1616 :returns: New name made of original + tag + suffix
1619 if original.endswith(suffix):
1620 return original[:-len(suffix)] + tag + suffix
1621 return original + tag + suffix
1625 Writes the (transformed) AST of this kernel to file and resets the
1626 'modified' flag to False. By default (config.kernel_naming ==
1627 "multiple"), the kernel is re-named so as to be unique within
1628 the kernel output directory stored within the configuration
1629 object. Alternatively, if config.kernel_naming is "single"
1630 then no re-naming and output is performed if there is already
1631 a transformed copy of the kernel in the output dir. (In this
1632 case a check is performed that the transformed kernel already
1633 present is identical to the one that we would otherwise write
1634 to file. If this is not the case then we raise a GenerationError.)
1636 :raises GenerationError: if config.kernel_naming == "single" and a \
1637 different, transformed version of this \
1638 kernel is already in the output directory.
1639 :raises NotImplementedError: if the kernel has been transformed but \
1640 is also flagged for module-inlining.
1645 config = Config.get()
1655 if orig_mod_name.lower().endswith(
"_mod"):
1656 old_base_name = orig_mod_name[:-4]
1658 old_base_name = orig_mod_name[:]
1671 new_suffix += f
"_{name_idx}"
1672 new_name = old_base_name + new_suffix +
"_mod.f90"
1678 os.path.join(config.kernel_output_dir, new_name),
1679 os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1680 except (OSError, IOError):
1683 if config.kernel_naming ==
"single":
1701 check_global_constraints=config.backend_checks_enabled)
1706 fll = FortLineLength()
1707 new_kern_code = fll.process(new_kern_code)
1714 with open(os.path.join(config.kernel_output_dir,
1715 new_name),
"r")
as ffile:
1716 kern_code = ffile.read()
1717 if kern_code != new_kern_code:
1719 f
"A transformed version of this Kernel "
1720 f
"'{self._module_name + '''.f90'''}' already exists "
1721 f
"in the kernel-output directory "
1722 f
"({config.kernel_output_dir}) but is not the "
1723 f
"same as the current, transformed kernel and the "
1724 f
"kernel-renaming scheme is set to "
1725 f
"'{config.kernel_naming}'. (If you wish to"
1726 f
" generate a new, unique kernel for every kernel "
1727 f
"that is transformed then use "
1728 f
"'--kernel-renaming multiple'.)")
1731 os.write(fdesc, new_kern_code.encode())
1735 def _rename_psyir(self, suffix):
1736 '''Rename the PSyIR module and kernel names by adding the supplied
1737 suffix to the names. This change affects the KernCall and
1738 KernelSchedule nodes as well as the kernel metadata declaration.
1740 :param str suffix: the string to insert into the quantity names.
1745 container = kern_schedule.ancestor(Container)
1752 new_kern_name = self.
_new_name_new_name(orig_kern_name, suffix,
"_code")
1753 new_mod_name = self.
_new_name_new_name(orig_mod_name, suffix,
"_mod")
1759 kern_schedule.name = new_kern_name[:]
1760 container.name = new_mod_name[:]
1764 kern_symbol = kern_schedule.symbol_table.lookup(orig_kern_name)
1765 container.symbol_table.rename_symbol(kern_symbol, new_kern_name)
1774 if hasattr(container,
"metadata"):
1775 container.metadata.procedure_name = new_kern_name[:]
1780 container_table = container.symbol_table
1781 for sym
in container_table.datatypesymbols:
1782 if isinstance(sym.datatype, UnsupportedFortranType):
1783 new_declaration = sym.datatype.declaration.replace(
1784 orig_kern_name, new_kern_name)
1788 partial_datatype=sym.datatype.partial_datatype)
1794 :returns: Whether or not this kernel has been modified (transformed).
1800 def modified(self, value):
1802 Setter for whether or not this kernel has been modified.
1804 :param bool value: True if kernel modified, False otherwise.
1810 '''A class representing a kernel that is inlined.
1811 It has one child which stores the Schedule for the child nodes.
1813 :param psyir_nodes: the list of PSyIR nodes that represent the body
1815 :type psyir_nodes: list of :py:class:`psyclone.psyir.nodes.Node`
1816 :param parent: the parent of this node in the PSyIR.
1817 :type parent: sub-class of :py:class:`psyclone.psyir.nodes.Node`
1820 _children_valid_format =
"Schedule"
1821 _text_name =
"InlinedKern"
1824 def __init__(self, psyir_nodes, parent=None):
1826 Node.__init__(self, parent=parent)
1827 schedule = Schedule(children=psyir_nodes, parent=self)
1832 def _validate_child(position, child):
1834 :param int position: the position to be validated.
1835 :param child: a child to be validated.
1836 :type child: :py:class:`psyclone.psyir.nodes.Node`
1838 :return: whether the given child and position are valid for this node.
1842 return position == 0
and isinstance(child, Schedule)
1847 :returns: list of the variable (names) that are local to this kernel \
1848 (and must therefore be e.g. threadprivate if doing OpenMP)
1853 ''' Returns the name of this node with (optional) control codes
1854 to generate coloured output in a terminal that supports it.
1856 :param bool colour: whether or not to include colour control codes.
1858 :returns: description of this node, possibly coloured.
1861 return self.coloured_name(colour) +
"[]"
1866 Parent class for all built-ins (field operations for which the user
1867 does not have to provide an implementation).
1870 _text_name =
"BuiltIn"
1885 :returns: the name to use in the DAG for this node.
1888 _, position = self._find_position(self.ancestor(Routine))
1889 return f
"builtin_{self.name}_{position}"
1891 def load(self, call, arguments, parent=None):
1892 ''' Set-up the state of this BuiltIn call '''
1893 name = call.ktype.name
1894 super(BuiltIn, self).__init__(parent, call, name, arguments)
1897 '''Variables that are local to this built-in and therefore need to be
1898 made private when parallelising using OpenMP or similar. By default
1899 builtin's do not have any local variables so set to nothing'''
1905 Arguments abstract base class.
1907 :param parent_call: kernel call with which the arguments are associated.
1908 :type parent_call: sub-class of :py:class:`psyclone.psyGen.Kern`
1910 def __init__(self, parent_call):
1917 self.
_args_args = []
1922 :returns: the PSyIR expressions representing this Argument list.
1923 :rtype: list of :py:class:`psyclone.psyir.nodes.Node`
1930 :returns: the Algorithm-visible kernel arguments in a \
1931 comma-delimited string.
1934 return ",".join([arg.name
for arg
in self.
argsargs])
1938 return self.
_args_args
1942 Returns an argument that can be iterated over, i.e. modified
1943 (has WRITE, READWRITE or INC access), but not the result of
1944 a reduction operation.
1946 :returns: a Fortran argument name
1948 :raises GenerationError: if none such argument is found.
1951 for arg
in self.
_args_args:
1952 if arg.access
in AccessType.all_write_accesses()
and \
1953 arg.access
not in AccessType.get_valid_reduction_modes():
1956 "we assume there is at least one writer, "
1957 "reader/writer, or increment as an argument")
1962 :returns: the list of quantities that must be available on an \
1963 OpenACC device before the associated kernel can be launched
1966 raise NotImplementedError(
1967 "Arguments.acc_args must be implemented in sub-class")
1972 :returns: the list of scalar quantities belonging to this object
1975 raise NotImplementedError(
1976 "Arguments.scalars must be implemented in sub-class")
1979 ''' Abstract method to append KernelArguments to the Argument
1982 :param str name: name of the appended argument.
1983 :param str argument_type: type of the appended argument.
1985 raise NotImplementedError(
1986 "Arguments.append must be implemented in sub-class")
1990 '''A helper class to simplify the determination of dependencies due to
1991 overlapping accesses to data associated with instances of the
1997 '''Store the argument associated with the instance of this class and
1998 the Call, HaloExchange or GlobalSum (or a subclass thereof)
1999 instance with which the argument is associated.
2001 :param arg: the argument that we are concerned with. An \
2002 argument can be found in a `Kern` a `HaloExchange` or a \
2003 `GlobalSum` (or a subclass thereof)
2004 :type arg: :py:class:`psyclone.psyGen.Argument`
2011 self.
_call_call = arg.call
2020 '''Determine whether the accesses to the provided argument overlap
2021 with the accesses of the source argument. Overlap means that
2022 the accesses share at least one memory location. For example,
2023 the arguments both access the 1st index of the same field.
2025 We do not currently deal with accesses to a subset of an
2026 argument (unless it is a vector). This distinction will need
2027 to be added once loop splitting is supported.
2029 :param arg: the argument to compare with our internal argument
2030 :type arg: :py:class:`psyclone.psyGen.Argument`
2031 :return bool: True if there are overlapping accesses between \
2032 arguments (i.e. accesses share at least one memory \
2033 location) and False if not.
2036 if self.
_arg_arg.name != arg.name:
2040 if isinstance(self.
_call_call, HaloExchange)
and \
2041 isinstance(arg.call, HaloExchange)
and \
2042 (self.
_arg_arg.vector_size > 1
or arg.vector_size > 1):
2049 if self.
_arg_arg.vector_size != arg.vector_size:
2051 f
"DataAccess.overlaps(): vector sizes differ for field "
2052 f
"'{arg.name}' in two halo exchange calls. Found "
2053 f
"'{self._arg.vector_size}' and '{arg.vector_size}'")
2054 if self.
_call_call.vector_index != arg.call.vector_index:
2061 '''Reset internal state to allow re-use of the object for a different
2073 '''Record any overlap between accesses to the supplied argument and
2074 the internal argument. Overlap means that the accesses to the
2075 two arguments share at least one memory location. If the
2076 overlap results in all of the accesses to the internal
2077 argument being covered (either directly or as a combination
2078 with previous arguments) then ensure that the covered() method
2079 returns True. Covered means that all memory accesses by the
2080 internal argument have at least one corresponding access by
2081 the supplied arguments.
2083 :param arg: the argument used to compare with our internal \
2084 argument in order to update coverage information
2085 :type arg: :py:class:`psyclone.psyGen.Argument`
2093 if isinstance(arg.call, HaloExchange)
and \
2094 (hasattr(self.
_arg_arg,
'vector_size')
and self.
_arg_arg.vector_size > 1):
2099 if isinstance(self.
_call_call, HaloExchange):
2106 f
"DataAccess:update_coverage() The halo exchange vector "
2107 f
"indices for '{self._arg.name}' are the same. This "
2108 f
"should never happen")
2120 "DataAccess:update_coverage() Found more than one "
2121 "dependent halo exchange with the same vector index")
2132 '''Returns True if all of the data associated with this argument has
2133 been covered by the arguments provided in update_coverage
2135 :return bool: True if all of an argument is covered by \
2136 previous accesses and False if not.
2144 Argument base class. Captures information on an argument that is passed
2145 to a Kernel from an Invoke.
2147 :param call: the kernel call that this argument is associated with.
2148 :type call: :py:class:`psyclone.psyGen.Kern`
2149 :param arg_info: Information about this argument collected by \
2151 :type arg_info: :py:class:`psyclone.parse.algorithm.Arg`
2152 :param access: the way in which this argument is accessed in \
2153 the 'Kern'. Valid values are specified in the config \
2154 object of the current API.
2159 def __init__(self, call, arg_info, access):
2160 self.
_call_call = call
2161 if arg_info
is not None:
2162 self.
_text_text = arg_info.text
2164 self.
_form_form = arg_info.form
2165 self.
_is_literal_is_literal = arg_info.is_literal()
2167 self.
_text_text =
""
2169 self.
_form_form =
""
2183 def _complete_init(self, arg_info):
2184 '''Provides the initialisation of name, text and the declaration of
2185 symbols in the symbol table if required. This initialisation
2186 is not performed in the constructor as subclasses may need to
2187 perform additional initialisation before infer_datatype is
2188 called (in order to determine the values of precision,
2189 data_type and module_name).
2191 :param arg_info: Information about this argument collected by \
2193 :type arg_info: :py:class:`psyclone.parse.algorithm.Arg`
2200 self.
_name_name = arg_info.text
2201 self.
_text_text =
None
2205 if self.
_call_call
and self.
_call_call.ancestor(InvokeSchedule):
2206 symtab = self.
_call_call.ancestor(InvokeSchedule).symbol_table
2209 previous_arguments = symtab.argument_list
2212 tag =
"AlgArgs_" + self.
_text_text
2215 argument_access = ArgumentInterface.Access.READWRITE
2223 if hasattr(self,
'vector_size')
and self.vector_size > 1:
2224 data_type = ArrayType(data_type, [self.vector_size])
2226 new_argument = symtab.find_or_create_tag(
2227 tag, root_name=self.
_orig_name_orig_name, symbol_type=DataSymbol,
2229 interface=ArgumentInterface(argument_access))
2230 self.
_name_name = new_argument.name
2234 if (isinstance(new_argument.interface, ArgumentInterface)
and
2235 new_argument
not in previous_arguments):
2236 symtab.specify_argument_list(previous_arguments +
2242 :returns: the PSyIR expression represented by this Argument.
2243 :rtype: :py:class:`psyclone.psyir.nodes.Node`
2248 ''' Infer the datatype of this argument using the API rules. If no
2249 specialisation of this method has been provided make the type
2250 UnresolvedType for now (it may be provided later in the execution).
2252 :returns: the datatype of this argument.
2253 :rtype: :py:class::`psyclone.psyir.symbols.DataType`
2256 return UnresolvedType()
2274 def is_literal(self):
2275 return self._is_literal
2282 def access(self, value):
2283 '''Set the access type for this argument.
2285 :param value: new access type.
2286 :type value: :py:class:`psyclone.core.access_type.AccessType`.
2288 :raises InternalError: if value is not an AccessType.
2291 if not isinstance(value, AccessType):
2292 raise InternalError(f
"Invalid access type '{value}' of type "
2300 Returns the type of the argument. APIs that do not have this
2301 concept can use this base class version which just returns "field"
2302 in all cases. APIs with this concept can override this method.
2304 :returns: the API type of the kernel argument.
2314 Abstract property for the intrinsic type of the argument with
2315 specific implementations in different APIs.
2317 :returns: the intrinsic type of this argument.
2325 :returns: the precision of this argument. Default value is None, \
2326 explicit implementation is left to a specific API.
2327 :rtype: str or NoneType
2335 :returns: the data type of this argument. Default value is None, \
2336 explicit implementation is left to a specific API.
2337 :rtype: str or NoneType
2345 :returns: the name of the Fortran module that contains definitions \
2346 for the argument data type. Default value is None, \
2347 explicit implementation is left to a specific API.
2348 :rtype: str or NoneType
2356 ''' Return the call that this argument is associated with '''
2357 return self.
_call_call
2361 ''' set the node that this argument is associated with '''
2362 self.
_call_call = value
2365 '''Returns the preceding argument that this argument has a direct
2366 dependence with, or None if there is not one. The argument may
2367 exist in a call, a haloexchange, or a globalsum.
2369 :returns: the first preceding argument that has a dependence \
2371 :rtype: :py:class:`psyclone.psyGen.Argument`
2374 nodes = self.
_call_call.preceding(reverse=
True)
2378 '''Returns a list of following write arguments that this argument has
2379 dependencies with. The arguments may exist in a call, a
2380 haloexchange (unless `ignore_halos` is `True`), or a globalsum. If
2381 none are found then return an empty list. If self is not a
2382 reader then return an empty list.
2384 :param bool ignore_halos: if `True` then any write dependencies \
2385 involving a halo exchange are ignored. Defaults to `False`.
2387 :returns: a list of arguments that have a following write \
2388 dependence on this argument.
2389 :rtype: list of :py:class:`psyclone.psyGen.Argument`
2392 nodes = self.
_call_call.following()
2397 '''Returns a list of previous write arguments that this argument has
2398 dependencies with. The arguments may exist in a call, a
2399 haloexchange (unless `ignore_halos` is `True`), or a globalsum. If
2400 none are found then return an empty list. If self is not a
2401 reader then return an empty list.
2403 :param ignore_halos: if `True` then any write dependencies \
2404 involving a halo exchange are ignored. Defaults to `False`.
2405 :type ignore_halos: bool
2407 :returns: a list of arguments that have a preceding write \
2408 dependence on this argument.
2409 :rtype: list of :py:class:`psyclone.psyGen.Argument`
2412 nodes = self.
_call_call.preceding(reverse=
True)
2417 '''Returns the following argument that this argument has a direct
2418 dependence on, or `None` if there is not one. The argument may
2419 exist in a call, a haloexchange, or a globalsum.
2421 :returns: the first following argument that has a dependence \
2423 :rtype: :py:class:`psyclone.psyGen.Argument`
2426 nodes = self.
_call_call.following()
2430 '''Returns a list of following read arguments that this argument has
2431 dependencies with. The arguments may exist in a call, a
2432 haloexchange, or a globalsum. If none are found then
2433 return an empty list. If self is not a writer then return an
2436 :returns: a list of following arguments that have a read \
2437 dependence on this argument.
2438 :rtype: list of :py:class:`psyclone.psyGen.Argument`
2441 nodes = self.
_call_call.following()
2444 def _find_argument(self, nodes):
2445 '''Return the first argument in the list of nodes that has a
2446 dependency with self. If one is not found return None
2448 :param nodes: the list of nodes that this method examines.
2449 :type nodes: list of :py:class:`psyclone.psyir.nodes.Node`
2451 :returns: An argument object or None.
2452 :rtype: :py:class:`psyclone.psyGen.Argument`
2455 nodes_with_args = [x
for x
in nodes
if
2456 isinstance(x, (Kern, HaloExchange, GlobalSum))]
2457 for node
in nodes_with_args:
2458 for argument
in node.args:
2463 def _find_read_arguments(self, nodes):
2464 '''Return a list of arguments from the list of nodes that have a read
2465 dependency with self. If none are found then return an empty
2466 list. If self is not a writer then return an empty list.
2468 :param nodes: the list of nodes that this method examines.
2469 :type nodes: list of :py:class:`psyclone.psyir.nodes.Node`
2471 :returns: a list of arguments that have a read dependence on \
2473 :rtype: list of :py:class:`psyclone.psyGen.Argument`
2476 if self.
accessaccessaccess
not in AccessType.all_write_accesses():
2481 nodes_with_args = [x
for x
in nodes
if
2482 isinstance(x, (Kern, HaloExchange, GlobalSum))]
2485 for node
in nodes_with_args:
2486 for argument
in node.args:
2488 if argument.access
in AccessType.all_read_accesses()
and \
2489 access.overlaps(argument):
2490 arguments.append(argument)
2491 if argument.access
in AccessType.all_write_accesses():
2492 access.update_coverage(argument)
2502 def _find_write_arguments(self, nodes, ignore_halos=False):
2503 '''Return a list of arguments from the list of nodes that have a write
2504 dependency with self. If none are found then return an empty
2505 list. If self is not a reader then return an empty list.
2507 :param nodes: the list of nodes that this method examines.
2508 :type nodes: list of :py:class:`psyclone.psyir.nodes.Node`
2510 :param bool ignore_halos: if `True` then any write dependencies \
2511 involving a halo exchange are ignored. Defaults to `False`.
2512 :returns: a list of arguments that have a write dependence with \
2514 :rtype: list of :py:class:`psyclone.psyGen.Argument`
2517 if self.
accessaccessaccess
not in AccessType.all_read_accesses():
2522 nodes_with_args = [x
for x
in nodes
if
2523 isinstance(x, (Kern, GlobalSum))
or
2524 (isinstance(x, HaloExchange)
and not ignore_halos)]
2527 for node
in nodes_with_args:
2528 for argument
in node.args:
2530 if argument.access
not in AccessType.all_write_accesses():
2533 if not access.overlaps(argument):
2536 arguments.append(argument)
2537 access.update_coverage(argument)
2540 if not isinstance(node, HaloExchange)
and \
2543 "Found a writer dependence but there are already "
2544 "dependencies. This should not happen.")
2550 "Argument()._field_write_arguments() There are no more nodes "
2551 "but there are already dependencies. This should not happen.")
2555 def _depends_on(self, argument):
2556 '''If there is a dependency between the argument and self then return
2557 True, otherwise return False. We consider there to be a
2558 dependency between two arguments if the names are the same and
2559 if one reads and one writes, or if both write. Dependencies
2560 are often defined as being read-after-write (RAW),
2561 write-after-read (WAR) and write after write (WAW). These
2562 dependencies can be considered to be forward dependencies, in
2563 the sense that RAW means that the read is after the write in
2564 the schedule. Similarly for WAR and WAW. We capture these
2565 dependencies in this method. However we also capture
2566 dependencies in the opposite direction (backward
2567 dependencies). These are the same dependencies as forward
2568 dependencies but are reversed. One could consider these to be
2569 read-before-write, write-before-read, and
2570 write-before-write. The terminology of forward and backward to
2571 indicate whether the argument we depend on is after or before
2572 us in the schedule is borrowed from loop dependence analysis
2573 where a forward dependence indicates a dependence in a future
2574 loop iteration and a backward dependence indicates a
2575 dependence on a previous loop iteration. Note, we currently
2576 assume that any read or write to an argument results in a
2577 dependence i.e. we do not consider the internal structure of
2578 the argument (e.g. it may be an array). However, this
2579 assumption is OK as all elements of an array are typically
2580 accessed. However, we may need to revisit this when we change
2581 the iteration spaces of loops e.g. for overlapping
2582 communication and computation.
2584 :param argument: the argument we will check to see whether \
2585 there is a dependence on this argument instance (self).
2586 :type argument: :py:class:`psyclone.psyGen.Argument`
2588 :returns: True if there is a dependence and False if not.
2592 if argument.name == self.
_name_name:
2593 if self.
accessaccessaccess
in AccessType.all_write_accesses()
and \
2594 argument.access
in AccessType.all_read_accesses():
2596 if self.
accessaccessaccess
in AccessType.all_read_accesses()
and \
2597 argument.access
in AccessType.all_write_accesses():
2599 if self.
accessaccessaccess
in AccessType.all_write_accesses()
and \
2600 argument.access
in AccessType.all_write_accesses():
2607 This class provides information about individual kernel-call
2608 arguments as specified by the kernel argument metadata and the
2609 kernel invocation in the Algorithm layer.
2611 :param arg: information obtained from the metadata for this kernel \
2613 :type arg: :py:class:`psyclone.parse.kernel.Descriptor`
2614 :param arg_info: information on how this argument is specified in \
2615 the Algorithm layer.
2616 :type arg_info: :py:class:`psyclone.parse.algorithm.Arg`
2617 :param call: the PSyIR kernel node to which this argument pertains.
2618 :type call: :py:class:`psyclone.psyGen.Kern`
2621 def __init__(self, arg, arg_info, call):
2623 super().__init__(call, arg_info, arg.access)
2627 return self.
_arg_arg.function_space
2631 return self.
_arg_arg.stencil
2636 ''':returns: whether this variable is a scalar variable or not.
2642 :returns: the position of the corresponding argument descriptor in \
2643 the kernel metadata.
2646 return self.
_arg_arg.metadata_index
2651 This class provides information about, and access, to the available
2652 transformations in this implementation of PSyclone. New transformations
2653 will be picked up automatically as long as they subclass the abstract
2654 Transformation class.
2658 >>> from psyclone.psyGen import TransInfo
2661 There is 1 transformation available:
2662 1: SwapTrans, A test transformation
2663 >>> # accessing a transformation by index
2664 >>> trans = t.get_trans_num(1)
2665 >>> # accessing a transformation by name
2666 >>> trans = t.get_trans_name("SwapTrans")
2671 ''' if module and/or baseclass are provided then use these else use
2672 the default module "Transformations" and the default base_class
2683 from psyclone
import transformations
2686 module = transformations
2687 if base_class
is None:
2688 base_class = Transformation
2695 for my_class
in self.
_classes_classes:
2696 my_object = my_class()
2697 self.
_objects_objects.append(my_object)
2698 self.
_obj_map_obj_map[my_object.name] = my_object
2705 my_object = LoopFuseTrans()
2708 if isinstance(my_object, base_class)
and module == transformations:
2709 self.
_objects_objects.append(LoopFuseTrans())
2714 ''' return a string with a human readable list of the available
2717 if len(self.
_objects_objects) == 1:
2718 result =
"There is 1 transformation available:"
2720 result = (f
"There are {len(self._objects)} transformations "
2722 result += os.linesep
2723 for idx, my_object
in enumerate(self.
_objects_objects):
2724 result +=
" " + str(idx+1) +
": " + my_object.name +
": " + \
2725 str(my_object) + os.linesep
2730 ''' return the number of transformations available '''
2734 ''' return the transformation with this number (use list() first to
2735 see available transformations) '''
2736 if number < 1
or number > len(self.
_objects_objects):
2738 return self.
_objects_objects[number-1]
2741 ''' return the transformation with this name (use list() first to see
2742 available transformations) '''
2747 f
"but expected one of "
2748 f
"{self._obj_map.keys()}")
2750 def _find_subclasses(self, module, base_class):
2751 ''' return a list of classes defined within the specified module that
2752 are a subclass of the specified baseclass. '''
2754 return [cls
for name, cls
in inspect.getmembers(module)
2755 if inspect.isclass(cls)
and not inspect.isabstract(cls)
and
2756 issubclass(cls, base_class)
and cls
is not base_class]
2760 '''Abstract baseclass for a transformation. Uses the abc module so it
2761 can not be instantiated.
2767 :returns: the transformation's class name.
2771 return type(self).__name__
2775 '''Abstract method that applies the transformation. This function
2776 must be implemented by each transform. As a minimum each apply
2777 function must take a node to which the transform is applied, and
2778 a dictionary of additional options, which will also be passed on
2779 to the validate functions. This dictionary is used to provide
2780 optional parameters, and also to modify the behaviour of
2781 validation of transformations: for example, if the user knows that
2782 a transformation can correctly be applied in a specific case, but
2783 the more generic code validation would not allow this. Validation
2784 functions should check for a key in the options dictionary to
2785 disable certain tests. Those keys will be documented in each
2786 apply() and validate() function.
2788 Note that some apply() functions might take a slightly different
2791 :param node: The node (or list of nodes) for the transformation \
2792 - specific to the actual transform used.
2793 :type node: depends on actual transformation
2794 :param options: a dictionary with options for transformations.
2795 :type options: Optional[Dict[str, Any]]
2800 '''Method that validates that the input data is correct.
2801 It will raise exceptions if the input data is incorrect. This
2802 function needs to be implemented by each transformation.
2804 The validate function can be called by the user independent of
2805 the apply() function, but it will automatically be executed as
2806 part of an apply() call.
2808 As minimum each validate function must take a node to which the
2809 transform is applied and a dictionary of additional options.
2810 This dictionary is used to provide optional parameters and also
2811 to modify the behaviour of validation: for example, if the user
2812 knows that a transformation can correctly be applied in a specific
2813 case but the more generic code validation would not allow this.
2814 Validation functions should check for particular keys in the options
2815 dict in order to disable certain tests. Those keys will be documented
2816 in each apply() and validate() function as 'options["option-name"]'.
2818 Note that some validate functions might take a slightly different
2821 :param node: The node (or list of nodes) for the transformation \
2822 - specific to the actual transform used.
2823 :type node: depends on actual transformation
2824 :param options: a dictionary with options for transformations.
2825 :type options: Optional[Dict[str, Any]]
2830 class DummyTransformation(Transformation):
2831 '''Dummy transformation use elsewhere to keep pyreverse happy.'''
2842 __all__ = [
'PSyFactory',
'PSy',
'Invokes',
'Invoke',
'InvokeSchedule',
2843 'GlobalSum',
'HaloExchange',
'Kern',
'CodedKern',
'InlinedKern',
2844 'BuiltIn',
'Arguments',
'DataAccess',
'Argument',
'KernelArgument',
2845 'TransInfo',
'Transformation',
'DummyTransformation']
def backward_write_dependencies(self, ignore_halos=False)
def _depends_on(self, argument)
def _find_write_arguments(self, nodes, ignore_halos=False)
def _find_read_arguments(self, nodes)
def forward_read_dependencies(self)
def psyir_expression(self)
def _find_argument(self, nodes)
def forward_dependence(self)
def backward_dependence(self)
def forward_write_dependencies(self, ignore_halos=False)
def iteration_space_arg(self)
def append(self, name, argument_type)
def psyir_expressions(self)
def load(self, call, arguments, parent=None)
def module_inline(self, value)
def set_opencl_options(self, options)
def lower_to_language_level(self)
def node_str(self, colour=True)
def _rename_psyir(self, suffix)
def get_kernel_schedule(self)
def _new_name(original, tag, suffix)
def incremented_arg(self)
def rename_and_write(self)
def modified(self, value)
def update_coverage(self, arg)
def node_str(self, colour=True)
def check_vector_halos_differ(self, node)
def node_str(self, colour=True)
def node_str(self, colour=True)
def node_str(self, colour=True)
def gen_code(self, parent)
def unique_declns_by_intent(self, argument_types, intrinsic_type=None)
def unique_declarations(self, argument_types, access=None, intrinsic_type=None)
def __init__(self, alg_invocation, idx, schedule_class, invokes, reserved_names=None)
def gen_code(self, parent)
def first_access(self, arg_name)
def __init__(self, alg_calls, invoke_cls, psy)
def get(self, invoke_name)
def gen_code(self, parent)
def arg_descriptors(self, obj)
def reference_accesses(self, var_accesses)
def node_str(self, colour=True)
def zero_reduction_variable(self, parent, position=None)
def reduction_sum_loop(self, parent)
def arg_descriptors(self)
def _reduction_reference(self)
def reprod_reduction(self)
def local_reduction_name(self)
def create(self, invoke_info)
def get_trans_num(self, number)
def get_trans_name(self, name)
def __init__(self, module=None, base_class=None)
def _find_subclasses(self, module, base_class)