37 '''This module contains PSyclone Kernel-layer-specific PSyIR classes
43 from fparser.common.readfortran
import FortranStringReader
44 from fparser.two
import Fortran2003
45 from fparser.two.parser
import ParserFactory
46 from fparser.two.utils
import walk, get_child
58 '''A GOcean-specific Container. This specialises the generic Container node
59 and adds in any domain-specific information.
61 :param str name: the name of the container.
62 :param metadata: the metadata object.
63 :type metadata: :py:class:`psyclone.domain.gocean.kernel.psyir.\
65 :param parent: optional parent node of this Container in the PSyIR.
66 :type parent: :py:class:`psyclone.psyir.nodes.Node`
67 :param symbol_table: initialise the node with a given symbol table.
69 Optional[:py:class:`psyclone.psyir.symbols.SymbolTable`]
72 def __init__(self, name, metadata, **kwargs):
73 super().__init__(name, **kwargs)
78 def create(cls, name, metadata, symbol_table, children):
79 '''Create a GOceanContainer instance given a name, metadata, a symbol
80 table and a list of child nodes. A GOcean-specific kernel is
81 created with the metadata describing the kernel interface for
82 a single kernel routine within the container.
84 :param str name: the name of the Container.
85 :param symbol_table: the symbol table associated with this \
87 :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
88 :param metadata: the metadata object.
89 :type metadata: :py:class:`psyclone.domain.gocean.kernel.psyir.\
91 :param children: a list of PSyIR nodes contained in the \
92 Container. These must be Containers or Routines.
93 :type children: List[:py:class:`psyclone.psyir.nodes.Container` \
94 | :py:class:`psyclone.psyir.nodes.Routine`]
96 :returns: an instance of `cls`.
97 :rtype: :py:class:`psyclone.psyir.nodes.Container` or subclass
101 return cls(name, metadata, children=children,
102 symbol_table=symbol_table.detach())
107 :returns the GOcean metadata object.
108 :rtype: :py:class:`psyclone.domain.gocean.kernel.psyir.\
109 GOceanKernelMetadata`
114 '''Lower this GOcean-specific container to language level psyir.
116 :returns: the lowered version of this node.
117 :rtype: :py:class:`psyclone.psyir.node.Node`
122 data_symbol = self.
metadatametadata.lower_to_psyir()
123 self.symbol_table.add(data_symbol)
126 children = self.pop_all_children()
127 generic_container = Container.create(
128 self.name, self.symbol_table.detach(), children)
129 self.replace_with(generic_container)
130 return generic_container
134 '''Contains GOcean kernel metadata. This class supports kernel
135 metadata creation, modification, loading from a fortran string,
136 writing to a fortran string, raising from existing language-level
137 PSyIR and lowering to language-level psyir.
139 :param iterates_over: the name of the quantity that this kernel is \
140 intended to iterate over.
141 :type iterates_over: Optional[str]
142 :param index_offset: the name of the quantity that specifies the \
143 index offset (how different field indices relate to each \
145 :type index_offset: Optional[str]
146 :param meta_args: a list of 'meta_arg' objects which capture the \
147 metadata values of the kernel arguments.
148 :type meta_args: Optional[List[:py:class:`GridArg` | :py:class:`FieldArg` \
149 | :py:class:`ScalarArg`]]
150 :param procedure_name: the name of the kernel procedure to call.
151 :type procedure_name: Optional[str]
152 :param name: the name of the symbol to use for the metadata in \
153 language-level PSyIR.
154 :type name: Optional[str]
157 def __init__(self, iterates_over=None, index_offset=None, meta_args=None,
158 procedure_name=None, name=None):
161 if iterates_over
is not None:
164 if index_offset
is not None:
166 if meta_args
is None:
169 if not isinstance(meta_args, list):
170 raise TypeError(f
"meta_args should be a list but found "
171 f
"{type(meta_args).__name__}.")
172 for entry
in meta_args:
173 if not isinstance(entry,
178 f
"meta_args should be a list of FieldArg, GridArg or "
179 f
"ScalarArg objects, but found "
180 f
"{type(entry).__name__}.")
183 if procedure_name
is not None:
185 self.
_name_name =
None
190 ''' Lower the metadata to language-level PSyIR.
192 :returns: metadata as stored in language-level PSyIR.
193 :rtype: :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
196 return DataTypeSymbol(
201 '''Create a new instance of GOceanKernelMetadata populated with
202 metadata from a kernel in language-level PSyIR.
204 :param symbol: the symbol in which the metadata is stored \
205 in language-level PSyIR.
206 :type symbol: :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
208 :returns: an instance of GOceanKernelMetadata.
209 :rtype: :py:class:`psyclone.domain.gocean.kernel.psyir.\
210 GOceanKernelMetadata`
212 :raises TypeError: if the symbol argument is not the expected \
214 :raises InternalError: if the datatype of the provided symbol \
215 is not the expected type.
218 if not isinstance(symbol, DataTypeSymbol):
220 f
"Expected a DataTypeSymbol but found a "
221 f
"{type(symbol).__name__}.")
223 datatype = symbol.datatype
225 if not isinstance(datatype, UnsupportedFortranType):
227 f
"Expected kernel metadata to be stored in the PSyIR as "
228 f
"an UnsupportedFortranType, but found "
229 f
"{type(datatype).__name__}.")
233 return GOceanKernelMetadata.create_from_fortran_string(
234 datatype.declaration)
238 '''Create a new instance of GOceanKernelMetadata populated with
239 metadata stored in a fortran string.
241 :param str fortran_string: the metadata stored as Fortran.
243 :returns: an instance of GOceanKernelMetadata.
244 :rtype: :py:class:`psyclone.domain.gocean.kernel.psyir.\
245 GOceanKernelMetadata`
247 :raises ValueError: if the string does not contain a fortran \
249 :raises ParseError: if the metadata has an unexpected format.
255 _ = ParserFactory().create(std=
"f2003")
256 reader = FortranStringReader(fortran_string)
258 spec_part = Fortran2003.Derived_Type_Def(reader)
259 except Fortran2003.NoMatchError:
262 f
"Expected kernel metadata to be a Fortran derived type, but "
263 f
"found '{fortran_string}'.")
265 kernel_metadata.name = spec_part.children[0].children[1].tostr()
272 value = GOceanKernelMetadata._get_property(
273 spec_part,
"iterates_over").string
274 kernel_metadata.iterates_over = value
277 value = GOceanKernelMetadata._get_property(
278 spec_part,
"index_offset").string
279 kernel_metadata.index_offset = value
282 kernel_metadata.procedure_name = GOceanKernelMetadata._get_property(
283 spec_part,
"code").string
288 meta_args = GOceanKernelMetadata._get_property(
289 spec_part,
"meta_args")
290 args = walk(meta_args, Fortran2003.Ac_Value_List)
293 f
"meta_args should be a list, but found "
294 f
"'{str(meta_args)}' in '{spec_part}'.")
296 for meta_arg
in args[0].children:
297 if len(meta_arg.children[1].children) == 2:
299 kernel_metadata.meta_args.append(
301 elif len(meta_arg.children[1].children) == 3:
303 arg2 = meta_arg.children[1].children[1].string.lower()
304 if arg2
in const.VALID_FIELD_GRID_TYPES:
305 kernel_metadata.meta_args.append(
307 meta_arg, kernel_metadata))
308 elif arg2
in const.VALID_SCALAR_TYPES:
309 kernel_metadata.meta_args.append(
311 meta_arg, kernel_metadata))
314 f
"Expected a 'meta_arg' entry with 3 arguments to "
315 f
"either be a field or a scalar, but found '{arg2}' "
316 f
"as the second argument instead of "
317 f
"'{const.VALID_FIELD_GRID_TYPES}' (fields) or "
318 f
"'{const.VALID_SCALAR_TYPES}' (scalars).")
321 f
"'meta_args' should have either 2 or 3 arguments, but "
322 f
"found {len(meta_arg.children[1].children)} in "
325 return kernel_metadata
328 def _get_property(spec_part, property_name):
329 '''Internal utility that gets the property 'property_name' from an
330 fparser2 tree capturing gocean metadata. It is assumed that
331 the code property is part of a type bound procedure and that
332 the other properties are part of the data declarations.
334 :param spec_part: the fparser2 parse tree containing the metadata.
335 :type spec_part: :py:class:`fparser.two.Fortran2003.Derived_Type_Def`
336 :param str property_name: the name of the property whose value \
337 is being extracted from the metadata.
339 :returns: the value of the property.
340 :rtype: :py:class:`fparser.two.Fortran2003.Name | \
341 :py:class:`fparser.two.Fortran2003.Array_Constructor`
343 :raises ParseError: if the property name is not found in the \
347 if property_name.lower() ==
"code":
350 type_bound_procedure = get_child(
351 spec_part, Fortran2003.Type_Bound_Procedure_Part)
352 if not type_bound_procedure:
354 f
"No type-bound procedure found within a 'contains' "
355 f
"section in '{spec_part}'.")
356 if len(type_bound_procedure.children) != 2:
358 f
"Expecting a type-bound procedure, but found "
360 specific_binding = type_bound_procedure.children[1]
361 if not isinstance(specific_binding, Fortran2003.Specific_Binding):
363 f
"Expecting a specific binding for the type-bound "
364 f
"procedure, but found '{specific_binding}' in "
366 binding_name = specific_binding.children[3]
367 procedure_name = specific_binding.children[4]
368 if binding_name.string.lower() !=
"code" and procedure_name:
370 f
"Expecting the type-bound procedure binding-name to be "
371 f
"'code' if there is a procedure name, but found "
372 f
"'{str(binding_name)}' in '{spec_part}'.")
373 if not procedure_name:
376 procedure_name = binding_name
377 return procedure_name
380 component_part = get_child(spec_part, Fortran2003.Component_Part)
381 if not component_part:
383 f
"No declarations were found in the kernel metadata: "
386 for component_decl
in walk(component_part, Fortran2003.Component_Decl):
388 name = component_decl.children[0].string
389 if name.lower() == property_name.lower():
391 comp_init = get_child(
392 component_decl, Fortran2003.Component_Initialization)
395 f
"No value for property {property_name} was found "
396 f
"in '{spec_part}'.")
398 return comp_init.children[1]
400 f
"'{property_name}' was not found in {str(spec_part)}.")
404 :returns: the metadata represented by this instance as Fortran.
409 go_args.append(go_arg.fortran_string())
410 go_args_str =
", &\n".join(go_args)
412 f
"TYPE, EXTENDS(kernel_type) :: {self.name}\n"
413 f
" TYPE(go_arg), DIMENSION({len(self.meta_args)}) :: "
414 f
"meta_args = (/ &\n{go_args_str}/)\n"
415 f
" INTEGER :: ITERATES_OVER = {self.iterates_over}\n"
416 f
" INTEGER :: INDEX_OFFSET = {self.index_offset}\n"
418 f
" PROCEDURE, NOPASS :: code => {self.procedure_name}\n"
419 f
"END TYPE {self.name}\n")
423 def _validate_iterates_over(value):
424 '''Check that 'value' is a valid 'iterates_over' value (go_all_pts,
427 :param str value: the value to check.
429 :raises ValueError: if the supplied value is invalid.
433 if value.lower()
not in const.VALID_ITERATES_OVER:
435 f
"Expected one of {str(const.VALID_ITERATES_OVER)} for "
436 f
"'iterates_over' metadata, but found '{value}'.")
441 :returns: the name of the symbol that will contain the \
442 metadata when lowering.
446 return self.
_name_name
449 def name(self, value):
451 :param str value: set the name of the symbol that will contain \
452 the metadata when lowering.
454 :raises ValueError: if the name is not valid.
457 FortranReader.validate_name(value)
458 self.
_name_name = value
461 def iterates_over(self):
463 :returns: the name of the quantity that this kernel is intended to \
469 @iterates_over.setter
470 def iterates_over(self, value):
472 :param str value: set the iterates_over metadata to the \
479 def _validate_index_offset(value):
480 '''Check that 'value' is a valid 'index_offset' value (go_offset_ne,
483 :param str value: the value to check.
485 :raises ValueError: if the supplied value is invalid.
489 if value.lower()
not in const.SUPPORTED_OFFSETS:
491 f
"Expected one of {str(const.SUPPORTED_OFFSETS)} for "
492 f
"'index_offset' metadata, but found '{value}'.")
495 def index_offset(self):
497 :returns: the name of the quantity that specifies the index \
498 offset (how different field indices relate to each other).
504 def index_offset(self, value):
506 :param str value: set the index_offset metadata to the \
515 :returns: a list of 'meta_arg' objects which capture the \
516 metadata values of the kernel arguments.
517 :rtype: List[:py:class:`psyclone.psyir.common.kernel.\
518 KernelMetadataSymbol.KernelMetadataArg`]
523 def procedure_name(self):
525 :returns: the kernel procedure name specified by the metadata.
530 @procedure_name.setter
531 def procedure_name(self, value):
533 :param str value: set the procedure name specified in the
534 metadata to the specified value.
536 :raises ValueError: if the supplied procedure name is invalid.
540 FortranReader.validate_name(value)
541 except (TypeError, ValueError)
as err:
543 f
"Expected procedure_name to be a valid value but found "
544 f
"'{value}'.")
from err
548 '''Internal class to capture Kernel metadata argument information for
551 :param meta_arg: an fparser2 tree representation of the metadata.
552 :type meta_arg: :py:class:`fparser.two.Fortran2003.Part_Ref`
553 :param parent: a KernelMetadataSymbol instance that captures \
554 other parts of the metadata and references this instance.
555 :type parent: :py:class`psyclone.psyir.common.kernel. \
556 KernelMetadataSymbol`
558 :raises ParseError: if the metadata does not contain two \
562 def __init__(self, meta_arg, parent):
565 arg_list = meta_arg.children[1]
566 if len(arg_list.children) != 2:
568 f
"There should be 2 kernel metadata arguments for a grid "
569 f
"property but found {len(arg_list.children)} in "
573 access = arg_list.children[0].string
577 name = arg_list.children[1].string
582 :returns: the metadata represented by this class as a \
586 return f
"go_arg({self.access}, {self.name})"
589 def _validate_access(value):
590 '''Check that 'value' is a valid 'access' value (go_read, ...).
592 :param str value: the value to check.
594 :raises ValueError: if the supplied value is invalid.
598 if value.lower()
not in const.get_valid_access_types():
600 f
"The first metadata entry for a grid property argument "
601 f
"should be a valid access descriptor (one of "
602 f
"{const.get_valid_access_types()}), but found '{value}'.")
607 :returns: the value of the access descriptor. This \
608 specifies how the grid property is accessed (read, write, \
615 def access(self, value):
617 :param str value: set the access descriptor for this grid \
618 property to the specified value.
624 def _validate_name(value):
625 '''Check that 'value' is a valid 'name' value for a GOcean grid
626 property (go_grid_mask_t, ...).
628 :param str value: the value to check.
630 :raises ValueError: if the supplied value is invalid.
633 config = Config.get()
634 api_config = config.api_conf(
"gocean1.0")
635 grid_property_names = list(api_config.grid_properties.keys())
636 if value.lower()
not in grid_property_names:
638 f
"The second metadata entry for a grid property argument "
639 f
"should have a valid name (one of "
640 f
"{grid_property_names}), but found '{value}'.")
645 :returns: the grid property name as specified by the metadata.
648 return self.
_name_name
651 def name(self, value):
653 :param str value: set the grid property name to the \
657 self.
_name_name = value
660 '''Internal class to capture Kernel metadata information for
663 :param meta_arg: an fparser2 tree representation of the metadata.
664 :type meta_arg: :py:class:`fparser.two.Fortran2003.Part_Ref`
666 :param parent: a KernelMetadataSymbol instance that captures \
667 other parts of the metadata and references this instance.
668 :type parent: :py:class`psyclone.psyir.common.kernel. \
669 KernelMetadataSymbol`
671 :raises ParseError: if the metadata does not contain three \
675 def __init__(self, meta_arg, parent):
678 arg_list = meta_arg.children[1]
679 if len(arg_list.children) != 3:
681 f
"There should be 3 kernel metadata entries for a field "
682 f
"argument, but found {len(arg_list.children)} in "
686 access = arg_list.children[0].string
690 grid_point_type = arg_list.children[1].string
693 if isinstance(arg_list.children[2], Fortran2003.Name):
695 form = arg_list.children[2].string
700 name = arg_list.children[2].children[0].string
704 for stencil_dim
in arg_list.children[2].children[1].children:
705 stencil.append(stencil_dim.children[0])
710 :returns: the metadata represented by this class as a \
715 return (f
"go_arg({self.access}, {self.grid_point_type}, "
716 f
"{self.form}({', '.join(self.stencil)}))")
717 return (f
"go_arg({self.access}, {self.grid_point_type}, "
721 def _validate_access(value):
722 '''Check that 'value' is a valid 'access' value (go_read, ...).
724 :param str value: the value to check.
726 :raises ValueError: if the supplied value is invalid.
730 if value.lower()
not in const.get_valid_access_types():
732 f
"The first metadata entry for a field argument should "
733 f
"be a recognised access descriptor (one of "
734 f
"{const.get_valid_access_types()}), but found '{value}'.")
739 :returns: the access descriptor for this field \
746 def access(self, value):
748 :param str value: set the access descriptor to the \
755 def _validate_grid_point_type(value):
756 '''Check that 'value' is a valid 'grid_point_type' value
759 :param str value: the value to check.
761 :raises ValueError: if the supplied value is invalid.
765 if value.lower()
not in const.VALID_FIELD_GRID_TYPES:
767 f
"The second metadata entry for a field argument should "
768 f
"be a recognised grid-point type descriptor (one of "
769 f
"{const.VALID_FIELD_GRID_TYPES}), but found '{value}'.")
772 def grid_point_type(self):
774 :returns: the value of the grid point type (go_ct, ...) \
775 for the field argument.
780 @grid_point_type.setter
781 def grid_point_type(self, value):
783 :param str value: set the field grid point type (ct, ...) \
784 to the specified value.
790 def _validate_form(value):
791 '''Check that 'value' is a valid 'form' value (go_pointwise, ...).
793 :param str value: the value to check.
795 :raises ValueError: if the supplied value is invalid.
799 if value.lower()
not in const.VALID_STENCIL_NAMES + [
"go_stencil"]:
801 f
"The third metadata entry for a field should "
802 f
"be a recognised stencil descriptor (one of "
803 f
"{const.VALID_STENCIL_NAMES} or 'go_stencil'), "
804 f
"but found '{value}'.")
809 :returns: the form of access for the field (pointwise, \
813 return self.
_form_form
816 def form(self, value):
818 :param str value: set the form of access for the field to \
822 self.
_form_form = value
825 def _validate_stencil_name(value):
826 '''Check that value is the expected stencil name (go_stencil).
828 :raises ValueError: if an invalid stencil name is found.
832 if value.lower() != const.VALID_STENCIL_NAME:
834 f
"The third metadata entry for a field should "
835 f
"be {const.VALID_STENCIL_NAME}(...) if it contains "
836 f
"arguments, but found '{value}'.")
839 def _validate_stencil(value_list):
840 '''Check that 'value_list' is a valid list of 'stencil' elements
841 (e.g. [000, 111, 000]).
843 :param value_list: the values to check.
844 :type value_list: List[str]
846 :raises TypeError: if the supplied argument is not a list.
847 :raises ValueError: if the supplied list is not of size 3.
848 :raises TypeError: if any of the list entries are not strings.
849 :raises ValueError: if any of the list entries do not \
850 conform to the expected format.
853 if not isinstance(value_list, list):
855 f
"Stencil entries should be provided as a list, but found "
856 f
"'{type(value_list).__name__}'.")
857 if len(value_list) != 3:
859 f
"If the third metadata entry is a stencil, it should "
860 f
"contain 3 arguments, but found "
861 f
"{len(value_list)} in {value_list}.")
864 pattern = re.compile(
"[01]{3,3}")
865 for value
in value_list:
866 if not isinstance(value, str):
868 f
"Stencil entries should be strings, but found "
869 f
"'{type(value).__name__}'.")
870 if not pattern.match(value):
872 f
"Stencil entries should follow the regular "
873 f
"expression [01]{{3,3}}, but found '{value}'.")
878 :returns: the stencil value, or None if there is no stencil.
879 :rtype: Optional[List[str]]
886 :param value_list: set the new stencil value, encoded as \
887 three strings, each of three digits (0 or 1), see the \
888 `psyclone user guide <https://psyclone.readthedocs.io/en/\
889 stable/gocean1p0.html#argument-metadata-meta-args>` \
891 :type value_list: List[str]
900 if self.
_form_form.upper() !=
"GO_STENCIL":
901 self.
_form_form =
"GO_STENCIL"
904 '''Internal class to capture Kernel metadata information for
907 :param meta_arg: an fparser2 tree representation of the metadata.
908 :type meta_arg: :py:class:`fparser.two.Fortran2003.Part_Ref`
910 :param parent: a KernelMetadataSymbol instance that captures \
911 other parts of the metadata and references this instance.
912 :type parent: :py:class`psyclone.psyir.common.kernel. \
913 KernelMetadataSymbol`
915 :raises ParseError: if the metadata does not contain three \
919 def __init__(self, meta_arg, parent):
922 arg_list = meta_arg.children[1]
923 if len(arg_list.children) != 3:
925 f
"There should be 3 kernel metadata entries for a scalar "
926 f
"argument, but found {len(arg_list.children)} in "
936 access = arg_list.children[0].string
941 datatype = arg_list.children[1].string
946 form = arg_list.children[2].string
948 self.
_form_form = form
952 :returns: the metadata represented by this class as a \
956 return f
"go_arg({self.access}, {self.datatype}, {self.form})"
959 def _validate_access(value):
960 '''Check that 'value' is a valid 'access' value (go_read, ...).
962 :param str value: the value to check.
964 :raises ValueError: if the supplied value is invalid.
968 if value.lower()
not in const.get_valid_access_types():
970 f
"The first metadata entry for a scalar argument should "
971 f
"be a recognised access descriptor (one of "
972 f
"{const.get_valid_access_types()}), but found '{value}'.")
977 :returns: the access descriptor for this scalar argument.
985 :param str value: set the access descriptor for this
986 scalar argument to the specified value.
992 def _validate_datatype(value):
993 '''Check that 'value' is a valid scalar 'datatype' value
996 :param str value: the value to check.
998 :raises ValueError: if the supplied value is invalid.
1002 if value.lower()
not in const.VALID_SCALAR_TYPES:
1004 f
"The second metadata entry for a scalar argument should "
1005 f
"be a recognised name (one of "
1006 f
"{const.VALID_SCALAR_TYPES}), but found '{value}'.")
1011 :returns: the datatype of the scalar argument.
1019 :param str value: set the scalar datatype to the specified \
1026 def _validate_form(value):
1027 '''Check that 'value' is a valid 'form' value (go_pointwise).
1029 :param str value: the value to check.
1031 :raises ValueError: if the supplied value is invalid.
1035 if value.lower()
not in const.VALID_STENCIL_NAMES:
1037 f
"The third metadata entry for a scalar should "
1038 f
"be a recognised name (one of "
1039 f
"{const.VALID_STENCIL_NAMES}), but found '{value}'.")
1044 :returns: the form of access for the scalar (pointwise ...).
1047 return self.
_form_form
1052 :param str value: set the form of access for the scalar to \
1053 the specified value.
1056 self.
_form_form = value
1059 __all__ = [
'GOceanContainer',
'GOceanKernelMetadata']
def lower_to_language_level(self)
def create(cls, name, metadata, symbol_table, children)
def grid_point_type(self)
def _validate_stencil_name(value)
def stencil(self, value_list)
def _validate_stencil(value_list)
def grid_point_type(self, value)
def _validate_access(value)
def _validate_grid_point_type(value)
def _validate_form(value)
def _validate_access(value)
def _validate_name(value)
def _validate_form(value)
def _validate_datatype(value)
def _validate_access(value)
def create_from_fortran_string(fortran_string)
def _validate_iterates_over(value)
def index_offset(self, value)
def procedure_name(self, value)
def iterates_over(self, value)
def create_from_psyir(symbol)
def _validate_index_offset(value)