40 '''This module implements the PSyclone GOcean 1.0 API by specialising
41 the required base classes for both code generation (PSy, Invokes,
42 Invoke, InvokeSchedule, Loop, Kern, Arguments and KernelArgument)
43 and parsing (Descriptor and KernelType). It adds a
44 GOKernelGridArgument class to capture information on kernel arguments
45 that supply properties of the grid (and are generated in the PSy
52 from fparser.two.Fortran2003
import NoMatchError, Nonlabel_Do_Stmt
53 from fparser.two.parser
import ParserFactory
62 DeclGen, UseGen, ModuleGen, SubroutineGen, TypeDeclGen, PSyIRGen)
67 PSy, Invokes, Invoke, InvokeSchedule, CodedKern, Arguments, Argument,
68 KernelArgument, args_filter, AccessType, HaloExchange)
72 Literal, Schedule, KernelSchedule, StructureReference, IntrinsicCall,
73 Reference, Call, Assignment, ACCEnterDataDirective, ACCParallelDirective,
74 ACCKernelsDirective, Container, ACCUpdateDirective, Routine,
77 ScalarType, INTEGER_TYPE, DataSymbol, RoutineSymbol, ContainerSymbol,
78 UnresolvedType, DataTypeSymbol, UnresolvedInterface, BOOLEAN_TYPE,
85 The GOcean 1.0 specific PSy class. This creates a GOcean specific
86 invokes object (which controls all the required invocation calls).
87 Also overrides the PSy gen method so that we generate GOcean-
88 specific PSy module code.
90 :param invoke_info: An object containing the required invocation \
91 information for code optimisation and generation.
92 :type invoke_info: :py:class:`psyclone.parse.FileInfo`
95 def __init__(self, invoke_info):
96 PSy.__init__(self, invoke_info)
99 field_sym = ContainerSymbol(
"field_mod")
100 field_sym.wildcard_import =
True
101 self.
containercontainer.symbol_table.add(field_sym)
102 kind_params_sym = ContainerSymbol(
"kind_params_mod")
103 kind_params_sym.wildcard_import =
True
104 self.
containercontainer.symbol_table.add(kind_params_sym)
112 Generate PSy code for the GOcean api v.1.0.
120 psy_module.add(
UseGen(psy_module, name=
"kind_params_mod"))
122 psy_module.add(
UseGen(psy_module, name=
"field_mod"))
123 self.
invokesinvokes.gen_code(psy_module)
124 return psy_module.root
129 The GOcean specific invokes class. This passes the GOcean specific
130 invoke class to the base class so it creates the one we require.
132 :param alg_calls: The Invoke calls discovered in the Algorithm layer.
133 :type alg_calls: OrderedDict of :py:class:`psyclone.parse.InvokeCall` \
135 :param psy: the PSy object containing this GOInvokes object.
136 :type psy: :py:class:`psyclone.gocean1p0.GOPSy`
139 def __init__(self, alg_calls, psy):
141 Invokes.__init__(self, alg_calls, GOInvoke, psy)
153 for kern_call
in invoke.schedule.coded_kernels():
156 if kern_call.index_offset !=
"go_offset_any":
158 for offset
in index_offsets:
159 if offset != kern_call.index_offset:
161 f
"Meta-data error in kernel {kern_call.name}: "
162 f
"INDEX_OFFSET of '{kern_call.index_offset}' "
163 f
"does not match that ({offset}) of other "
164 f
"kernels. This is not supported.")
167 index_offsets.append(kern_call.index_offset)
171 GOcean redefines the Invokes.gen_code() to start using the PSyIR
172 backend when possible. In cases where the backend can not be used yet
173 (e.g. OpenCL and PSyDataNodes) the parent class will be called. This
174 is a temporary workaround to avoid modifying the generator file while
175 other APIs still use the f2pygen module for code generation.
176 Once the PSyIR backend has generated an output, this is added into a
177 f2pygen PSyIRGen block in the f2pygen AST for each Invoke in the
180 :param parent: the parent node in the f2pygen AST to which to add \
182 :type parent: `psyclone.f2pygen.ModuleGen`
190 invoke.schedule.root.lower_to_language_level()
195 for child
in invoke.schedule.root.children:
201 The GOcean specific invoke class. This passes the GOcean specific
202 schedule class to the base class so it creates the one we require.
203 A set of GOcean infrastructure reserved names are also passed to
204 ensure that there are no name clashes. Also overrides the gen_code
205 method so that we generate GOcean specific invocation code and
206 provides three methods which separate arguments that are arrays from
207 arguments that are {integer, real} scalars.
209 :param alg_invocation: Node in the AST describing the invoke call.
210 :type alg_invocation: :py:class:`psyclone.parse.InvokeCall`
211 :param int idx: The position of the invoke in the list of invokes \
212 contained in the Algorithm.
213 :param invokes: the Invokes object containing this GOInvoke \
215 :type invokes: :py:class:`psyclone.gocean1p0.GOInvokes`
218 def __init__(self, alg_invocation, idx, invokes):
220 Invoke.__init__(self, alg_invocation, idx, GOInvokeSchedule, invokes)
222 if Config.get().distributed_memory:
225 loop.create_halo_exchanges()
229 ''' find unique arguments that are arrays (defined as those that are
230 field objects as opposed to scalars or properties of the grid). '''
233 for arg
in call.arguments.args:
234 if arg.argument_type ==
'field' and arg.name
not in result:
235 result.append(arg.name)
241 :returns: the unique arguments that are scalars of type integer \
242 (defined as those that are i_scalar 'space').
248 for arg
in args_filter(call.arguments.args, arg_types=[
"scalar"],
249 include_literals=
False):
250 if arg.space.lower() ==
"go_i_scalar" and \
251 arg.name
not in result:
252 result.append(arg.name)
258 Generates GOcean specific invocation code (the subroutine called
259 by the associated invoke call in the algorithm layer). This
260 consists of the PSy invocation subroutine and the declaration of
263 :param parent: the node in the generated AST to which to add content.
264 :type parent: :py:class:`psyclone.f2pygen.ModuleGen`
273 parent.add(invoke_sub)
280 my_decl_arrays =
TypeDeclGen(invoke_sub, datatype=
"r2d_field",
283 invoke_sub.add(my_decl_arrays)
287 for argument
in self.
schedulescheduleschedule.symbol_table.argument_datasymbols:
289 i_args.append(argument.name)
292 my_decl_iscalars =
DeclGen(invoke_sub, datatype=
"INTEGER",
295 invoke_sub.add(my_decl_iscalars)
298 for symbol
in self.
schedulescheduleschedule.symbol_table.automatic_datasymbols:
299 if isinstance(symbol.datatype, ScalarType):
302 datatype=symbol.datatype.intrinsic.name,
303 entity_decls=[symbol.name]))
307 ''' The GOcean specific InvokeSchedule sub-class. We call the base class
308 constructor and pass it factories to create GO-specific calls to both
309 user-supplied kernels and built-ins.
311 :param str name: name of the Invoke.
312 :param alg_calls: list of KernelCalls parsed from the algorithm layer.
313 :type alg_calls: list of :py:class:`psyclone.parse.algorithm.KernelCall`
314 :param reserved_names: optional list of names that are not allowed in the \
315 new InvokeSchedule SymbolTable.
316 :type reserved_names: list of str
317 :param parent: the parent of this node in the PSyIR.
318 :type parent: :py:class:`psyclone.psyir.nodes.Node`
322 _text_name =
"GOInvokeSchedule"
324 def __init__(self, name, alg_calls, reserved_names=None, parent=None):
325 InvokeSchedule.__init__(self, name, GOKernCallFactory,
326 GOBuiltInCallFactory,
327 alg_calls, reserved_names, parent=parent)
332 ''' The GOcean specific PSyLoop class. This passes the GOcean specific
333 single loop information to the base class so it creates the one we
334 require. Adds a GOcean specific setBounds method which tells the loop
335 what to iterate over. Need to harmonise with the topology_name method
338 :param parent: optional parent node (default None).
339 :type parent: :py:class:`psyclone.psyir.nodes.Node`
340 :param str loop_type: loop type - must be 'inner' or 'outer'.
341 :param str field_name: name of the field this loop iterates on.
342 :param str field_space: space of the field this loop iterates on.
343 :param str iteration_space: iteration space of the loop.
345 :raises GenerationError: if the loop is not inserted inside a \
346 GOInvokeSchedule region.
351 def __init__(self, parent, loop_type="", field_name="", field_space="",
352 iteration_space="", index_offset=""):
356 super().__init__(parent=parent,
357 valid_loop_types=const.VALID_LOOP_TYPES)
367 if not self.ancestor(GOInvokeSchedule):
369 "GOLoops must always be constructed with a parent which is"
370 " inside (directly or indirectly) of a GOInvokeSchedule")
376 tag =
"contiguous_kidx"
379 tag =
"noncontiguous_kidx"
382 raise InternalError(f
"While the loop type '{self._loop_type}' is "
383 f
"valid, it is not yet supported.")
389 symtab = self.ancestor(InvokeSchedule).symbol_table
391 self.
variablevariable = symtab.lookup_with_tag(tag)
393 self.
variablevariable = symtab.new_symbol(
394 suggested_name, tag, symbol_type=DataSymbol,
395 datatype=INTEGER_TYPE)
398 if not GOLoop._bounds_lookup:
399 GOLoop.setup_bounds()
402 def create(parent, loop_type, field_name="", field_space="",
403 iteration_space="", index_offset=""):
406 Create a new instance of a GOLoop with the expected children to
407 represent the bounds given by the loop properties.
409 :param parent: parent node of this GOLoop.
410 :type parent: :py:class:`psyclone.psyir.nodes.Node`
411 :param str loop_type: loop type - must be 'inner' or 'outer'.
412 :param str field_name: name of the field this loop iterates on.
413 :param str field_space: space of the field this loop iterates on.
414 :param str iteration_space: iteration space of the loop.
415 :param str index_offset: the grid index offset used by the kernel(s) \
418 :returns: a new GOLoop node (with appropriate child nodes).
419 :rtype: :py:class:`psyclone.gocean1p0.GOLoop`
423 node =
GOLoop(parent, loop_type, field_name, field_space,
424 iteration_space, index_offset)
427 node.addchild(node.lower_bound())
428 node.addchild(node.upper_bound())
429 node.addchild(Literal(
"1", INTEGER_TYPE))
430 node.addchild(Schedule())
435 def field_space(self):
437 :returns: the loop's field space (e.g. CU, CV...).
443 def field_space(self, my_field_space):
444 ''' Sets new value for the field_space and updates the Loop bounds,
445 if these exist, to match the given field_space.
447 :param str my_field_space: new field_space value.
449 :raises TypeError: if the provided field_space is not a string.
450 :raises ValueError: if the provided field_space is not a valid GOcean \
456 if not isinstance(my_field_space, str):
458 f
"Field space must be a 'str' but found "
459 f
"'{type(my_field_space).__name__}' instead.")
461 if my_field_space
not in valid_fs:
463 f
"Invalid string '{my_field_space}' provided for a GOcean "
464 f
"field_space. The valid values are {valid_fs}")
467 if len(self.children) > 1:
468 self.start_expr.replace_with(self.
lower_boundlower_bound())
469 if len(self.children) > 2:
470 self.stop_expr.replace_with(self.
upper_boundupper_bound())
473 def iteration_space(self):
475 :returns: the loop's iteration space (e.g. 'go_internal_pts', \
481 @iteration_space.setter
482 def iteration_space(self, it_space):
483 ''' Sets new value for the iteration_space and updates the Loop bounds,
484 if these exist, to match the given iteration_space.
486 :param str it_space: new iteration_space value.
488 :raises TypeError: if the provided it_space is not a string.
491 if not isinstance(it_space, str):
493 f
"Iteration space must be a 'str' but found "
494 f
"'{type(it_space).__name__}' instead.")
500 if len(self.children) > 1:
501 self.start_expr.replace_with(self.
lower_boundlower_bound())
502 if len(self.children) > 2:
503 self.stop_expr.replace_with(self.
upper_boundupper_bound())
508 :returns: the GOcean loop bounds lookup table. This is a \
509 5-dimensional dictionary with index-offset, field-space, \
510 iteration-space, loop-type, and boundary-side lookup keys \
511 which provides information about how to construct the \
512 loop boundaries for a kernel with such parameters.
518 test_all_variables=False,
519 signatures_to_ignore=None,
522 This function is a GOcean-specific override of the default method
523 in the Loop class. It allows domain-specific rules to be applied when
524 determining whether or not loop iterations are independent.
526 :param bool test_all_variables: if True, it will test if all variable
527 accesses are independent, otherwise it will stop after the first
528 variable access is found that isn't.
529 :param signatures_to_ignore: list of signatures for which to skip
531 :type signatures_to_ignore: Optional[
532 List[:py:class:`psyclone.core.Signature`]]
533 :param dep_tools: an optional instance of DependencyTools so that the
534 caller can access any diagnostic messages detailing why the loop
535 iterations are not independent.
536 :type dep_tools: Optional[
537 :py:class:`psyclone.psyir.tools.DependencyTools`]
539 :returns: True if the loop iterations are independent, False otherwise.
544 dtools = DependencyTools()
549 stat = dtools.can_loop_be_parallelised(
550 self, test_all_variables=test_all_variables,
551 signatures_to_ignore=signatures_to_ignore)
553 except InternalError:
559 def _halo_read_access(self, arg):
560 '''Determines whether the supplied argument has (or might have) its
561 halo data read within this loop. Returns True if it does, or if
562 it might and False if it definitely does not.
564 :param arg: an argument contained within this loop.
565 :type arg: :py:class:`psyclone.gocean1p0.GOKernelArgument`
567 :return: True if the argument reads, or might read from the \
568 halo and False otherwise.
572 return arg.argument_type ==
'field' and arg.stencil.has_stencil
and \
573 arg.access
in [AccessType.READ, AccessType.READWRITE,
577 '''Add halo exchanges before this loop as required by fields within
578 this loop. The PSyIR insertion logic is coded in the _add_halo_exchange
584 prev_arg_list = halo_field.backward_write_dependencies()
585 if not prev_arg_list:
595 prev_node = prev_arg_list[0].call
596 if not isinstance(prev_node, HaloExchange):
602 def _add_halo_exchange(self, halo_field):
603 '''An internal helper method to add the halo exchange call immediately
604 before this loop using the halo_field argument for the associated
607 :param halo_field: the argument requiring a halo exchange
608 :type halo_field: :py:class:`psyclone.gocean1p0.GOKernelArgument`
612 self.parent.children.insert(self.position, exchange)
617 '''Populates the GOLoop._bounds_lookup dictionary. This is
618 used by PSyclone to look up the loop boundaries for each loop
623 for grid_offset
in const.SUPPORTED_OFFSETS:
624 GOLoop._bounds_lookup[grid_offset] = {}
625 for gridpt_type
in const.VALID_FIELD_GRID_TYPES:
626 GOLoop._bounds_lookup[grid_offset][gridpt_type] = {}
627 for itspace
in const.VALID_ITERATES_OVER:
628 GOLoop._bounds_lookup[grid_offset][gridpt_type][
632 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_ct'][
'go_all_pts'] = \
633 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}+1"},
634 'outer': {
'start':
"{start}-1",
'stop':
"{stop}+1"}}
635 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_ct'][
'go_internal_pts'] = \
636 {
'inner': {
'start':
"{start}",
'stop':
"{stop}"},
637 'outer': {
'start':
"{start}",
'stop':
"{stop}"}}
638 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_cu'][
'go_all_pts'] = \
639 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}"},
640 'outer': {
'start':
"{start}-1",
'stop':
"{stop}+1"}}
641 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_cu'][
'go_internal_pts'] = \
642 {
'inner': {
'start':
"{start}",
'stop':
"{stop}-1"},
643 'outer': {
'start':
"{start}",
'stop':
"{stop}"}}
644 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_cv'][
'go_all_pts'] = \
645 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}+1"},
646 'outer': {
'start':
"{start}-1",
'stop':
"{stop}"}}
647 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_cv'][
'go_internal_pts'] = \
648 {
'inner': {
'start':
"{start}",
'stop':
"{stop}"},
649 'outer': {
'start':
"{start}",
'stop':
"{stop}-1"}}
650 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_cf'][
'go_all_pts'] = \
651 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}"},
652 'outer': {
'start':
"{start}-1",
'stop':
"{stop}"}}
653 GOLoop._bounds_lookup[
'go_offset_ne'][
'go_cf'][
'go_internal_pts'] = \
654 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}-1"},
655 'outer': {
'start':
"{start}-1",
'stop':
"{stop}-1"}}
657 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_ct'][
'go_all_pts'] = \
658 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}+1"},
659 'outer': {
'start':
"{start}-1",
'stop':
"{stop}+1"}}
660 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_ct'][
'go_internal_pts'] = \
661 {
'inner': {
'start':
"{start}",
'stop':
"{stop}"},
662 'outer': {
'start':
"{start}",
'stop':
"{stop}"}}
663 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_cu'][
'go_all_pts'] = \
664 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}+1"},
665 'outer': {
'start':
"{start}-1",
'stop':
"{stop}+1"}}
666 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_cu'][
'go_internal_pts'] = \
667 {
'inner': {
'start':
"{start}",
'stop':
"{stop}+1"},
668 'outer': {
'start':
"{start}",
'stop':
"{stop}"}}
669 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_cv'][
'go_all_pts'] = \
670 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}+1"},
671 'outer': {
'start':
"{start}-1",
'stop':
"{stop}+1"}}
672 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_cv'][
'go_internal_pts'] = \
673 {
'inner': {
'start':
"{start}",
'stop':
"{stop}"},
674 'outer': {
'start':
"{start}",
'stop':
"{stop}+1"}}
675 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_cf'][
'go_all_pts'] = \
676 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}+1"},
677 'outer': {
'start':
"{start}-1",
'stop':
"{stop}+1"}}
678 GOLoop._bounds_lookup[
'go_offset_sw'][
'go_cf'][
'go_internal_pts'] = \
679 {
'inner': {
'start':
"{start}",
'stop':
"{stop}+1"},
680 'outer': {
'start':
"{start}",
'stop':
"{stop}+1"}}
682 for gridpt_type
in const.VALID_FIELD_GRID_TYPES:
683 for itspace
in const.VALID_ITERATES_OVER:
684 GOLoop._bounds_lookup[
'go_offset_any'][gridpt_type][itspace] =\
685 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}"},
686 'outer': {
'start':
"{start}-1",
'stop':
"{stop}"}}
688 for offset
in const.SUPPORTED_OFFSETS:
689 for itspace
in const.VALID_ITERATES_OVER:
690 GOLoop._bounds_lookup[offset][
'go_every'][itspace] = \
691 {
'inner': {
'start':
"{start}-1",
'stop':
"{stop}+1"},
692 'outer': {
'start':
"{start}-1",
'stop':
"{stop}+1"}}
699 Adds a new iteration space to PSyclone. An iteration space in the
700 gocean1.0 API is for a certain offset type and field type. It defines
701 the loop boundaries for the outer and inner loop. The format is a
704 >>> bound_info = offset-type:field-type:iteration-space:outer-start:
705 outer-stop:inner-start:inner-stop
709 >>> bound_info = go_offset_ne:go_ct:go_all_pts:
710 {start}-1:{stop}+1:{start}:{stop}
712 The expressions {start} and {stop} will be replaced with the loop
713 indices that correspond to the inner points (i.e. non-halo or
714 boundary points) of the field. So the index {start}-1 is actually
715 on the halo / boundary.
717 :param str bound_info: A string that contains a ":" separated \
718 tuple with the iteration space definition.
720 :raises ValueError: if bound_info is not a string.
721 :raises ConfigurationError: if bound_info is not formatted correctly.
724 if not isinstance(bound_info, str):
726 f
"string, got '{bound_info}' "
727 f
"(type {type(bound_info)})")
729 data = bound_info.split(
":")
732 f
"\"offset-type:field-type:"
733 f
"iteration-space:outer-start:"
734 f
"outer-stop:inner-start:inner-stop\"\n"
735 f
"But got \"{bound_info}\"")
737 if not GOLoop._bounds_lookup:
738 GOLoop.setup_bounds()
743 bracket_regex = re.compile(
"{[^}]+}")
744 for bound
in data[3:7]:
745 all_expr = bracket_regex.findall(bound)
746 for bracket_expr
in all_expr:
747 if bracket_expr
not in [
"{start}",
"{stop}"]:
749 f
"'{{stop}}' are allowed as "
750 f
"bracketed expression in an "
751 f
"iteration space. But got "
757 ParserFactory().
create(std=
"f2008")
761 for bound
in data[3:7]:
762 do_string = f
"do i=1, {bound}"
765 do_string = do_string.format(start=
'15', stop=
'25')
768 _ = Nonlabel_Do_Stmt(do_string)
769 except NoMatchError
as err:
771 f
"valid do loop boundary. Error "
772 f
"message: '{err}'.")
from err
776 current_bounds = GOLoop._bounds_lookup
778 if not data[0]
in current_bounds:
779 current_bounds[data[0]] = {}
782 if not data[1]
in current_bounds[data[0]]:
783 current_bounds[data[0]][data[1]] = {}
787 if not data[2]
in current_bounds[data[0]][data[1]]:
788 current_bounds[data[0]][data[1]][data[2]] = {}
789 const.VALID_ITERATES_OVER.append(data[2])
791 current_bounds[data[0]][data[1]][data[2]] = \
792 {
'outer': {
'start': data[3],
'stop': data[4]},
793 'inner': {
'start': data[5],
'stop': data[6]}}
797 Get the string that represents a customized custom bound for this
798 GOLoop (provided by the add_bounds() method). It can provide the
799 'start' or 'stop' side of the bounds.
801 :param str side: 'start' or 'stop' side of the bound.
803 :returns: the string that represents the loop bound.
806 :raises GenerationError: if this node can not find a field in \
807 the Invoke to be the base of the infrastructure call.
808 :raises GenerationError: if no expression is known to obtain the \
809 boundaries for a loop of this characteristics, because they \
810 are not in the GOcean lookup table or the loop type is not \
813 api_config = Config.get().api_conf(
"gocean1.0")
816 invoke = self.ancestor(InvokeSchedule)
818 for arg
in invoke.symbol_table.argument_list:
819 if isinstance(arg.datatype, DataTypeSymbol):
820 if arg.datatype.name ==
"r2d_field":
826 f
"Cannot generate custom loop bound for loop {self}. "
827 f
"Couldn't find any suitable field.")
830 prop_access = api_config.grid_properties[
"go_grid_xstop"]
832 prop_access = api_config.grid_properties[
"go_grid_ystop"]
835 f
"Invalid loop type of '{self.loop_type}'. Expected one of "
836 f
"{GOceanConstants().VALID_LOOP_TYPES}")
838 stop_expr = prop_access.fortran.format(field.name)
842 start=
'2', stop=stop_expr)
843 except KeyError
as err:
845 f
"Cannot generate custom loop bound for a loop with an index-"
846 f
"offset of '{self.index_offset}', a field-space of "
847 f
"'{self.field_space}', an iteration-space of "
848 f
"'{self.iteration_space}' and a loop-type of "
849 f
"'{self.loop_type}', for the side '{side}' because "
850 f
"this keys combination does not exist in the "
851 f
"GOLoop.bounds_lookup table.")
from err
856 def _grid_property_psyir_expression(self, grid_property):
858 Create a PSyIR reference expression using the supplied grid-property
859 information (which will have been read from the config file).
861 :param str grid_property: the property of the grid for which to \
862 create a reference. This is the format string read from the \
863 config file or just a simple name.
865 :returns: the PSyIR expression for the grid-property access.
866 :rtype: :py:class:`psyclone.psyir.nodes.Reference` or sub-class
869 members = grid_property.split(
"%")
870 if len(members) == 1:
874 sym = self.scope.symbol_table.lookup(members[0])
876 sym = self.scope.symbol_table.new_symbol(
877 members[0], symbol_type=DataSymbol, datatype=INTEGER_TYPE)
878 return Reference(sym, parent=self)
880 if members[0] !=
"{0}":
881 raise NotImplementedError(
882 f
"Supplied grid property is a derived-type reference but "
883 f
"does not begin with '{{0}}': '{grid_property}'")
886 return StructureReference.create(fld_sym, members[1:])
889 ''' Creates the PSyIR of the upper bound of this loop.
891 :returns: the PSyIR for the upper bound of this loop.
892 :rtype: :py:class:`psyclone.psyir.nodes.Node`
899 stop = IntrinsicCall(IntrinsicCall.Intrinsic.SIZE)
902 api_config = Config.get().api_conf(
"gocean1.0")
904 api_config.grid_properties[
"go_grid_data"].fortran)
907 stop.addchild(Literal(
"1", INTEGER_TYPE, parent=stop))
909 stop.addchild(Literal(
"2", INTEGER_TYPE, parent=stop))
917 props = Config.get().api_conf(
"gocean1.0").grid_properties
920 props[f
"go_grid_internal_{self._loop_type}_stop"].fortran)
923 props[f
"go_grid_whole_{self._loop_type}_stop"].fortran)
926 bound_str, self.ancestor(GOInvokeSchedule).symbol_table)
929 ''' Returns the lower bound of this loop as a string.
931 :returns: root of PSyIR sub-tree describing this lower bound.
932 :rtype: :py:class:`psyclone.psyir.nodes.Node`
937 return Literal(
"1", INTEGER_TYPE)
944 props = Config.get().api_conf(
"gocean1.0").grid_properties
947 props[f
"go_grid_internal_{self._loop_type}_start"].fortran)
950 props[f
"go_grid_whole_{self._loop_type}_start"].fortran)
953 bound_str, self.ancestor(GOInvokeSchedule).symbol_table)
955 def _validate_loop(self):
956 ''' Validate that the GOLoop has all necessary boundaries information
957 to lower or gen_code to f2pygen.
959 :raises GenerationError: if we can't find an enclosing Schedule.
960 :raises GenerationError: if this loop does not enclose a Kernel.
961 :raises GenerationError: if constant loop bounds are enabled but are \
962 not supported for the current grid offset.
963 :raises GenerationError: if the kernels within this loop expect \
964 different different grid offsets.
969 schedule = self.ancestor(GOInvokeSchedule)
976 go_kernels = self.walk(GOKern)
979 "GOcean Kernel enclosed by this loop")
980 index_offset = go_kernels[0].index_offset
984 for kernel
in go_kernels:
985 if kernel.index_offset != index_offset:
987 f
"grid offset but kernel "
988 f
"'{kernel.name}' has offset"
989 f
" '{kernel.index_offset}' which does "
990 f
"not match '{index_offset}'.")
993 ''' Create the f2pygen AST for this loop (and update the PSyIR
994 representing the loop bounds if necessary).
996 :param parent: the node in the f2pygen AST to which to add content.
997 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1008 ''' A GOcean-specific built-in call factory. No built-ins
1009 are supported in GOcean at the moment. '''
1013 ''' Placeholder to create a GOocean-specific built-in call.
1014 This will require us to create a doubly-nested loop and then create
1015 the body of the particular built-in operation. '''
1017 "Built-ins are not supported for the GOcean 1.0 API")
1022 ''' A GOcean-specific kernel-call factory. A standard kernel call in
1023 GOcean consists of a doubly-nested loop (over i and j) and a call to
1024 the user-supplied kernel routine. '''
1027 ''' Create a new instance of a call to a GO kernel. Includes the
1028 looping structure as well as the call to the kernel itself.
1030 :param parent: node where the kernel call structure will be inserted.
1031 :type parent: :py:class:`psyclone.psyir.nodes.Node`
1033 :returns: new PSyIR tree representing the kernel call loop.
1034 :rtype: :py:class:`psyclone.gocean1p0.GOLoop`
1042 gocall =
GOKern(call, parent=parent)
1045 iteration_space = gocall.iterates_over
1046 field_space = gocall.arguments.iteration_space_arg().function_space
1047 field_name = gocall.arguments.iteration_space_arg().name
1048 index_offset = gocall.index_offset
1051 outer_loop = GOLoop.create(loop_type=
"outer",
1052 iteration_space=iteration_space,
1053 field_space=field_space,
1054 field_name=field_name,
1055 index_offset=index_offset,
1057 inner_loop = GOLoop.create(loop_type=
"inner",
1058 iteration_space=iteration_space,
1059 field_space=field_space,
1060 field_name=field_name,
1061 index_offset=index_offset,
1062 parent=outer_loop.loop_body)
1063 outer_loop.loop_body.addchild(inner_loop)
1066 gocall._parent =
None
1067 inner_loop.loop_body.addchild(gocall)
1073 Stores information about GOcean Kernels as specified by the Kernel
1074 metadata. Uses this information to generate appropriate PSy layer
1075 code for the Kernel instance. Specialises the gen_code method to
1076 create the appropriate GOcean specific kernel call.
1078 :param call: information on the way in which this kernel is called \
1079 from the Algorithm layer.
1080 :type call: :py:class:`psyclone.parse.algorithm.KernelCall`
1081 :param parent: optional node where the kernel call will be inserted.
1082 :type parent: :py:class:`psyclone.psyir.nodes.Node`
1085 def __init__(self, call, parent=None):
1086 super().__init__(GOKernelArguments, call, parent, check=
False)
1096 def _create_psyir_for_access(symbol, var_value, depth):
1097 '''This function creates the PSyIR of an index-expression:
1098 - if `var_value` is negative, it returns 'symbol-depth'.
1099 - if `var_value` is positive, it returns 'symbol+depth`
1100 - otherwise it just returns a Reference to `symbol`.
1101 This is used to create artificial stencil accesses for GOKernels.
1103 :param symbol: the symbol to use.
1104 :type symbol: :py:class:`psyclone.psyir.symbols.Symbol`
1105 :param int var_value: value of the variable, which determines the \
1106 direction (adding or subtracting depth).
1107 :param int depth: the depth of the access (>0).
1109 :returns: the index expression for an access in the given direction.
1110 :rtype: union[:py:class:`psyclone.psyir.nodes.Reference`,
1111 :py:class:`psyclone.psyir.nodes.BinaryOperation`]
1115 return Reference(symbol)
1117 operator = BinaryOperation.Operator.ADD
1119 operator = BinaryOperation.Operator.SUB
1121 return BinaryOperation.create(operator,
1123 Literal(str(depth), INTEGER_TYPE))
1125 def _record_stencil_accesses(self, signature, arg, var_accesses):
1126 '''This function adds accesses to a field depending on the
1127 meta-data declaration for this argument (i.e. accounting for
1128 any stencil accesses).
1130 :param signature: signature of the variable.
1131 :type signature: :py:class:`psyclone.core.Signature`
1132 :param arg: the meta-data information for this argument.
1133 :type arg: :py:class:`psyclone.gocean1p0.GOKernelArgument`
1134 :param var_accesses: VariablesAccessInfo instance that stores the\
1135 information about the field accesses.
1136 :type var_accesses: \
1137 :py:class:`psyclone.core.VariablesAccessInfo`
1143 sym_tab = self.ancestor(GOInvokeSchedule).symbol_table
1144 symbol_i = sym_tab.lookup_with_tag(
"contiguous_kidx")
1145 symbol_j = sym_tab.lookup_with_tag(
"noncontiguous_kidx")
1149 for j
in [-1, 0, 1]:
1150 for i
in [-1, 0, 1]:
1151 depth = arg.stencil.depth(i, j)
1152 for current_depth
in range(1, depth+1):
1155 i_expr = GOKern._create_psyir_for_access(symbol_i, i,
1157 j_expr = GOKern._create_psyir_for_access(symbol_j, j,
1162 if i == 0
and j == 0:
1165 acc = AccessType.READ
1167 var_accesses.add_access(signature, acc, self,
1171 '''Get all variable access information. All accesses are marked
1172 according to the kernel metadata.
1174 :param var_accesses: VariablesAccessInfo instance that stores the\
1175 information about variable accesses.
1176 :type var_accesses: \
1177 :py:class:`psyclone.core.VariablesAccessInfo`
1182 field_for_grid_property =
None
1183 for arg
in self.
argumentsarguments.args:
1184 if arg.argument_type ==
"grid_property":
1185 if not field_for_grid_property:
1186 field_for_grid_property = \
1188 var_name = arg.dereference(field_for_grid_property.name)
1192 signature =
Signature(var_name.split(
"%"))
1195 if not arg.is_literal:
1196 var_accesses.add_access(signature, arg.access, self)
1198 if arg.argument_type ==
"field":
1207 sym_tab = self.ancestor(GOInvokeSchedule).symbol_table
1208 symbol_i = sym_tab.lookup_with_tag(
"contiguous_kidx")
1209 symbol_j = sym_tab.lookup_with_tag(
"noncontiguous_kidx")
1210 var_accesses.add_access(signature, arg.access,
1211 self, [Reference(symbol_i),
1212 Reference(symbol_j)])
1214 var_accesses.next_location()
1217 '''Return a list of the variable (names) that are local to this loop
1218 (and must therefore be e.g. threadprivate if doing OpenMP)
1225 ''' The grid index-offset convention that this kernel expects '''
1230 :returns: a schedule representing the GOcean kernel code.
1231 :rtype: :py:class:`psyclone.gocean1p0.GOKernelSchedule`
1233 :raises GenerationError: if there is a problem raising the language- \
1234 level PSyIR of this kernel to GOcean PSyIR.
1241 psyir = astp.generate_psyir(self.
astast)
1244 RaisePSyIR2GOceanKernTrans)
1245 raise_trans = RaisePSyIR2GOceanKernTrans(self.
_metadata_name_metadata_name)
1247 raise_trans.apply(psyir)
1248 except Exception
as err:
1250 f
"Failed to raise the PSyIR for kernel '{self.name}' "
1251 f
"to GOcean PSyIR. Error was:\n{err}")
from err
1252 for routine
in psyir.walk(Routine):
1264 '''Provides information about GOcean kernel-call arguments
1265 collectively, as specified by the kernel argument metadata. This
1266 class ensures that initialisation is performed correctly. It also
1267 overrides the iteration_space_arg method to supply a
1268 GOcean-specific dictionary for the mapping of argument-access
1271 :param call: the kernel meta-data for which to extract argument info.
1272 :type call: :py:class:`psyclone.parse.KernelCall`
1273 :param parent_call: the kernel-call object.
1274 :type parent_call: :py:class:`psyclone.gocean1p0.GOKern`
1275 :param bool check: whether to check for consistency between the \
1276 kernel metadata and the algorithm layer. Defaults to \
1277 True. Currently does nothing in this API.
1280 def __init__(self, call, parent_call, check=True):
1284 Arguments.__init__(self, parent_call)
1288 for (idx, arg)
in enumerate(call.ktype.arg_descriptors):
1290 if arg.argument_type ==
"grid_property":
1293 elif arg.argument_type
in [
"scalar",
"field"]:
1298 raise ParseError(f
"Invalid kernel argument type. Found "
1299 f
"'{arg.argument_type}' but must be one of "
1300 f
"['grid_property', 'scalar', 'field'].")
1301 self.
_dofs_dofs = []
1305 :returns: the PSyIR expressions representing this Argument list.
1306 :rtype: list of :py:class:`psyclone.psyir.nodes.Node`
1309 symtab = self.
_parent_call_parent_call.scope.symbol_table
1310 symbol1 = symtab.lookup_with_tag(
"contiguous_kidx")
1311 symbol2 = symtab.lookup_with_tag(
"noncontiguous_kidx")
1312 return ([Reference(symbol1), Reference(symbol2)] +
1313 [arg.psyir_expression()
for arg
in self.
argsargs])
1317 Determine the best kernel argument from which to get properties of
1318 the grid. For this, an argument must be a field (i.e. not
1319 a scalar) and must be supplied by the algorithm layer
1320 (i.e. not a grid property). If possible it should also be
1321 a field that is read-only as otherwise compilers can get
1322 confused about data dependencies and refuse to SIMD
1324 :returns: the argument object from which to get grid properties.
1325 :rtype: :py:class:`psyclone.gocean1p0.GOKernelArgument` or None
1327 for access
in [AccessType.READ, AccessType.READWRITE,
1330 if arg.argument_type ==
"field" and arg.access == access:
1339 ''' Currently required for invoke base class although this makes no
1340 sense for GOcean. Need to refactor the Invoke base class and
1341 remove the need for this property (#279). '''
1342 return self.
_dofs_dofs
1347 Provide the list of references (both objects and arrays) that must
1348 be present on an OpenACC device before the kernel associated with
1349 this Arguments object may be launched.
1351 :returns: list of (Fortran) quantities
1362 grid_ptr = grid_fld.name +
"%grid"
1363 api_config = Config.get().api_conf(
"gocean1.0")
1365 data_fmt = api_config.grid_properties[
"go_grid_data"].fortran
1366 arg_list.extend([grid_fld.name, data_fmt.format(grid_fld.name)])
1368 if arg.argument_type ==
"scalar":
1369 arg_list.append(arg.name)
1370 elif arg.argument_type ==
"field" and arg != grid_fld:
1373 arg_list.extend([arg.name, data_fmt.format(arg.name)])
1374 elif arg.argument_type ==
"grid_property":
1375 if grid_ptr
not in arg_list:
1378 arg_list.append(grid_ptr)
1379 arg_list.append(grid_ptr+
"%"+arg.name)
1385 Provides the list of names of field objects that are required by
1386 the kernel associated with this Arguments object.
1388 :returns: List of names of (Fortran) field objects.
1391 args = args_filter(self.
_args_args_args, arg_types=[
"field"])
1392 return [arg.name
for arg
in args]
1397 Provides the list of names of scalar arguments required by the
1398 kernel associated with this Arguments object. If there are none
1399 then the returned list is empty.
1401 :returns: A list of the names of scalar arguments in this object.
1404 args = args_filter(self.
_args_args_args, arg_types=[
"scalar"])
1405 return [arg.name
for arg
in args]
1408 ''' Create and append a GOKernelArgument to the Argument list.
1410 :param str name: name of the appended argument.
1411 :param str argument_type: type of the appended argument.
1413 :raises TypeError: if the given name is not a string.
1416 if not isinstance(name, str):
1418 f
"The name parameter given to GOKernelArguments.append "
1419 f
"method should be a string, but found "
1420 f
"'{type(name).__name__}' instead.")
1429 arg =
Arg(
"variable", name)
1435 ''' Provides information about individual GOcean kernel call arguments
1436 as specified by the kernel argument metadata. '''
1437 def __init__(self, arg, arg_info, call):
1440 KernelArgument.__init__(self, arg, arg_info, call)
1447 :returns: the PSyIR expression represented by this Argument.
1448 :rtype: :py:class:`psyclone.psyir.nodes.Node`
1450 :raises InternalError: if this Argument type is not "field" or \
1456 if self.
namename.isnumeric():
1457 return Literal(self.
namename, INTEGER_TYPE)
1462 return Literal(self.
namename, REAL_TYPE)
1467 symbol = self.
_call_call.scope.symbol_table.lookup(self.
namename)
1471 return StructureReference.create(symbol, [
"data"])
1475 return Reference(symbol)
1477 raise InternalError(f
"GOcean expects the Argument.argument_type() to "
1478 f
"be 'field' or 'scalar' but found "
1479 f
"'{self.argument_type}'.")
1482 ''' Infer the datatype of this argument using the API rules.
1484 :returns: the datatype of this argument.
1485 :rtype: :py:class::`psyclone.psyir.symbols.DataType`
1487 :raises InternalError: if this Argument type is not "field" or \
1489 :raises InternalError: if this argument is scalar but its space \
1490 property is not 'go_r_scalar' or 'go_i_scalar'.
1497 type_symbol = self.
_call_call.root.symbol_table.find_or_create_tag(
1498 "r2d_field", symbol_type=DataTypeSymbol,
1499 datatype=UnresolvedType(), interface=UnresolvedInterface())
1504 if self.
spacespace.lower() ==
"go_r_scalar":
1505 go_wp = self.
_call_call.root.symbol_table.find_or_create_tag(
1506 "go_wp", symbol_type=DataSymbol, datatype=UnresolvedType(),
1507 interface=UnresolvedInterface())
1508 return ScalarType(ScalarType.Intrinsic.REAL, go_wp)
1509 if self.
spacespace.lower() ==
"go_i_scalar":
1511 raise InternalError(f
"GOcean expects scalar arguments to be of "
1512 f
"'go_r_scalar' or 'go_i_scalar' type but "
1513 f
"found '{self.space.lower()}'.")
1515 raise InternalError(f
"GOcean expects the Argument.argument_type() "
1516 f
"to be 'field' or 'scalar' but found "
1517 f
"'{self.argument_type}'.")
1522 :returns: the intrinsic type of this argument. If it's not a scalar \
1523 integer or real it will return an empty string.
1528 if self.
spacespace.lower() ==
"go_r_scalar":
1530 if self.
spacespace.lower() ==
"go_i_scalar":
1535 def argument_type(self):
1537 Return the type of this kernel argument - whether it is a field,
1538 a scalar or a grid_property (to be supplied by the PSy layer).
1539 If it has no type it defaults to scalar.
1541 :returns: the type of the argument.
1545 if self.
_arg_arg_arg.argument_type:
1546 return self.
_arg_arg_arg.argument_type
1551 ''' Returns the expected finite difference space for this
1552 argument as specified by the kernel argument metadata.'''
1553 return self.
_arg_arg_arg.function_space
1557 ''':return: whether this variable is a scalar variable or not.
1564 Describes arguments that supply grid properties to a kernel.
1565 These arguments are provided by the PSy layer rather than in
1566 the Algorithm layer.
1568 :param arg: the meta-data entry describing the required grid property.
1569 :type arg: :py:class:`psyclone.gocean1p0.GO1p0Descriptor`
1570 :param kernel_call: the kernel call node that this Argument belongs to.
1571 :type kernel_call: :py:class:`psyclone.gocean1p0.GOKern`
1573 :raises GenerationError: if the grid property is not recognised.
1576 def __init__(self, arg, kernel_call):
1577 super().__init__(
None,
None, arg.access)
1582 api_config = Config.get().api_conf(
"gocean1.0")
1584 deref_name = api_config.grid_properties[arg.grid_prop].fortran
1585 except KeyError
as err:
1586 all_keys = str(api_config.grid_properties.keys())
1588 f
"Expected one of {all_keys} but found "
1589 f
"'{arg.grid_prop}'")
from err
1593 self.
_name_name_name = deref_name.split(
"%")[-1]
1605 ''' Returns the Fortran name of the grid property, which is used
1606 in error messages etc.'''
1611 :returns: the PSyIR expression represented by this Argument.
1612 :rtype: :py:class:`psyclone.psyir.nodes.Node`
1616 base_field = self.
_call_call_call.arguments.find_grid_access().name
1617 tag =
"AlgArgs_" + base_field
1618 symbol = self.
_call_call_call.scope.symbol_table.find_or_create_tag(tag)
1621 access = self.
dereferencedereference(base_field).split(
'%')[1:]
1624 return StructureReference.create(symbol, access)
1627 '''Returns a Fortran string to dereference a grid property of the
1628 specified field. It queries the current config file settings for
1629 getting the proper dereference string, which is a format string
1630 where {0} represents the field name.
1632 :param str fld_name: The name of the field which is used to \
1633 dereference a grid property.
1635 :returns: the dereference string required to access a grid property
1636 in a dl_esm field (e.g. "subdomain%internal%xstart"). The name
1637 must contains a "{0}" which is replaced by the field name.
1639 api_config = Config.get().api_conf(
"gocean1.0")
1640 deref_name = api_config.grid_properties[self.
_property_name_property_name].fortran
1641 return deref_name.format(fld_name)
1645 ''' The type of this argument. We have this for compatibility with
1646 GOKernelArgument objects since, for this class, it will always be
1647 "grid_property". '''
1653 :returns: the intrinsic_type of this argument.
1657 api_config = Config.get().api_conf(
"gocean1.0")
1658 return api_config.grid_properties[self.
_property_name_property_name].intrinsic_type
1663 :returns: if this variable is a scalar variable or not.
1667 api_config = Config.get().api_conf(
"gocean1.0")
1668 return api_config.grid_properties[self.
_property_name_property_name].type \
1673 ''' The raw text used to pass data from the algorithm layer
1674 for this argument. Grid properties are not passed from the
1675 algorithm layer so None is returned.'''
1680 A grid-property argument is read-only and supplied by the
1681 PSy layer so has no dependencies
1683 :returns: None to indicate no dependencies
1690 A grid-property argument is read-only and supplied by the
1691 PSy layer so has no dependencies
1693 :returns: None to indicate no dependencies
1700 '''GOcean 1.0 stencil information for a kernel argument as obtained by
1701 parsing the kernel meta-data. The expected structure of the
1702 metadata and its meaning is provided in the description of the
1707 ''' Set up any internal variables. '''
1709 self.
_stencil_stencil = [[0
for _
in range(3)]
for _
in range(3)]
1710 self.
_name_name =
None
1714 def load(self, stencil_info, kernel_name):
1715 '''Take parsed stencil metadata information, check it is valid and
1716 store it in a convenient form. The kernel_name argument is
1717 only used to provide the location if there is an error.
1719 The stencil information should either be a name which
1720 indicates a particular type of stencil or in the form
1721 stencil(xxx,yyy,zzz) which explicitly specifies a stencil
1722 shape where xxx, yyy and zzz are triplets of integers
1723 indicating whether there is a stencil access in a particular
1724 direction and the depth of that access. For example:
1730 indicates that there is a stencil access of depth 1 in the
1731 "North" and "South" directions and stencil access of depth 2
1732 in the "East" and "West" directions. The value at the centre
1733 of the stencil will not be used by PSyclone but can be 0 or 1
1734 and indicates whether the local field value is accessed.
1736 The convention is for the associated arrays to be
1737 2-dimensional. If we denote the first dimension as "i" and the
1738 second dimension as "j" then the following directions are
1748 For example a stencil access like:
1750 a(i,j) + a(i+1,j) + a(i,j-1)
1758 :param stencil_info: contains the appropriate part of the parser AST
1759 :type stencil_info: :py:class:`psyclone.expression.FunctionVar`
1760 :param string kernel_name: the name of the kernel from where this \
1761 stencil information came from.
1763 :raises ParseError: if the supplied stencil information is invalid.
1768 if not isinstance(stencil_info, expr.FunctionVar):
1771 f
"Meta-data error in kernel '{kernel_name}': 3rd descriptor "
1772 f
"(stencil) of field argument is '{stencil_info}' but "
1773 f
"expected either a name or the format 'go_stencil(...)'")
1776 name = stencil_info.name.lower()
1779 if stencil_info.args:
1784 args = stencil_info.args
1785 if name !=
"go_stencil":
1787 f
"Meta-data error in kernel '{kernel_name}': 3rd "
1788 f
"descriptor (stencil) of field argument is '{name}' but "
1789 f
"must be 'go_stencil(...)")
1792 f
"Meta-data error in kernel '{kernel_name}': 3rd "
1793 f
"descriptor (stencil) of field argument with format "
1794 f
"'go_stencil(...)', has {len(args)} arguments but should "
1801 for arg_idx
in range(3):
1803 if not isinstance(arg, str):
1805 f
"Meta-data error in kernel '{kernel_name}': 3rd "
1806 f
"descriptor (stencil) of field argument with format "
1807 f
"'go_stencil(...)'. Argument index {arg_idx} should "
1808 f
"be a number but found '{arg}'.")
1811 f
"Meta-data error in kernel '{kernel_name}': 3rd "
1812 f
"descriptor (stencil) of field argument with format "
1813 f
"'go_stencil(...)'. Argument index {arg_idx} should "
1814 f
"consist of 3 digits but found {len(arg)}.")
1816 if args[1][1]
not in [
"0",
"1"]:
1818 f
"Meta-data error in kernel '{kernel_name}': 3rd "
1819 f
"descriptor (stencil) of field argument with format "
1820 f
"'go_stencil(...)'. Argument index 1 position 1 "
1821 f
"should be a number from 0-1 but found {args[1][1]}.")
1824 if args[0] ==
"000" and \
1825 (args[1] ==
"000" or args[1] ==
"010")
and \
1828 f
"Meta-data error in kernel '{kernel_name}': 3rd "
1829 f
"descriptor (stencil) of field argument with format "
1830 f
"'go_stencil(...)'. A zero sized stencil has been "
1831 f
"specified. This should be specified with the "
1832 f
"'go_pointwise' keyword.")
1835 for idx0
in range(3):
1836 for idx1
in range(3):
1842 self.
_stencil_stencil[idx0][2-idx1] = int(args[idx1][idx0])
1846 if name
not in const.VALID_STENCIL_NAMES:
1848 f
"Meta-data error in kernel '{kernel_name}': 3rd "
1849 f
"descriptor (stencil) of field argument is '{name}' "
1850 f
"but must be one of {const.VALID_STENCIL_NAMES} or "
1852 self.
_name_name = name
1858 self.
_stencil_stencil = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
1860 def _check_init(self):
1861 '''Internal method which checks that the stencil information has been
1864 :raises GenerationError: if the GOStencil object has not been
1865 initialised i.e. the load() method has not been called
1870 "Error in class GOStencil: the object has not yet been "
1871 "initialised. Please ensure the load() method is called.")
1875 '''Specifies whether this argument has stencil information or not. The
1876 only case when this is False is when the stencil information
1877 specifies 'pointwise' as this indicates that there is no
1880 :returns: True if this argument has stencil information and False \
1890 '''Provides the stencil name if one is provided
1892 :returns: the name of the type of stencil if this is provided \
1898 return self.
_name_name
1901 '''Provides the depth of the stencil in the 8 possible stencil
1902 directions in a 2d regular grid (see the description in the
1903 load class for more information). Values must be between -1
1904 and 1 as they are considered to be relative to the centre of
1905 the stencil For example:
1916 :param int index0: the relative stencil offset for the first \
1917 index of the associated array. This value \
1918 must be between -1 and 1.
1919 :param int index1: the relative stencil offset for the second \
1920 index of the associated array. This value \
1921 must be between -1 and 1
1923 :returns: the depth of the stencil in the specified direction.
1926 :raises GenerationError: if the indices are out-of-bounds.
1930 if index0 < -1
or index0 > 1
or index1 < -1
or index1 > 1:
1932 f
"The indices arguments to the depth method in the GOStencil "
1933 f
"object must be between -1 and 1 but found "
1934 f
"({index0},{index1})")
1935 return self.
_stencil_stencil[index0+1][index1+1]
1939 ''' Description of a GOcean 1.0 kernel argument, as obtained by
1940 parsing the kernel metadata.
1942 :param str kernel_name: the name of the kernel metadata type \
1943 that contains this metadata.
1944 :param kernel_arg: the relevant part of the parser's AST.
1945 :type kernel_arg: :py:class:`psyclone.expression.FunctionVar`
1946 :param int metadata_index: the postion of this argument in the list of \
1947 arguments specified in the metadata.
1949 :raises ParseError: if a kernel argument has an invalid grid-point type.
1950 :raises ParseError: for an unrecognised grid property.
1951 :raises ParseError: for an invalid number of arguments.
1952 :raises ParseError: for an invalid access argument.
1955 def __init__(self, kernel_name, kernel_arg, metadata_index):
1957 nargs = len(kernel_arg.args)
1965 access = kernel_arg.args[0].name
1966 funcspace = kernel_arg.args[1].name
1968 stencil_info.load(kernel_arg.args[2],
1974 valid_func_spaces = const.VALID_FIELD_GRID_TYPES + \
1975 const.VALID_SCALAR_TYPES
1978 if funcspace.lower()
in const.VALID_FIELD_GRID_TYPES:
1980 elif funcspace.lower()
in const.VALID_SCALAR_TYPES:
1983 raise ParseError(f
"Meta-data error in kernel {kernel_name}: "
1984 f
"argument grid-point type is '{funcspace}' "
1985 f
"but must be one of {valid_func_spaces}")
1991 access = kernel_arg.args[0].name
1992 grid_var = kernel_arg.args[1].name
1997 api_config = Config.get().api_conf(
"gocean1.0")
1999 if grid_var.lower()
not in api_config.grid_properties:
2000 valid_keys = str(api_config.grid_properties.keys())
2002 f
"Meta-data error in kernel {kernel_name}: un-recognised "
2003 f
"grid property '{grid_var}' requested. Must be one of "
2007 f
"Meta-data error in kernel {kernel_name}: 'arg' type "
2008 f
"expects 2 or 3 arguments but found '{len(kernel_arg.args)}' "
2009 f
"in '{kernel_arg.args}'")
2011 api_config = Config.get().api_conf(
"gocean1.0")
2012 access_mapping = api_config.get_access_mapping()
2014 access_type = access_mapping[access]
2015 except KeyError
as err:
2016 valid_names = api_config.get_valid_accesses_api()
2018 f
"Meta-data error in kernel {kernel_name}: argument access is "
2019 f
"given as '{access}' but must be one of {valid_names}"
2023 super().__init__(access_type, funcspace, metadata_index,
2024 stencil=stencil_info,
2033 :returns: the name of the grid-property that this argument is to \
2034 supply to the kernel.
2042 ''' Description of a kernel including the grid index-offset it
2043 expects and the region of the grid that it expects to
2047 return (
'GOcean 1.0 kernel ' + self.
_name_name +
', index-offset = ' +
2051 def __init__(self, ast, name=None):
2053 KernelType.__init__(self, ast, name=name)
2060 raise ParseError(f
"Meta-data error in kernel {name}: an "
2061 f
"INDEX_OFFSET must be specified and must be "
2062 f
"one of {const.VALID_OFFSET_NAMES}")
2064 if self.
_index_offset_index_offset.lower()
not in const.VALID_OFFSET_NAMES:
2065 raise ParseError(f
"Meta-data error in kernel {name}: "
2066 f
"INDEX_OFFSET has value '{self._index_offset}'"
2067 f
" but must be one of {const.VALID_OFFSET_NAMES}")
2072 raise ParseError(f
"Meta-data error in kernel {name}: "
2073 f
"ITERATES_OVER is missing. (Valid values are: "
2074 f
"{const.VALID_ITERATES_OVER})")
2076 if self.
_iterates_over_iterates_over.lower()
not in const.VALID_ITERATES_OVER:
2077 raise ParseError(f
"Meta-data error in kernel {name}: "
2078 f
"ITERATES_OVER has value '"
2079 f
"{self._iterates_over.lower()}' but must be "
2080 f
"one of {const.VALID_ITERATES_OVER}")
2084 have_grid_prop =
False
2085 for idx, init
in enumerate(self.
_inits_inits):
2086 if init.name !=
'go_arg':
2087 raise ParseError(f
"Each meta_arg value must be of type "
2088 f
"'go_arg' for the gocean1.0 api, but "
2089 f
"found '{init.name}'")
2095 have_grid_prop = (have_grid_prop
or
2096 (new_arg.argument_type ==
"grid_property"))
2105 if arg.argument_type ==
"field":
2110 f
"Kernel {name} requires a property of the grid but does "
2111 f
"not have any field objects as arguments.")
2119 ''' Count and return the number of arguments that this kernel
2120 expects the Algorithm layer to provide '''
2123 if arg.argument_type !=
"grid_property":
2129 ''' Return the grid index-offset that this kernel expects '''
2135 Sub-classes ACCEnterDataDirective to provide the dl_esm_inf infrastructure-
2136 specific interfaces to flag and update when data is on a device.
2139 def _read_from_device_routine(self):
2140 ''' Return the symbol of the routine that reads data from the OpenACC
2141 device, if it doesn't exist create the Routine and the Symbol.
2143 :returns: the symbol representing the read_from_device routine.
2144 :rtype: :py:class:`psyclone.psyir.symbols.symbol`
2146 symtab = self.root.symbol_table
2148 return symtab.lookup_with_tag(
"openacc_read_func")
2155 subroutine_name = symtab.new_symbol(
2156 "read_from_device", symbol_type=RoutineSymbol,
2157 tag=
"openacc_read_func").name
2160 subroutine read_openacc(from, to, startx, starty, nx, ny, blocking)
2161 use iso_c_binding, only: c_ptr
2162 use kind_params_mod, only: go_wp
2163 type(c_ptr), intent(in) :: from
2164 real(go_wp), dimension(:,:), intent(inout), target :: to
2165 integer, intent(in) :: startx, starty, nx, ny
2166 logical, intent(in) :: blocking
2167 end subroutine read_openacc
2172 container = fortran_reader.psyir_from_source(code)
2173 subroutine = container.children[0]
2175 subroutine.addchild(ACCUpdateDirective([
Signature(
"to")],
"host",
2179 subroutine.name = subroutine_name
2182 if not self.ancestor(Container):
2184 f
"The GOACCEnterDataDirective can only be generated/lowered "
2185 f
"inside a Container in order to insert a sibling "
2186 f
"subroutine, but '{self}' is not inside a Container.")
2187 self.ancestor(Container).addchild(subroutine.detach())
2189 return symtab.lookup_with_tag(
"openacc_read_func")
2193 In-place replacement of DSL or high-level concepts into generic PSyIR
2194 constructs. In addition to calling this method in the base class, the
2195 GOACCEnterDataDirective sets up the 'data_on_device' flag for
2196 each of the fields accessed.
2198 :returns: the lowered version of this node.
2199 :rtype: :py:class:`psyclone.psyir.node.Node`
2202 self.
_acc_dirs_acc_dirs = self.ancestor(InvokeSchedule).walk(
2203 (ACCParallelDirective, ACCKernelsDirective))
2206 for var
in pdir.fields:
2207 if var
not in obj_list:
2208 obj_list.append(var)
2212 for var
in obj_list:
2213 symbol = self.scope.symbol_table.lookup(var)
2214 assignment = Assignment.create(
2215 StructureReference.create(symbol, [
'data_on_device']),
2216 Literal(
"true", BOOLEAN_TYPE))
2217 self.parent.children.insert(self.position, assignment)
2221 codeblock = reader.psyir_from_statement(
2222 f
"{symbol.name}%read_from_device_f => "
2223 f
"{read_routine_symbol.name}\n",
2224 self.scope.symbol_table)
2226 self.parent.children.insert(self.position, codeblock)
2233 Sub-classes KernelSchedule to provide a GOcean-specific implementation.
2235 :param str name: Kernel subroutine name
2238 _symbol_table_class = GOSymbolTable
2242 '''GOcean specific halo exchange class which can be added to and
2243 manipulated in a schedule.
2245 :param field: the field that this halo exchange will act on.
2246 :type field: :py:class:`psyclone.gocean1p0.GOKernelArgument`
2247 :param bool check_dirty: optional argument default False (contrary to \
2248 its generic class - revisit in #856) indicating whether this halo \
2249 exchange should be subject to a run-time check for clean/dirty halos.
2250 :param parent: optional PSyIR parent node (default None) of this object.
2251 :type parent: :py:class:`psyclone.psyir.nodes.Node`
2253 def __init__(self, field, check_dirty=False, parent=None):
2254 super().__init__(field, check_dirty=check_dirty, parent=parent)
2261 In-place replacement of DSL or high-level concepts into generic
2262 PSyIR constructs. A GOHaloExchange is replaced by a call to the
2263 appropriate library method.
2265 :returns: the lowered version of this node.
2266 :rtype: :py:class:`psyclone.psyir.node.Node`
2277 rsymbol = RoutineSymbol(self.
fieldfield.name +
"%" +
2279 call_node = Call.create(rsymbol, [Literal(
"1", INTEGER_TYPE)])
2280 self.replace_with(call_node)
2285 __all__ = [
'GOPSy',
'GOInvokes',
'GOInvoke',
'GOInvokeSchedule',
'GOLoop',
2286 'GOBuiltInCallFactory',
'GOKernCallFactory',
'GOKern',
2287 'GOKernelArguments',
'GOKernelArgument',
2288 'GOKernelGridArgument',
'GOStencil',
'GO1p0Descriptor',
2289 'GOKernelType1p0',
'GOACCEnterDataDirective',
2290 'GOKernelSchedule',
'GOHaloExchange']
def iteration_space(self)
def loop_type(self, value)
def field_space(self, my_field_space)
def unique_fields_with_halo_reads(self)
def iteration_space(self, it_space)
def field_name(self, my_field_name)
def _read_from_device_routine(self)
def lower_to_language_level(self)
def lower_to_language_level(self)
def unique_args_iscalars(self)
def gen_code(self, parent)
def unique_args_arrays(self)
def gen_code(self, parent)
def create(call, parent=None)
def get_kernel_schedule(self)
def reference_accesses(self, var_accesses)
def _record_stencil_accesses(self, signature, arg, var_accesses)
def psyir_expression(self)
def append(self, name, argument_type)
def psyir_expressions(self)
def find_grid_access(self)
def psyir_expression(self)
def backward_dependence(self)
def dereference(self, fld_name)
def forward_dependence(self)
def iteration_space(self)
def create(parent, loop_type, field_name="", field_space="", iteration_space="", index_offset="")
def get_custom_bound_string(self, side)
def iteration_space(self, it_space)
def add_bounds(bound_info)
def _grid_property_psyir_expression(self, grid_property)
def field_space(self, my_field_space)
def create_halo_exchanges(self)
dictionary _bounds_lookup
def gen_code(self, parent)
def independent_iterations(self, test_all_variables=False, signatures_to_ignore=None, dep_tools=None)
def _add_halo_exchange(self, halo_field)
def depth(self, index0, index1)
def load(self, stencil_info, kernel_name)
def arg_descriptors(self)
def _complete_init(self, arg_info)
def psy_unique_var_names(self)