diff --git a/doc/tutorials_and_examples/lfric_examples.rst b/doc/tutorials_and_examples/lfric_examples.rst index a698808c07..7f166afde9 100644 --- a/doc/tutorials_and_examples/lfric_examples.rst +++ b/doc/tutorials_and_examples/lfric_examples.rst @@ -18,10 +18,12 @@ Example 1: Basic Operation Basic operation of PSyclone with an ``invoke()`` containing two kernels, one :ref:`user-supplied `, the other a -:ref:`Built-in `. Code is generated both with and +:ref:`Built-in `. The user-supplied kernel accepts +field, scalar and scalar-array arguments. Code is generated both with and without distributed-memory support. Also demonstrates the use of the ``-d`` flag to specify where to search for user-supplied kernel code -(see :ref:`psyclone_command` section for more details). +(see :ref:`psyclone_command` section for more details). Generated +code can be compiled (``make compile``) but not executed. Example 2: Applying Transformations ----------------------------------- diff --git a/doc/user_guide/lfric.rst b/doc/user_guide/lfric.rst index 75f019a079..0dd2c4e2f7 100644 --- a/doc/user_guide/lfric.rst +++ b/doc/user_guide/lfric.rst @@ -116,6 +116,7 @@ with ``GH_SCALAR`` metadata. Scalar arguments can have ``real``, ``integer`` or ``logical`` data type in :ref:`user-defined Kernels ` (``logical`` data type is not supported in the :ref:`LFRic Built-ins `). +See example ``examples/lfric/eg1``. .. _lfric-array: @@ -127,6 +128,7 @@ least rank (number of dimensions) one. Scalar arrays are identified with ``GH_SCALAR_ARRAY`` metadata. As with scalars, array arguments can have ``real``, ``integer`` or ``logical`` data type in :ref:`user-defined Kernels `. +See example ``examples/lfric/eg1``. .. _lfric-field: @@ -1242,8 +1244,9 @@ has. More details about the supported function spaces are in subsection For example, the metadata for a kernel that applies a column-wise operator to a field might look like:: - type(arg_type) :: meta_args(3) = (/ & + type(arg_type) :: meta_args(4) = (/ & arg_type(GH_FIELD, GH_REAL, GH_INC, W1), & + arg_type(GH_SCALAR_ARRAY, GH_INTEGER, GH_READ, 5), & arg_type(GH_FIELD, GH_REAL, GH_READ, W2H), & arg_type(GH_COLUMNWISE_OPERATOR, GH_REAL, GH_READ, W1, W2H) & /) diff --git a/examples/lfric/code/testkern_mod.F90 b/examples/lfric/code/testkern_mod.F90 index 8a206cc82c..e613024068 100644 --- a/examples/lfric/code/testkern_mod.F90 +++ b/examples/lfric/code/testkern_mod.F90 @@ -15,12 +15,13 @@ module testkern_mod implicit none type, extends(kernel_type) :: testkern_type - type(arg_type), dimension(5) :: meta_args = & - (/ arg_type(gh_scalar, gh_real, gh_read), & - arg_type(gh_field, gh_real, gh_inc, w1), & - arg_type(gh_field, gh_real, gh_read, w2), & - arg_type(gh_field, gh_real, gh_read, w2), & - arg_type(gh_field, gh_real, gh_read, w3) & + type(arg_type), dimension(6) :: meta_args = & + (/ arg_type(gh_scalar, gh_real, gh_read ), & + arg_type(gh_scalar_array, gh_real, gh_read, 2 ), & + arg_type(gh_field, gh_real, gh_inc, w1), & + arg_type(gh_field, gh_real, gh_read, w2), & + arg_type(gh_field, gh_real, gh_read, w2), & + arg_type(gh_field, gh_real, gh_read, w3) & /) integer :: operates_on = cell_column contains @@ -29,10 +30,11 @@ module testkern_mod contains - subroutine testkern_code(nlayers, ascalar, & - fld1, fld2, fld3, fld4, & - ndf_w1, undf_w1, map_w1, & - ndf_w2, undf_w2, map_w2, & + subroutine testkern_code(nlayers, ascalar, & + dims_ascalar_array, ascalar_array, & + fld1, fld2, fld3, fld4, & + ndf_w1, undf_w1, map_w1, & + ndf_w2, undf_w2, map_w2, & ndf_w3, undf_w3, map_w3) implicit none @@ -44,6 +46,8 @@ subroutine testkern_code(nlayers, ascalar, & integer(kind=i_def), intent(in), dimension(ndf_w1) :: map_w1 integer(kind=i_def), intent(in), dimension(ndf_w2) :: map_w2 integer(kind=i_def), intent(in), dimension(ndf_w3) :: map_w3 + integer(kind=i_def), intent(in), dimension(2) :: dims_ascalar_array + real(kind=r_def), intent(in), dimension(dims_ascalar_array(1), dims_ascalar_array(2)) :: ascalar_array real(kind=r_def), intent(in) :: ascalar real(kind=r_def), intent(inout), dimension(undf_w1) :: fld1 real(kind=r_def), intent(in), dimension(undf_w2) :: fld2 diff --git a/examples/lfric/eg1/Makefile b/examples/lfric/eg1/Makefile index a1e376c16b..55b179078b 100644 --- a/examples/lfric/eg1/Makefile +++ b/examples/lfric/eg1/Makefile @@ -5,20 +5,53 @@ # See the full LICENSE file in the project root for details. # ----------------------------------------------------------------------------- -include ../../common.mk +# The compiler to use may be specified via the F90 environment variable +# +# export F90=gfortran +# export F90FLAGS="-g -O0" + +include ../lfric_common.mk + +OBJ = testkern_mod.o single_invoke_psy.o single_invoke_alg.o + +GENERATED_FILES = single_invoke_alg.f90 single_invoke_psy.f90 \ + *.mod ${OBJ} *.exe + +F90 ?= gfortran +F90FLAGS ?= -g -O0 .PHONY: transform compile run # The '-d ../code' argument specifies that PSyclone should search the # '../code' directory when looking for user-supplied kernels. -transform: - @echo "Sequential code:" - ${PSYCLONE} -api lfric -d ../code -nodm ./single_invoke.x90 +transform: single_invoke_alg.f90 @echo "With distributed-memory support:" ${PSYCLONE} -api lfric -d ../code ./single_invoke.x90 -compile: transform - @echo "No compilation supported for lfric/eg1" +single_invoke_alg.f90 single_invoke_psy.f90: single_invoke.x90 + @echo "Sequential code:" + ${PSYCLONE} -api lfric -d ../code -nodm ./single_invoke.x90 \ + -oalg single_invoke_alg.f90 \ + -opsy single_invoke_psy.f90 + +# Note that we compile and link primarily for testing purposes - the +# resulting binary won't run successfully because the example omits +# all the necessary LFRic setup. +compile: $(LFRIC_LIB) single_invoke_psy.o single_invoke_alg.o + $(F90) $(F90FLAGS) -o will_not_run.exe ${OBJ} ${LDFLAGS} + +testkern_mod.o: ../code/testkern_mod.F90 + $(F90) $(F90FLAGS) -c $< + +%.o: %.f90 + $(F90) $(F90FLAGS) -c $< + +%.o: %.F90 + $(F90) $(F90FLAGS) -c $< + +single_invoke_alg.o: single_invoke_psy.o +single_invoke_psy.o: testkern_mod.o +testkern_mod.o: $(LFRIC_LIB) run: compile @echo "No run targets for lfric/eg1" diff --git a/examples/lfric/eg1/single_invoke.x90 b/examples/lfric/eg1/single_invoke.x90 index eaa4619e77..16b0652105 100644 --- a/examples/lfric/eg1/single_invoke.x90 +++ b/examples/lfric/eg1/single_invoke.x90 @@ -7,7 +7,7 @@ program single_invoke - ! Example of the LFRic domain with a single 'invoke()' that calls two + ! Example of the LFRic API with a single 'invoke()' that calls two ! kernels. use constants_mod, only: r_def use field_mod, only: field_type @@ -15,10 +15,17 @@ program single_invoke implicit none - type(field_type) :: f1, f2, m1, m2 - real(r_def) :: a + ! LFRic field objects. + type(field_type) :: f1, f2, m1, m2 + ! A simple scalar. + real(r_def) :: a + ! A 'scalar array'. The number of dimensions of this array must match + ! that specified in the kernel metadata. The extents of the dimensions + ! are queried in the generated PSy layer and passed to the kernel + ! subroutine. They are therefore not provided to the `invoke` call. + real(r_def), dimension(50,100) :: ascalar_array call invoke( setval_c(f1, 0.0_r_def), & - testkern_type(a, f1, f2, m1, m2) ) + testkern_type(a, ascalar_array, f1, f2, m1, m2) ) end program single_invoke diff --git a/examples/lfric/eg2/multi_invoke_mod.x90 b/examples/lfric/eg2/multi_invoke_mod.x90 index 8155482764..487b229ee4 100644 --- a/examples/lfric/eg2/multi_invoke_mod.x90 +++ b/examples/lfric/eg2/multi_invoke_mod.x90 @@ -16,8 +16,9 @@ program multi_invoke implicit none - type(field_type) :: f1, f2, m1, m2 - real(r_def) :: a + type(field_type) :: f1, f2, m1, m2 + real(r_def) :: a + real(r_def), dimension(50,100) :: ascalar_array call invoke( setval_c(f1, 0.0_r_def), & setval_c(f2, 0.0_r_def), & @@ -25,7 +26,7 @@ program multi_invoke setval_c(m2, 0.0_r_def) ) ! This example artificially has two separate 'invoke()' calls. In practice ! these would be merged into a single call. - call invoke( testkern_type(a, f1, f2, m1, m2), & - testkern_type(a, f1, f2, m1, m2) ) + call invoke( testkern_type(a, ascalar_array, f1, f2, m1, m2), & + testkern_type(a, ascalar_array, f1, f2, m1, m2) ) end program multi_invoke diff --git a/src/psyclone/domain/lfric/kern_call_invoke_arg_list.py b/src/psyclone/domain/lfric/kern_call_invoke_arg_list.py index d7d429c739..1d58822e2e 100644 --- a/src/psyclone/domain/lfric/kern_call_invoke_arg_list.py +++ b/src/psyclone/domain/lfric/kern_call_invoke_arg_list.py @@ -14,6 +14,7 @@ from psyclone.core import VariablesAccessMap from psyclone.domain.lfric.arg_ordering import ArgOrdering from psyclone.domain.lfric.lfric_constants import LFRicConstants +from psyclone.domain.lfric.lfric_kern import LFRicKern from psyclone.domain.lfric.lfric_types import LFRicTypes from psyclone.psyir.symbols import ( ArrayType, DataSymbol, DataTypeSymbol, UnresolvedType, SymbolTable, @@ -27,15 +28,13 @@ class KernCallInvokeArgList(ArgOrdering): kernel, according to that kernel's metadata. :param kern: the kernel object for which to determine arguments. - :type kern: :py:class:`psyclone.domain.lfric.LFRicKern` - :param symbol_table: the symbol table associated with the routine that \ + :param symbol_table: the symbol table associated with the routine that contains the `invoke` of this kernel. - :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable` :raises TypeError: if supplied symbol table is of incorrect type. ''' - def __init__(self, kern, symbol_table): + def __init__(self, kern: LFRicKern, symbol_table: SymbolTable): super().__init__(kern) if not isinstance(symbol_table, SymbolTable): raise TypeError( diff --git a/src/psyclone/domain/lfric/lfric_invoke.py b/src/psyclone/domain/lfric/lfric_invoke.py index adb69bd6ea..bbd835b8c2 100644 --- a/src/psyclone/domain/lfric/lfric_invoke.py +++ b/src/psyclone/domain/lfric/lfric_invoke.py @@ -285,7 +285,8 @@ def setup_psy_layer_symbols(self): self.cma_ops, self.boundary_conditions, self.function_spaces, self.evaluators, self.reference_element_properties, - self.mesh_properties, self.loop_bounds]: + self.mesh_properties, self.loop_bounds, + self.scalar_array_args]: cursor = entities.initialise(cursor) if self.schedule.reductions(reprod=True): diff --git a/src/psyclone/domain/lfric/lfric_scalar_array_args.py b/src/psyclone/domain/lfric/lfric_scalar_array_args.py index 1e47e73629..96dc74ae31 100644 --- a/src/psyclone/domain/lfric/lfric_scalar_array_args.py +++ b/src/psyclone/domain/lfric/lfric_scalar_array_args.py @@ -20,7 +20,8 @@ LFRicKern, LFRicInvoke) from psyclone.errors import GenerationError, InternalError from psyclone.psyGen import FORTRAN_INTENT_NAMES -from psyclone.psyir.nodes import Literal, ArrayReference +from psyclone.psyir.nodes import ( + ArrayReference, Assignment, IntrinsicCall, Literal, Reference) from psyclone.psyir.symbols import (DataSymbol, ArrayType, ScalarType, ArgumentInterface) @@ -32,7 +33,7 @@ class LFRicScalarArrayArgs(LFRicCollection): Handles the declarations of ScalarArray kernel arguments appearing in either an Invoke or a Kernel stub. - :param node: the Invoke or Kernel stub for which to manage the \ + :param node: the Invoke or Kernel stub for which to manage the ScalarArray arguments. ''' def __init__(self, node: Union[LFRicKern, LFRicInvoke]): @@ -179,36 +180,55 @@ def _create_declarations(self): # ScalarArray arguments for intent in FORTRAN_INTENT_NAMES: for arg in self._scalar_array_args[intent]: - # Create the dimensions array symbol - dims_array_symbol = self.symtab.find_or_create_tag( + # Create the symbol for the dimensions array. + self.symtab.find_or_create_tag( tag="dims_" + arg.name, symbol_type=DataSymbol, datatype=ArrayType( LFRicTypes("LFRicIntegerScalarDataType")(), [arg._array_ndims])) - dims_array_symbol.interface = ArgumentInterface( - INTENT_MAPPING[intent]) - self.symtab.append_argument(dims_array_symbol) - # Create list of dims_array references - sym_list = [ArrayReference.create( - dims_array_symbol, - [Literal(str(idx), ScalarType.integer_type())]) - for idx in range(1, arg._array_ndims + 1)] # Find the ScalarArray tag and convert it to an ArrayType array_symbol = self.symtab.lookup_with_tag( "AlgArgs_" + arg.name) array_symbol.datatype = ArrayType( type_map[arg.intrinsic_type], - sym_list) - # Replace the symbol with itself to ensure - # the ScalarArray is generated after the - # dimensions array to avoid compilation errors - # TODO: #2202 may allow this to be removed - self.symtab.swap(array_symbol, array_symbol) + arg._array_ndims*[ArrayType.Extent.ATTRIBUTE]) array_symbol.interface = ArgumentInterface( INTENT_MAPPING[intent]) self.symtab.append_argument(array_symbol) + def initialise(self, cursor: int) -> int: + ''' + Add code to initialise the array holding the extent of each dimension + of any ScalarArray arguments. + + :param cursor: position to add the next initialisation statements. + + :returns: Updated cursor value. + + ''' + first = True + # For each ScalarArray argument... + for intent in FORTRAN_INTENT_NAMES: + for arg in self._scalar_array_args[intent]: + # We want dims_array = SHAPE(scalar_array) + dim_array_sym = self.symtab.lookup_with_tag("dims_" + arg.name) + array_symbol = self.symtab.lookup_with_tag( + "AlgArgs_" + arg.name) + shape_call = IntrinsicCall.create( + IntrinsicCall.Intrinsic.SHAPE, + [Reference(array_symbol)]) + assign = Assignment.create(lhs=Reference(dim_array_sym), + rhs=shape_call) + if first: + assign.preceding_comment = ( + "Store dimensions of ScalarArray arguments") + first = False + self._invoke.schedule.addchild(assign, cursor) + cursor += 1 + + return cursor + # ---------- Documentation utils -------------------------------------------- # # The list of module members that we wish AutoAPI to generate diff --git a/src/psyclone/tests/domain/lfric/conftest.py b/src/psyclone/tests/domain/lfric/conftest.py index 8b151fd9ec..6f3c42ef4f 100644 --- a/src/psyclone/tests/domain/lfric/conftest.py +++ b/src/psyclone/tests/domain/lfric/conftest.py @@ -28,15 +28,15 @@ def lfrickern_fixture(): mdata_code = ''' module testkern_field_mod type, extends(kernel_type) :: testkern_field_type - type(arg_type), meta_args(8) = & - (/ arg_type(gh_scalar, gh_real, gh_read), & - arg_type(gh_field, gh_real, gh_readinc, w0), & - arg_type(gh_field, gh_real, gh_inc, w1), & - arg_type(gh_field*3,gh_integer, gh_read, w2), & - arg_type(gh_field, gh_integer, gh_write, wtheta), & - arg_type(gh_field, gh_integer, gh_read, w3), & - arg_type(gh_scalar, gh_integer, gh_read), & - arg_type(gh_scalar, gh_logical, gh_read) & + type(arg_type), meta_args(8) = & + (/ arg_type(gh_scalar, gh_real, gh_read), & + arg_type(gh_field, gh_real, gh_readinc, w0), & + arg_type(gh_field, gh_real, gh_inc, w1), & + arg_type(gh_field*3, gh_integer, gh_read, w2), & + arg_type(gh_field, gh_integer, gh_write, wtheta), & + arg_type(gh_field, gh_integer, gh_read, w3), & + arg_type(gh_scalar_array, gh_integer, gh_read, 3), & + arg_type(gh_scalar, gh_logical, gh_read) & /) type(func_type), dimension(2) :: meta_funcs = & (/ func_type(w1, gh_basis), & diff --git a/src/psyclone/tests/domain/lfric/lfric_scalar_codegen_test.py b/src/psyclone/tests/domain/lfric/lfric_scalar_codegen_test.py index 3fb1fc5e70..f9d6dc7e9a 100644 --- a/src/psyclone/tests/domain/lfric/lfric_scalar_codegen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_scalar_codegen_test.py @@ -578,32 +578,39 @@ def test_scalar_array(tmpdir, dist_mem): generated_code = str(psy.gen) expected_subroutine = ( " subroutine invoke_0(f1, real_array, logical_array, integer_array, " - "dims_integer_array, a, f2, f3, f4, b, dims_real_array, " - "dims_logical_array, dims_integer_array_1)\n" + "dims_integer_array, a, f2, f3, f4, b)\n" ) + assert expected_subroutine in generated_code - expected_declarations = ( + expected_arg_declarations = ( " type(field_type), intent(in) :: f1\n" + " real(kind=r_def), dimension(:,:), intent(in) :: real_array\n" + " logical(kind=l_def), dimension(:), intent(in) :: logical_array\n" + " integer(kind=i_def), dimension(:,:,:,:), intent(in) :: " + "integer_array\n" " integer(kind=i_def), intent(in) :: dims_integer_array\n" " integer(kind=i_def), intent(in) :: a\n" " type(field_type), intent(in) :: f2\n" " type(field_type), intent(in) :: f3\n" " type(field_type), intent(in) :: f4\n" " integer(kind=i_def), intent(in) :: b\n" - " integer(kind=i_def), dimension(2), intent(in) :: " - "dims_real_array\n" - " real(kind=r_def), dimension(dims_real_array(1)," - "dims_real_array(2)), intent(in) :: real_array\n" - " integer(kind=i_def), dimension(1), intent(in) :: " - "dims_logical_array\n" - " logical(kind=l_def), dimension(dims_logical_array(1)), " - "intent(in) :: logical_array\n" - " integer(kind=i_def), dimension(4), intent(in) :: " - "dims_integer_array_1\n" - " integer(kind=i_def), dimension(dims_integer_array_1(1)," - "dims_integer_array_1(2),dims_integer_array_1(3)," - "dims_integer_array_1(4)), intent(in) :: integer_array\n" ) + assert expected_arg_declarations in generated_code + + expected_local_declns = ( + " integer(kind=i_def), dimension(2) :: dims_real_array\n" + " integer(kind=i_def), dimension(1) :: dims_logical_array\n" + " integer(kind=i_def), dimension(4) :: dims_integer_array_1\n" + ) + assert expected_local_declns in generated_code + + expected_init = ( + " ! Store dimensions of ScalarArray arguments\n" + " dims_real_array = SHAPE(real_array)\n" + " dims_logical_array = SHAPE(logical_array)\n" + " dims_integer_array_1 = SHAPE(integer_array)\n" + ) + assert expected_init in generated_code expected_calls = ( " do cell = loop0_start, loop0_stop, 1\n" @@ -618,8 +625,6 @@ def test_scalar_array(tmpdir, dist_mem): "ndf_w2, undf_w2, map_w2(:,cell), ndf_w3, undf_w3, map_w3(:,cell))\n" " enddo\n" ) - assert expected_subroutine in generated_code - assert expected_declarations in generated_code assert expected_calls[0] in generated_code assert expected_calls[1] in generated_code assert LFRicBuild(tmpdir).code_compiles(psy)