diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index ef2c214e355..aec7fc05b77 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -38,14 +38,24 @@ # Modified by N. M. Nobre, STFC Daresbury Lab # This workflow will use a self-hosted runner to perform the more expensive -# integrations tests that are not run on GHA systems. At the moment it only -# runs the test suite with compilation enabled (using gfortran). +# compilation tests that are not run on GHA systems. At the moment it: +# +# * Runs the test suite with compilation with gfortran and nvfortran; +# * Builds the examples with gfortran and nvfortran (the latter with +# OpenACC enabled). +# * Builds the tutorials with gfortran and nvfortran. name: Compilation tests on: push +env: + PYTHON_VERSION: 3.11.4 + GFORTRAN_VERSION: 13.2.0 + NVFORTRAN_VERSION: 23.7 + CUDA_VERSION: 12.2 + jobs: run_if_on_mirror: # Only PSyclone-mirror has the necessary self-hosted runner. @@ -66,7 +76,7 @@ jobs: commit-filter: '[skip ci]' - name: Install dependencies run: | - module load python/3.11.3 + module load python/${PYTHON_VERSION} python -m venv .runner_venv . .runner_venv/bin/activate python -m pip install --upgrade pip @@ -74,9 +84,52 @@ jobs: # submodule instead of the released version (from PyPI) then # uncomment the following line: pip install external/fparser - pip install .[test] - - name: Test with pytest and compilation + pip install .[test,psydata] + - name: Unit tests with compilation - gfortran + run: | + module load python/${PYTHON_VERSION} + . .runner_venv/bin/activate + module load gcc/${GFORTRAN_VERSION} openmpi netcdf_fortran + pytest -n 2 --f90=gfortran --compile --compileopencl src/psyclone/tests + module rm netcdf_fortran gcc + - name: Unit tests with compilation - nvfortran + run: | + module load python/${PYTHON_VERSION} + . .runner_venv/bin/activate + module load nvidia-hpcsdk/${NVFORTRAN_VERSION} netcdf_fortran + # We have to tell nvfortran where to find the OpenCL library. + pytest -n 2 --f90=nvfortran --f90flags="-L/apps/packages/compilers/nvidia-hpcsdk/Linux_x86_64/${NVFORTRAN_VERSION}/cuda/${CUDA_VERSION}/targets/x86_64-linux/lib" --compile --compileopencl src/psyclone/tests + module rm netcdf_fortran nvidia-hpcsdk + - name: Examples with compilation - gfortran + run: | + module load python/${PYTHON_VERSION} + . .runner_venv/bin/activate + module load gcc/${GFORTRAN_VERSION} openmpi netcdf_fortran + # Although we're using gfortran, we link with the OpenCL lib that comes + # with CUDA. + make -C examples allclean + F90=gfortran F90FLAGS="-L/apps/packages/compilers/nvidia-hpcsdk/Linux_x86_64/${NVFORTRAN_VERSION}/cuda/${CUDA_VERSION}/targets/x86_64-linux/lib" make -C examples compile + - name: Tutorials with compilation - gfortran + run: | + module load python/${PYTHON_VERSION} + . .runner_venv/bin/activate + make -C tutorial/practicals allclean + module load gcc/${GFORTRAN_VERSION} openmpi netcdf_fortran + make -C tutorial/practicals compile + - name: Examples with compilation - nvfortran + run: | + module load python/${PYTHON_VERSION} + . .runner_venv/bin/activate + make -C examples allclean + module load nvidia-hpcsdk/${NVFORTRAN_VERSION} netcdf_fortran + # We have to tell nvfortran where to find the OpenCL library. + F90=nvfortran F90FLAGS="-acc -Minfo=all -L/apps/packages/compilers/nvidia-hpcsdk/Linux_x86_64/${NVFORTRAN_VERSION}/cuda/${CUDA_VERSION}/targets/x86_64-linux/lib" make -C examples compile + - name: Tutorials with compilation - nvfortran run: | - module load python/3.11.3 + module load python/${PYTHON_VERSION} . .runner_venv/bin/activate - pytest -n 2 --f90=gfortran --compile src/psyclone/tests + make -C tutorial/practicals allclean + module load nvidia-hpcsdk/${NVFORTRAN_VERSION} netcdf_fortran + # TODO #2251. Cannot build the LFRic practicals with 23.5/7 of nvfortran + # because the compilation of nan_test.f90 gives an ICE. + F90=nvfortran F90FLAGS="-acc -Minfo=all" make -C tutorial/practicals/nemo compile diff --git a/.github/workflows/lfric_test.yml b/.github/workflows/lfric_test.yml new file mode 100644 index 00000000000..abdd1c0b28f --- /dev/null +++ b/.github/workflows/lfric_test.yml @@ -0,0 +1,113 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Author S. Siso, STFC Daresbury Lab + +# This workflow will use a self-hosted runner to perform the more expensive +# integrations tests that are not run on GHA systems. + +name: LFRic Integration Tests + +on: + push + +jobs: + run_if_on_mirror: + if: ${{ github.repository == 'stfc/PSyclone-mirror' }} + runs-on: self-hosted + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + # This is required to get the commit history for merge commits for + # the ci-skip check below. + fetch-depth: '0' + - name: Check for [skip ci] in commit message + uses: mstachniuk/ci-skip@v1 + with: + # This setting causes the tests to 'fail' if [skip ci] is specified + fail-fast: true + commit-filter: '[skip ci]' + - name: Install dependencies + run: | + python -m venv .runner_venv + . .runner_venv/bin/activate + python -m pip install --upgrade pip + # If you wish to install the version of fparser pointed to by the + # submodule instead of the released version (from PyPI) then + # uncomment the following line: + pip install external/fparser + pip install .[test] + pip install jinja2 + + # PSyclone, compile and run MetOffice LFRic with 6 MPI ranks + - name: LFRic passthrough (with DistributedMemory) + run: | + . .runner_venv/bin/activate + export PSYCLONE_LFRIC_DIR=${GITHUB_WORKSPACE}/examples/lfric/scripts + export MINIAPP_DIR=${HOME}/LFRic/miniapps/gungho_model + cd ${MINIAPP_DIR} + # Compile + module load lfric_env + export PSYCLONE_CONFIG=${HOME}/lfric_psyclone_config.cfg + make clean + make -j 6 build + # Run + cd example + cp ${HOME}/lfric_gunho_configuration.nml configuration.nml + mpirun -n 6 ../bin/gungho_model configuration.nml + tail PET0.gungho_model.Log + cat timer.txt + + # PSyclone, compile and run MetOffice LFRic with all optimisations and 6 OpenMP threads + - name: LFRic with all transformations + run: | + . .runner_venv/bin/activate + export PSYCLONE_LFRIC_DIR=${GITHUB_WORKSPACE}/examples/lfric/scripts + export MINIAPP_DIR=${HOME}/LFRic/miniapps/gungho_model + cd ${MINIAPP_DIR} + # Prepare script (LFRics expects a folder with a global.py) + mkdir -p psyclone-test + cp ${PSYCLONE_LFRIC_DIR}/everything_everywhere_all_at_once.py psyclone-test/global.py + # Compile + module load lfric_env + export PSYCLONE_CONFIG=${HOME}/lfric_psyclone_config.cfg + make clean + make OPTIMISATION_PATH=${MINIAPP_DIR}/psyclone-test -j 6 build + # Run + cd example + cp ${HOME}/lfric_gunho_configuration.nml configuration.nml + export OMP_NUM_THREADS=6 + mpirun -n 1 ../bin/gungho_model configuration.nml + cat timer.txt diff --git a/.github/workflows/nemo_tests.yml b/.github/workflows/nemo_tests.yml index 478d76651bb..2e91d5233e9 100644 --- a/.github/workflows/nemo_tests.yml +++ b/.github/workflows/nemo_tests.yml @@ -100,7 +100,28 @@ jobs: tail -n 1 output.txt | grep -q "S_min: 0.482686" tail -n 1 output.txt | grep -q "S_max: 0.407622" grep -A 1 "Elapsed Time" output.txt - echo $GITHUB_REF_NAME $GITHUB_SHA $(grep -A 1 "Elapsed Time" output.txt | head -n 2 | tail -n 1) >> ${NEMO_DIR}/performance_history + echo $GITHUB_REF_NAME $GITHUB_SHA $(grep -A 1 "Elapsed Time" output.txt | head -n 2 | tail -n 1) >> ${NEMO_DIR}/performance_history_openmp_gpu + + # PSyclone, compile and run MetOffice NEMO with OpenACC kernels for GPUs + - name: NEMO MetOffice OpenACC kernels for GPU + run: | + . .runner_venv/bin/activate + export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts + export NEMO_DIR=${HOME}/NEMO + cd examples/nemo/scripts + make -j 4 openacc_kernels + module load nvidia-hpcsdk netcdf_fortran + COMPILER_ARCH=linux_nvidia_acc_gpu make -j 4 compile-openacc_kernels + export NV_ACC_POOL_THRESHOLD=75 + make run-openacc_kernels | tee output.txt + # Check the output is as expected for the first 6 digits + tail -n 1 output.txt | grep -q " it : 10" + tail -n 1 output.txt | grep -q "|ssh|_max: 0.259483" + tail -n 1 output.txt | grep -q "|U|_max: 0.458515" + tail -n 1 output.txt | grep -q "S_min: 0.482686" + tail -n 1 output.txt | grep -q "S_max: 0.407622" + grep -A 1 "Elapsed Time" output.txt + echo $GITHUB_REF_NAME $GITHUB_SHA $(grep -A 1 "Elapsed Time" output.txt | head -n 2 | tail -n 1) >> ${NEMO_DIR}/performance_history_openacc_kernels_gpu # PSyclone, compile and run ECMWF NEMO with OpenMP for CPUs - name: NEMO ECMWF OpenMP for CPU @@ -129,4 +150,4 @@ jobs: tail -n 1 output.txt | grep -q "S_min: 0.108530" tail -n 1 output.txt | grep -q "S_max: 0.404045" grep -A 1 "Elapsed Time" output.txt - echo $GITHUB_REF_NAME $GITHUB_SHA $(grep -A 1 "Elapsed Time" output.txt | head -n 2 | tail -n 1) >> ${NEMO_DIR}/performance_history + echo $GITHUB_REF_NAME $GITHUB_SHA $(grep -A 1 "Elapsed Time" output.txt | head -n 2 | tail -n 1) >> ${NEMO_DIR}/performance_history_openmp_cpu diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 461a430a4a1..9887ca8e207 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -93,7 +93,14 @@ jobs: - uses: actions/setup-python@v4 - run: python -m pip install --upgrade pip - run: pip install .[doc] - - run: cd doc/developer_guide; make doctest + # Sphinx since version 7.2 (7.2.0/1/2) aborts with + # exec('from sympy import *', global_dict) + # File "", line 1, in + # AttributeError: module 'sympy' has no attribute 'external' + # when it is run without creating the documentation first. + # So till this is fixed in Sphinx, we trigger the creation + # of the html documents before actually running doctest + - run: cd doc/developer_guide; make html; make doctest build: if: ${{ github.repository != 'stfc/PSyclone-mirror' }} runs-on: ubuntu-latest diff --git a/changelog b/changelog index 43bdd05ca56..3d96640bc6a 100644 --- a/changelog +++ b/changelog @@ -509,6 +509,79 @@ 172) PR #2163 for #1537. Support array-slicing notation in SymPy comparisons. + 173) PR #2221 for #2214. Fix names of invokes containing a single + kernel in the PSyIR of the LFRic Alg layer. + + 174) PR #2225 for #2224. Fix erroneous CI link check error by + omitting test for the particular link. + + 175) PR #2172 for #1833. Fixes the LFRic tutorials so that they + build correctly. + + 176) PR #2227 for #2226. Adds new Node.path_from(ancestor) method. + + 177) PR #2158 for #81. Ensures all example/test Fortran code for + LFRic follows the LFRic naming convention. + + 178) PR #2218 towards #2132. Adds LFRic integration tests and + new, associated optimisation scripts. Includes bug fixes for + matmul inlining transformation. + + 179) PR #2230 for #2228. Improve reporting of errors due to kernel + functors not explicitly included in algorithm use statements. + + 180) PR #2207 for #1419. Split the DataSymbol constant_value attribute + into is_constant (bool) and initial_value (PSyIR Node). This allows + parsing Fortran declarations with initial_value that are not constant. + + 181) PR #2231 for #2213. The Fortran frontend and backend now also + consider that UnresolvedInterface symbols can come from the body of + a Fortran Interface. + + 182) PR #2240 for #2234. Fix WhileLoop issue in the NEMO OpenACC kernels + script. + + 183) PR #2182 for #2179. Fix LFRic eg14 nvfortran compilation and add + nvfortran examples and tutorials steps in the Integration tests. + + 184) PR #2250 for #2248 and #1575. Fixes bug in line-length limiter + to prevent it breaking a line before the first non-whitepsace + character. + + 185) PR #2262 towards #446. Implement reference_access for Call nodes. + + 186) PR #2184 for #2166. Adds Sympy support for user-defined types. + + 187) PR #2270 for #1575. Fixes the line-length limiter for code that + has an indent greater than the max. line length. + + 188) PR #2259 for #1932. Adds support for the 'gang' and 'vector' + clauses on OpenACC Loop directives. + + 189) PR #2276 for #2274. Small fixes for various complaints + produced by latest version of pycodestyle. + + 190) PR #2291 for #2290. Bug fix to KernelModuleInlineTrans - + ensures that the new RoutineSymbol added to the Container is given + a DefaultModuleInterface. + + 191) PR #2238 for #2234. Adds support for a generic interface to + perform variable comparison when the datatype is unknown. (Required + for canonicalising SELECT CASE constructs.) + + 192) PR #2292 for #2288. Workaround for Sphinx-doctest bug by + building html dev guide before running the doctests. + + 193) PR #2260 for #2245. Splits LFRicLoopBounds class from + dynamo0.3.py. + + 194 PR #2241 for #2215. Adds support for Fortran names being the + same as Python keywords when using sympy within PSyclone, e.g. in + comparisons between expressions. + + 195) PR #2295 for #2294. Extends the PSyData extraction library + to support character variables + release 2.3.1 17th of June 2022 1) PR #1747 for #1720. Adds support for If blocks to PSyAD. diff --git a/doc/developer_guide/psy_data.rst b/doc/developer_guide/psy_data.rst index d8a8b43a9c9..a02719c6acd 100644 --- a/doc/developer_guide/psy_data.rst +++ b/doc/developer_guide/psy_data.rst @@ -654,6 +654,8 @@ takes the following parameters: 64-bit integer value ``logical``: 32-bit logical value + ``char``: + A default string value Default value is ``real,double,int``. diff --git a/doc/developer_guide/psyir.rst b/doc/developer_guide/psyir.rst index 79f7f2f022e..5a94ee6acee 100644 --- a/doc/developer_guide/psyir.rst +++ b/doc/developer_guide/psyir.rst @@ -507,6 +507,16 @@ var2, var3)` would be represented by a The PSyIR supports the concept of named arguments for operation nodes, see the :ref:`named_arguments-label` section for more details. +.. note:: Similar to Fortran, the PSyIR has two comparison operators, one for + booleans (EQV) and one for non-booleans (EQ). These are not + interchangeable because they have different precedence priorities and + some compilers will not compile with the wrong operator. In some cases + we need to insert a comparison of two expressions and we don't know the + datatype of the operands (e.g. in the select-case canonicalisation). + A solution to this is to create an abstract interface with appropriate + implementations for each possible datatype. + + IntrinsicCall Nodes ------------------- diff --git a/doc/developer_guide/psyir_symbols.rst b/doc/developer_guide/psyir_symbols.rst index d1cb9677915..96d5ab5ad5f 100644 --- a/doc/developer_guide/psyir_symbols.rst +++ b/doc/developer_guide/psyir_symbols.rst @@ -230,7 +230,8 @@ the subclass. For example: Sometimes providing additional properties of the new sub-class is desirable, and sometimes even mandatory (e.g. a `DataSymbol` must always have a datatype -and optionally a constant_value parameter). For this reason the specialise +and optionally is_constant and initial_value parameters). For this reason +the specialise method implementation provides the same interface as the constructor of the symbol type in order to provide the same behaviour and default values as the constructor. For instance, in the `DataSymbol` case the following @@ -241,15 +242,21 @@ specialisations are possible: >>> sym = Symbol("a") >>> # The following statement would fail because it doesn't have a datatype >>> # sym.specialise(DataSymbol) - >>> # The following statement is valid and constant_value is set to None + >>> # The following statement is valid (in this case initial_value will + >>> # default to None and is_constant to False): >>> sym.specialise(DataSymbol, datatype=INTEGER_TYPE) >>> sym2 = Symbol("b") - >>> # The following statement would fail because the constant_value doesn't - >>> # match the datatype of the symbol - >>> # sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, constant_value=3.14) - >>> # The following statement is valid and constant_value is set to 3 - >>> sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, constant_value=3) + >>> # The following statement would fail because the initial_value doesn't + >>> # match the datatype of the symbol: + >>> # sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, initial_value=3.14) + >>> # The following statement is valid and initial_value is set to 3 + >>> # (and is_constant will default to False): + >>> sym2.specialise(DataSymbol, datatype=INTEGER_TYPE, initial_value=3) + >>> print(sym2.initial_value) + Literal[value:'3', Scalar] + >>> print(sym2.is_constant) + False Routine Interfaces diff --git a/doc/developer_guide/sympy.rst b/doc/developer_guide/sympy.rst index d3c8f1f64a7..112178cdf0f 100644 --- a/doc/developer_guide/sympy.rst +++ b/doc/developer_guide/sympy.rst @@ -153,63 +153,27 @@ is case sensitive. User-defined Types ~~~~~~~~~~~~~~~~~~ -SymPy has no concept of user-defined types like -``a(i)%b`` in Fortran. But this case is not handled especially, the -PSyIR is converted to Fortran syntax and is provided unmodified to SymPy. -SymPy interprets the ``%`` symbol -as modulo function, so the expression above is read as ``Mod(a(i), b)``. -This interpretation achieves the expected outcome when comparing structures -and array references. -For example, ``a(i+2*j-1)%b(k-i)`` and ``a(j*2-1+i)%b(-i+k)`` will be -considered to be equal: - -1. Converting the two expressions to SymPy internally results in - ``Mod(a(i+2*j-1), b(k-i))`` and ``Mod(a(j*2-1+i, b(-i+k))``. -2. Since nothing is known about the arguments of any of the ``Mod`` - functions, SymPy will first detect that the same function is called - in both expression, and then continue to compare the arguments of - this function. -3. The first arguments are ``a(i+2*j-1)`` and ``a(j*2-1+i)``. - The name ``a`` is considered an unknown function. SymPy detects - that both expressions appear to call the same function, and it - will therefore compare the arguments. -4. SymPy compares ``i+2*j-1`` and ``j*2-1+i`` symbolically, and - evaluate these expressions to be identical. Therefore, the - two expressions ``a(...)`` are identical, so the first arguments - of the ``Mod`` function are identical. -5. Similarly, it will then continue to evaluate the second argument - of the ``Mod`` function (``b(...)``), and evaluate them to be - identical. -6. Since all arguments of the ``Mod`` function are identical, - SymPy will report these two functions to be the same, which - is the expected outcome. - -A member of a structure in Fortran becomes a stand-alone symbol (or -function if it is an array) in SymPy. The SymPy -writer will rename members to better indicate that they are members: -an expression like ``a%b%c`` will be written as ``a%a_b%a_b_c``, which -SymPy then parses as ``MOD(a, MOD(a_b, a_b_c))``. This convention -makes it easier to identify what the various expressions in SymPy are. - -This handling of member variables can result in name clashes. Consider -the expression ``a%b + a_b + b``. The structure access will be using -two symbols ``a`` and ``a_b`` - but now there are two different symbols -with the same name. Note that the renaming of the member from ``b`` to -``a_b`` is not the reason for this - without renaming the same clash would -happen with the symbol ``b``. - -The SymPy writer uses a symbol table to make sure it creates unique symbols. +SymPy has no concept of user-defined types like ``a(i)%b`` in Fortran. +A structure reference like this is converted to a single new symbol +(scalar) or function (if an array index is involved). The default name +will be the name of the reference and members concatenated using ``_``, +e.g. ``a%b%c`` becomes ``a_b_c``, which will be declared as a new SymPy +symbol or function (if it is an array access). The SymPy writer uses a +symbol table to make sure it creates unique symbols. It first adds all References in the expression to the symbol table, which -guarantees that no Reference to an existing symbol is renamed. The writer -then renames all members and makes sure it uses a unique name. In the case of -``a%b + a_b + b``, it would create ``a%a_b_1 + a_b + b``, using the name -``a_b_1`` for the member to avoid the name clash with the reference -``a_b`` - so an existing Reference will not be renamed, only members. - -.. note:: At this stage an expression using user-defined types cannot be - converted back to a PSyIR (which is what - `psyclone.core.SymbolicMaths.expand` does as a final step). This is - tracked as issue #2166. +guarantees that no Reference to an existing symbol is renamed. In the case of +``a%b + a_b + b``, it would create ``a_b_1 + a_b + b``, using the name +``a_b_1`` for the structure reference to avoid the name clash with the +reference ``a_b``. + +Any array indices are converted into arguments of this new function. So an +expression like ``a(i)%b%c(j,k)`` becomes ``a_b_c(i,i,1,j,j,1,k,k,1)`` +(see :ref:`array_expressions`). The ``SymPyWriter`` creates a custom SymPy +function, which keeps a list of which reference/member contained how many +indices. In the example this would be ``[1, 0, 2]``, indicating that the +first reference had one index, the second one none (i.e. it is not an +array access), and the last reference had two indices. This allows the +function to properly re-create the Fortran string. Documentation for SymPyWriter Functions diff --git a/doc/developer_guide/working_practises.rst b/doc/developer_guide/working_practises.rst index 43dd2a33ddf..99258388082 100644 --- a/doc/developer_guide/working_practises.rst +++ b/doc/developer_guide/working_practises.rst @@ -375,7 +375,8 @@ code to file and then invoking a Fortran compiler. This testing is not performed by default since it requires a Fortran compiler and significantly increases the time taken to run the test suite. -The Gnu Fortran compiler (gfortran) is used by default. If you wish to +If compilation testing is requested then the Gnu Fortran compiler (gfortran) +is used by default. If you wish to use a different compiler and/or supply specific flags then these are specified by further command-line flags:: @@ -462,7 +463,7 @@ Since we try to be good 'open-source citizens' we do not do any compilation testing using GitHub as that would use a lot more compute time. Instead, it is the responsibility of the developer and code reviewer to run these checks locally (see :ref:`compilation_testing`). Code reviewers are able to make -use of the ``compilation`` GitHub Action which will eventually perform +use of the ``compilation`` GitHub Action which performs these checks semi-automatically - see :ref:`integration-testing`. By default, the GitHub Actions configuration uses ``pip`` to install @@ -541,14 +542,14 @@ dictionary within ``conf.py``. Compilation and Integration Testing ----------------------------------- -As mentioned above, running the test suite and/or examples with compilation -enabled significantly increases the required compute time. However, there -is a need to test PSyclone with full builds of the LFRic and NEMO -applications. Therefore, in addition to the principal action described -above, there are the following workflow files that manage multiple -Integration tests: +As mentioned above, running the test suite, examples and tutorials +with compilation enabled significantly increases the required compute +time. However, there is a need to test PSyclone with full builds of +the LFRic and NEMO applications. Therefore, in addition to the +principal action described above, there are the following workflow +files that manage multiple Integration tests: -The ``repo-sync`` action, which must be triggered +The ``repo-sync.yml`` action, which must be triggered manually (on GitHub) and pushes a copy of the current branch to a private repository. (This action uses the ``integration`` environment and can therefore only be triggered by GitHub users who have ``review`` permissions @@ -561,14 +562,24 @@ below. Since the self-hosted runner is only available in the private repository, these action are configured such that they only run if the name of the repository is that of the private one. -The ``compilation.yml`` action, runs the test suite and examples with -compilation enabled (using ``gfortran``). +The ``compilation.yml`` action runs the test suite, examples and tutorials +with compilation enabled for both ``gfortran`` and ``nvfortran`` (the latter +with OpenACC enabled). -The ``nemo.yml`` action, processes the NEMO source code (available in -self-hosted runner) with the PSyclone scripts in examples/nemo/scripts. +The ``nemo.yml`` action, processes the NEMO source code (available in the +self-hosted runner) with the PSyclone scripts in ``examples/nemo/scripts``. Then it compiles the generated code, runs it, and validates that the -output produced matches with the expected results. +output produced matches with the expected results. The wallclock time used +by the run is also recorded for future reference. +The ``lfric_test.yml`` action performs integration testing of PSyclone with +the LFRic model (available in the self-hosted runner). Two tests are performed: + + 1. A 'pass-through' test where the LFRic GungHo mini-app is built and then + run 6-way parallel using MPI; + 2. An optimisation test where the LFRic GungHo mini-app is transformed using + the ``examples/lfric/scripts/everything_everywhere_all_at_once.py`` script + and then compiled and run 6-way parallel using OpenMP threading. Performance =========== diff --git a/doc/user_guide/conf.py b/doc/user_guide/conf.py index 3f87d1e4833..2d2104ff0b8 100644 --- a/doc/user_guide/conf.py +++ b/doc/user_guide/conf.py @@ -365,6 +365,10 @@ # -- Options for linkcheck ------------------------------------------------- linkcheck_anchors = True +# We need to ignore this anchor (used a couple of times in examples.rst) +# because it seems that GitHub's JavaScript-generated page defeats the +# link checker. +linkcheck_anchors_ignore = ['user-content-netcdf-library-lfric-examples'] # MyBinder fails on a very regular basis so we skip those links. # The puma site no longer exists but the GOcean documentation needs to be diff --git a/doc/user_guide/examples.rst b/doc/user_guide/examples.rst index 074cf822791..0533788a618 100644 --- a/doc/user_guide/examples.rst +++ b/doc/user_guide/examples.rst @@ -140,8 +140,9 @@ so it can be recorded in this table. ======================= ======================================================= Compiler Version ======================= ======================================================= -Gnu Fortran compiler 9.3 -Intel Fortran compiler 17, 21 +Gnu Fortran 9.3 +Intel Fortran 17, 21 +NVIDIA Fortran 23.5 ======================= ======================================================= .. _examples_dependencies: @@ -479,34 +480,49 @@ better job when optimising the code. Example 14: OpenACC ^^^^^^^^^^^^^^^^^^^ -Example of adding OpenACC directives in the dynamo0.3 API. -A single transformation script (``acc_parallel_dm.py``) is provided -which demonstrates how to add OpenACC Loop, Parallel and Enter Data +Example of adding OpenACC directives in the LFRic API. +A single transformation script (``acc_parallel.py``) is provided +which demonstrates how to add OpenACC Kernels and Enter Data directives to the PSy-layer. It supports distributed memory being -switched on by placing an OpenACC Parallel directive around each -OpenACC Loop directive, rather than having one for the whole invoke. +switched on by placing an OpenACC Kernels directive around each +(parallelisable) loop, rather than having one for the whole invoke. This approach avoids having halo exchanges within an OpenACC Parallel region. The script also uses :ref:`ACCRoutineTrans ` to transform the one user-supplied kernel through the addition of an ``!$acc routine`` directive. This ensures that the compiler builds a version suitable for execution on the accelerator (GPU). -The generated code has two problems: - - 1. There are no checks on whether loops are safe to parallelise or not, - it is just assumed they are - i.e. support for colouring or locking - is not yet implemented. - 2. Although the user-supplied kernel is transformed so as to have the - necessary ``!$acc routine`` directive, the associated (but unnecessary) - ``use`` statement in the transformed Algorithm layer still uses the - name of the original, untransformed kernel (issue #1724). - -Since no colouring is required in this case, the generated Alg layer -may be fixed by hand (by simply deleting the offending ``use`` statement) -and the resulting code compiled and run on GPU. However, performance will -be very poor as, with the limited optimisations and directives currently -applied, the NVIDIA compiler refuses to run the user-supplied kernel in -parallel. +This script is used by the supplied Makefile. The invocation of PSyclone +within that Makefile also specifies the ``--profile invokes`` option so that +each ``invoke`` is enclosed within profiling calipers (by default the +'template' profiling library supplied with PSyclone is used at the link +stage). Compilation of the example using the NVIDIA compiler may be performed +by e.g.: + +.. code-block:: bash + + > F90=nvfortran F90FLAGS="-acc -Minfo=all" make compile + +Launching the resulting binary with ``NV_ACC_NOTIFY`` set will show details +of the kernel launches and data transfers: + +.. code-block:: bash + + > NV_ACC_NOTIFY=3 ./example_openacc + ... + Step 5 : chksm = 2.1098315506694516E-004 + PreStart called for module 'main_psy' region 'invoke_2:setval_c:r2' + upload CUDA data file=PSyclone/examples/lfric/eg14/main_psy.f90 function=invoke_2 line=183 device=0 threadid=1 variable=.attach. bytes=144 + upload CUDA data file=PSyclone/examples/lfric/eg14/main_psy.f90 function=invoke_2 line=183 device=0 threadid=1 variable=.attach. bytes=144 + launch CUDA kernel file=PSyclone/examples/lfric/eg14/main_psy.f90 function=invoke_2 line=186 device=0 threadid=1 num_gangs=5 num_workers=1 vector_length=128 grid=5 block=128 + PostEnd called for module 'main_psy' region 'invoke_2:setval_c:r2' + download CUDA data file=PSyclone/src/psyclone/tests/test_files/dynamo0p3/infrastructure//field/field_r64_mod.f90 function=log_minmax line=756 device=0 threadid=1 variable=self%data(:) bytes=4312 + 20230807214504.374+0100:INFO : Min/max minmax of field1 = 0.30084014E+00 0.17067212E+01 + ... + +However, performance will be very poor as, with the limited +optimisations and directives currently applied, the NVIDIA compiler +refuses to run the user-supplied kernel in parallel. Example 15: CPU Optimisation of Matvec ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/user_guide/psyclone_kern.rst b/doc/user_guide/psyclone_kern.rst index 017271d4619..29a65091918 100644 --- a/doc/user_guide/psyclone_kern.rst +++ b/doc/user_guide/psyclone_kern.rst @@ -1,7 +1,7 @@ .. ----------------------------------------------------------------------------- .. BSD 3-Clause License .. -.. Copyright (c) 2017-2022, Science and Technology Facilities Council +.. Copyright (c) 2017-2023, Science and Technology Facilities Council .. All rights reserved. .. .. Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ .. POSSIBILITY OF SUCH DAMAGE. .. ----------------------------------------------------------------------------- .. Written by R. W. Ford and A. R. Porter, STFC Daresbury Lab -.. Modified by I. Kavcic, Met Office +.. Modified by I. Kavcic and L. Turner, Met Office PSyclone Kernel Tools ===================== @@ -197,7 +197,7 @@ directory where ```` refers to the location where you download or clone PSyclone (:ref:`Getting Going `). In the ``tests/test_files/dynamo0p3`` directory the majority of examples -start with ``testkern``. Amongst the exceptions are: ``simple.f90``, +start with ``testkern``. Amongst the exceptions are: ``testkern_simple_mod.f90``, ``ru_kernel_mod.f90`` and ``matrix_vector_kernel_mod.F90``. The following test kernels can be used to generate kernel stub code (running stub generation from the ``/src/psyclone`` directory):: @@ -207,7 +207,7 @@ generation from the ``/src/psyclone`` directory):: tests/test_files/dynamo0p3/testkern_operator_mod.f90 tests/test_files/dynamo0p3/testkern_operator_nofield_mod.f90 tests/test_files/dynamo0p3/ru_kernel_mod.f90 - tests/test_files/dynamo0p3/simple.f90 + tests/test_files/dynamo0p3/testkern_simple_mod.f90 .. _stub-generation-example: @@ -215,7 +215,7 @@ Example +++++++ A simple, single field example of a kernel that can be used as input for the -stub generator is found in ``tests/test_files/dynamo0p3/simple.f90`` and +stub generator is found in ``tests/test_files/dynamo0p3/testkern_simple_mod.f90`` and is shown below: .. _simple_metadata: @@ -260,9 +260,9 @@ is shown below: the generator relies on currently requires a dummy kernel subroutine to exist. -If we run the kernel stub generator on the ``simple.f90`` example:: +If we run the kernel stub generator on the ``testkern_simple_mod.f90`` example:: - > psyclone-kern -gen stub tests/test_files/dynamo0p3/simple.f90 + > psyclone-kern -gen stub tests/test_files/dynamo0p3/testkern_simple_mod.f90 we get the following kernel stub output: @@ -448,20 +448,20 @@ supported in the stub generator:: tests/test_files/dynamo0p3/testkern_any_space_4_mod.f90 tests/test_files/dynamo0p3/testkern_any_discontinuous_space_op_2_mod.f90 tests/test_files/dynamo0p3/testkern_dofs_mod.f90 - tests/test_files/dynamo0p3/testkern_invalid_fortran.F90 - tests/test_files/dynamo0p3/testkern_short_name.F90 - tests/test_files/dynamo0p3/testkern_no_datatype.F90 - tests/test_files/dynamo0p3/testkern_qr.F90 + tests/test_files/dynamo0p3/testkern_invalid_fortran_mod.f90 + tests/test_files/dynamo0p3/testkern_short_name_mod.f90 + tests/test_files/dynamo0p3/testkern_no_datatype_mod.f90 + tests/test_files/dynamo0p3/testkern_wrong_file_name.F90 -``testkern_invalid_fortran.F90``, ``testkern_no_datatype.F90``, -``testkern_short_name.F90`` and ``testkern_qr.F90`` are designed to be +``testkern_invalid_fortran_mod.f90``, ``testkern_no_datatype_mod.f90``, +``testkern_short_name_mod.f90`` and ``testkern_wrong_file_name.F90`` are designed to be invalid for PSyclone stub generation testing purposes and should produce appropriate errors. Two examples are below:: - > psyclone-kern -gen stub tests/test_files/dynamo0p3/testkern_invalid_fortran.F90 + > psyclone-kern -gen stub tests/test_files/dynamo0p3/testkern_invalid_fortran_mod.f90 Error: 'Parse Error: Code appears to be invalid Fortran' - > psyclone-kern -gen stub tests/test_files/dynamo0p3/testkern_no_datatype.F90 + > psyclone-kern -gen stub tests/test_files/dynamo0p3/testkern_no_datatype_mod.f90 Error: 'Parse Error: Kernel type testkern_type does not exist' ``testkern_dofs_mod.f90`` is an example with an unsupported feature, as the @@ -488,8 +488,8 @@ As noted above, if the LFRic API naming convention for module and type names is not followed, the stub generator will return with an error message. For example:: - > psyclone-kern -gen stub tests/test_files/dynamo0p3/testkern_qr.F90 - Error: "Parse Error: Error, module name 'testkern_qr' does not have + > psyclone-kern -gen stub tests/test_files/dynamo0p3/testkern_wrong_file_name.F90 + Error: "Parse Error: Error, module name 'testkern_wrong_file_name' does not have '_mod' as an extension. This convention is assumed." @@ -543,7 +543,7 @@ Example If we take the same kernel used in the stub-generation :ref:`example ` then running :: - > psyclone-kern -gen alg tests/test_files/dynamo0p3/simple.f90 + > psyclone-kern -gen alg tests/test_files/dynamo0p3/testkern_simple_mod.f90 gives the following algorithm layer code: diff --git a/doc/user_guide/psyir.rst b/doc/user_guide/psyir.rst index f931f2f265f..2d2dd48903b 100644 --- a/doc/user_guide/psyir.rst +++ b/doc/user_guide/psyir.rst @@ -208,12 +208,17 @@ information about the exact location. .. automethod:: psyclone.psyir.nodes.Node.walk -Finally, all nodes also provide the `ancestor` method which may be used to +All nodes also provide the `ancestor` method which may be used to recurse back up the tree from a given node in order to find a node of a particular type: .. automethod:: psyclone.psyir.nodes.Node.ancestor +Finally, the `path_from` method can be used to find the route through the +tree from an ancestor node to the node: + +.. automethod:: psyclone.psyir.nodes.Node.path_from + DataTypes ========= @@ -252,7 +257,7 @@ example: >>> int_type = ScalarType(ScalarType.Intrinsic.INTEGER, ... ScalarType.Precision.SINGLE) >>> bool_type = ScalarType(ScalarType.Intrinsic.BOOLEAN, 4) - >>> symbol = DataSymbol("rdef", int_type, constant_value=4) + >>> symbol = DataSymbol("rdef", int_type, initial_value=4) >>> scalar_type = ScalarType(ScalarType.Intrinsic.REAL, symbol) For convenience PSyclone predefines a number of scalar datatypes: @@ -517,10 +522,12 @@ as keyword arguments. For example, the following code: symbol_table.new_symbol(root_name="something", symbol_type=DataSymbol, datatype=REAL_TYPE, - constant_value=3) + is_constant=True, + initial_value=3) declares a symbol named "something" of REAL_TYPE datatype where the -constant_value argument will be passed to the DataSymbol constructor. +``is_constant`` and ``initial_value`` arguments will be passed to the +DataSymbol constructor. An example of using the ``new_symbol()`` method can be found in the PSyclone ``examples/psyir`` directory. diff --git a/doc/user_guide/transformations.rst b/doc/user_guide/transformations.rst index 234a59319dd..ca5e4fb808c 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -1,7 +1,7 @@ .. ----------------------------------------------------------------------------- .. BSD 3-Clause License .. -.. Copyright (c) 2017-2022, Science and Technology Facilities Council +.. Copyright (c) 2017-2023, Science and Technology Facilities Council .. All rights reserved. .. .. Redistribution and use in source and binary forms, with or without @@ -1049,9 +1049,7 @@ PSyclone-generated loops in the PSy layer. PSyclone therefore provides the ``ACCRoutineTrans`` transformation which, given a Kernel node in the PSyIR, creates a new version of that kernel with the ``routine`` directive added. See either PSyclone/examples/gocean/eg2 or -PSyclone/examples/lfric/eg14 for an example (although please note that -this transformation is not yet fully working for kernels in -the LFRic (Dynamo0.3) API - see #1724). +PSyclone/examples/lfric/eg14 for an example. SIR --- diff --git a/examples/lfric/README.md b/examples/lfric/README.md index a72dbb5d377..ecf40be854d 100644 --- a/examples/lfric/README.md +++ b/examples/lfric/README.md @@ -278,19 +278,17 @@ psyclone -s ./kernel_constants.py \ ## Example 14: OpenACC This example shows how OpenACC directives can be added to the LFRic -PSy-layer. It adds OpenACC enter data, parallel and loop directives in the +PSy-layer. It adds OpenACC enter data and kernels directives in the presence of halo exchanges. It also transforms the (one) user-supplied kernel with the addition of an `ACC routine` directive. ```sh cd eg14/ -psyclone -s ./acc_parallel_dm.py main.x90 +psyclone -s ./acc_parallel.py main.x90 ``` The supplied Makefile defines a `compile` target that will build the -transformed code. Currently the compilation will fail because the -generated PSy-layer code does not contain the correct name for the -transformed kernel module (issue #1724). +transformed code. ## Example 15: Optimise matvec Kernel for CPU diff --git a/examples/lfric/eg14/Makefile b/examples/lfric/eg14/Makefile index f97c711190a..d126951927d 100644 --- a/examples/lfric/eg14/Makefile +++ b/examples/lfric/eg14/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,30 +36,43 @@ # The compiler to use may be specified via the F90 and F90FLAGS # environment variables. To use the NVIDIA compiler and enable -# openacc compilation with managed memory, use: +# openacc compilation, use: # # export F90=nvfortran -# export F90FLAGS="-acc=gpu -Minfo=all -gpu=managed" +# export F90FLAGS="-acc=gpu -Minfo=all" PSYROOT=../../.. include $(PSYROOT)/examples/common.mk GENERATED_FILES = *.o *.mod $(EXEC) main_alg.f90 main_psy.f90 \ - testkern_w0_kernel_0_mod.f90 + other_alg_mod_psy.f90 other_alg_mod_alg.f90 \ + testkern_w0_kernel_?_mod.f90 F90 ?= gfortran F90FLAGS ?= -Wall -g -OBJ = main_psy.o main_alg.o testkern_w0_kernel_0_mod.o +OBJ = main_psy.o main_alg.o other_alg_mod_psy.o other_alg_mod_alg.o \ + testkern_w0_kernel_0_mod.o EXEC = example_openacc -LFRIC_DIR ?= $(PSYROOT)/src/psyclone/tests/test_files/dynamo0p3/infrastructure +LFRIC_PATH ?= $(PSYROOT)/src/psyclone/tests/test_files/dynamo0p3/infrastructure LFRIC_NAME=lfric -LFRIC_LIB=$(LFRIC_DIR)/lib$(LFRIC_NAME).a - -F90FLAGS += -I$(LFRIC_DIR) +LFRIC_LIB=$(LFRIC_PATH)/lib$(LFRIC_NAME).a +# This sets up LFRIC_INCLUDE_FLAGS +include $(LFRIC_PATH)/lfric_include_flags.inc + +# PSyData profiling wrapper library. +# Default is to use the 'template' library which simply prints entry and exit +# messages to stdout. +PROFILE_PATH=$(PSYROOT)/lib/profiling/template +PROFILE_LIB=$(PROFILE_PATH)/libdummy.a +PROFILE_LINK=-ldummy +# For NVIDIA profiling +#PROFILE_PATH=$(PSYROOT)/lib/profiling/nvidia +#PROFILE_LIB=$(PROFILE_PATH)/libnvtx_prof.a +#PROFILE_LINK="-lnvtx_prof -lnvToolsExt" .PHONY: transform compile run @@ -68,44 +81,50 @@ F90FLAGS += -I$(LFRIC_DIR) # will create 'testkern_..._1_mod.f90' so remove it first. transform: rm -f testkern_w0_kernel_0_mod.f90 - ${PSYCLONE} -dm -s ./acc_parallel_dm.py \ - -opsy main_psy.f90 -oalg main_alg.f90 main.x90 + ${MAKE} main_psy.f90 + ${MAKE} other_alg_mod_psy.f90 +# Instruct PSyclone to automatically put profiling calipers around every +# invoke. %_psy.f90: %.x90 - ${PSYCLONE} -s ./acc_parallel_dm.py \ + ${PSYCLONE} -dm -s ./acc_parallel.py --profile invokes \ -opsy $*_psy.f90 -oalg $*_alg.f90 $< testkern_w0_kernel_0_mod.f90: main_psy.f90 -# TODO #1724 - compilation currently fails because module name in use -# statement needs correcting following ACCRoutineTrans of Kernel. -compile: transform - @echo "No compilation supported for lfric/eg14 due to #1724" +compile: transform ${EXEC} +$(LFRIC_LIB): + $(MAKE) -C $(LFRIC_PATH) run: compile ./$(EXEC) -$(EXEC): $(LFRIC_LIB) $(OBJ) - $(F90) $(F90FLAGS) $(OBJ) -o $(EXEC) -L$(LFRIC_DIR) -l$(LFRIC_NAME) +$(EXEC): $(PROFILE_LIB) $(LFRIC_LIB) $(OBJ) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(OBJ) -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) -L${PROFILE_PATH} $(PROFILE_LINK) $(LFRIC_LIB): - $(MAKE) -C $(LFRIC_DIR) + $(MAKE) -C $(LFRIC_PATH) + +$(PROFILE_LIB): + $(MAKE) -C $(PROFILE_PATH) # Dependencies -main_psy.o: testkern_w0_kernel_0_mod.o -main_alg.o: main_psy.o testkern_w0_kernel_0_mod.o +main_psy.o: other_alg_mod_psy.o testkern_w0_kernel_0_mod.o +main_alg.o: other_alg_mod_alg.o main_psy.o testkern_w0_kernel_0_mod.o %.o: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) -I$(PROFILE_PATH) $(LFRIC_INCLUDE_FLAGS) -c $< %.o: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) -I$(PROFILE_PATH) $(LFRIC_INCLUDE_FLAGS) -c $< # Keep the generated psy and alg files .precious: main_psy.f90 main_alg.f90 main_alg.f90: main_psy.f90 +other_alg_mod_alg.f90: other_alg_mod_psy.f90 allclean: clean - $(MAKE) -C $(LFRIC_DIR) allclean + $(MAKE) -C $(LFRIC_PATH) allclean + $(MAKE) -C $(PROFILE_PATH) allclean diff --git a/examples/lfric/eg14/README.md b/examples/lfric/eg14/README.md index 661158633f9..e6096f7d2ca 100644 --- a/examples/lfric/eg14/README.md +++ b/examples/lfric/eg14/README.md @@ -2,30 +2,26 @@ This directory contains a runnable example of an LFRic mini-app that uses OpenACC. The framework for this stand-alone example is explained in -more details in the directory +more detail in the directory ``/examples/lfric/eg17/full_example``. -The script ``acc_parallel_dm.py`` applies various OpenACC transformations +The script ``acc_parallel.py`` applies various OpenACC transformations to all kernels. See the PSyclone User Guide for [details](https://psyclone.readthedocs.io/en/stable/examples.html#example-14-openacc). ## Compilation -Note that due to #1724 compilation will currently fail. A temporary workaround -is to edit the generated Alg file (``main_alg.f90``) and remove the -``use testkern_w0_kernel_mod, only: ...`` line. - A simple Makefile is provided to compile the example. It needs: - the infrastructure library ``liblfric.a`` provided in ``/src/psyclone/tests/test_files/dynamo0p3/infrastructure`` The infrastructure library will be compiled if it is not available. -The following environment variables can be set to define the compiler -you want to use: +The ``F90`` and ``F90FLAGS`` environment variables can be set to define the +compiler you want to use. For instance, to use NVIDIA's nvfortran with OpenACC +enabled: + ```shell -export F90=gfortran -export F90FLAGS="-Wall -g -fopenacc" -make +F90=nvfortran F90FLAGS="-acc -Minfo=all" make compile ``` ## Running @@ -34,7 +30,16 @@ The binary can be executed using ``example_openacc`` without additional paramete ```shell ./example_openacc Mesh has 5 layers. -20210318131720.135+1100:INFO : Min/max minmax of field1 = 0.10000000E+01 0.80000000E+01 + profile_PSyDataInit called + PreStart called for module 'main_psy' region 'invoke_initialise_fields:r0' + PostEnd called for module 'main_psy' region 'invoke_initialise_fields:r0' + PreStart called for module 'main_psy' region ' + invoke_testkern_w0:testkern_w0_code:r1' + PostEnd called for module 'main_psy' region ' + invoke_testkern_w0:testkern_w0_code:r1' + ... +20230807214504.374+0100:INFO : Min/max minmax of field1 = 0.30084014E+00 0.17067212E+01 +20230807214504.374+0100:INFO : Min/max minmax of field2 = 0.21098316E-03 0.21098316E-03 ``` If you are using NVIDIA hardware, you can specify NV_ACC_NOTIFY=3 diff --git a/examples/lfric/eg14/acc_parallel_dm.py b/examples/lfric/eg14/acc_parallel.py similarity index 67% rename from examples/lfric/eg14/acc_parallel_dm.py rename to examples/lfric/eg14/acc_parallel.py index 8dacc3ac43d..33e87952057 100644 --- a/examples/lfric/eg14/acc_parallel_dm.py +++ b/examples/lfric/eg14/acc_parallel.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2022, Science and Technology Facilities Council. +# Copyright (c) 2019-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -34,26 +34,35 @@ # Authors: R. W. Ford and A. R. Porter, STFC Daresbury Laboratory '''File containing a PSyclone transformation script for the LFRic -API to apply OpenACC Loop, Parallel and Enter Data directives -generically in the presence of halo exchanges. Any user-supplied kernels -are also transformed through the addition of an OpenACC Routine directive. -The psyclone script can apply this transformation script via its -s option. +API to apply OpenACC Kernels and Enter Data directives generically. Any +user-supplied kernels are also transformed through the addition of an OpenACC +Routine directive. PSyclone can apply this transformation script via its + -s option. ''' +from psyclone.domain.lfric import LFRicConstants from psyclone.psyGen import CodedKern -from psyclone.transformations import ACCEnterDataTrans, ACCParallelTrans, \ - ACCLoopTrans, ACCRoutineTrans +from psyclone.transformations import ( + ACCEnterDataTrans, ACCKernelsTrans, ACCRoutineTrans, Dynamo0p3ColourTrans) def trans(psy): - '''PSyclone transformation script for the LFRic API to apply OpenACC loop, - parallel and enter data directives generically. User-supplied kernels are + '''PSyclone transformation script for the LFRic API to apply OpenACC + kernels and enter data directives generically. User-supplied kernels are transformed through the addition of a routine directive. + :param psy: the PSy object containing the invokes to transform. + :type psy: :py:class:`psyclone.dynamo0p3.DynamoPSy` + + :returns: the transformed PSy object. + :rtype: :py:class:`psyclone.dynamo0p3.DynamoPSy` + ''' - loop_trans = ACCLoopTrans() - parallel_trans = ACCParallelTrans() + const = LFRicConstants() + + ctrans = Dynamo0p3ColourTrans() enter_data_trans = ACCEnterDataTrans() + kernel_trans = ACCKernelsTrans() rtrans = ACCRoutineTrans() # Loop over all of the Invokes in the PSy object @@ -61,10 +70,19 @@ def trans(psy): print("Transforming invoke '"+invoke.name+"'...") schedule = invoke.schedule + + # Colour loops over cells unless they are on discontinuous + # spaces or over dofs for loop in schedule.loops(): - loop_trans.apply(loop) - # The loop is now the child of the Directive's Schedule - parallel_trans.apply(loop.parent.parent) + if loop.iteration_space == "cell_column": + if (loop.field_space.orig_name not in + const.VALID_DISCONTINUOUS_NAMES): + ctrans.apply(loop) + + for loop in schedule.loops(): + if loop.loop_type not in ["colours", "null"]: + kernel_trans.apply(loop) + enter_data_trans.apply(schedule) # We transform every user-supplied kernel using ACCRoutineTrans. This diff --git a/examples/lfric/eg14/main.x90 b/examples/lfric/eg14/main.x90 index 69fec916e4a..edccaf59417 100644 --- a/examples/lfric/eg14/main.x90 +++ b/examples/lfric/eg14/main.x90 @@ -43,7 +43,7 @@ program main use testkern_w0_kernel_mod, only: testkern_w0_kernel_type use constants_mod, only: r_def, i_def use log_mod, only: LOG_LEVEL_ALWAYS - + use other_alg_mod, only: my_alg implicit none type(global_mesh_base_type), target :: global_mesh @@ -58,7 +58,10 @@ program main type(field_type) :: field1, field2 integer(kind=i_def) :: lfric_fs = W0 ! W0 integer(kind=i_def) :: element_order = 1 - integer(kind=i_def) :: ndata_sz + integer(kind=i_def) :: ndata_sz, istp + real(kind=r_def) :: chksm + + chksm = 0.0_r_def ! Use the unit-testing constructor: global_mesh = global_mesh_base_type() @@ -88,12 +91,28 @@ program main vector_space_ptr => vector_space call field1%initialise( vector_space = vector_space_ptr, name="field1" ) call field2%initialise( vector_space = vector_space_ptr, name="field2" ) - call invoke( name = 'Initialise fields', & - setval_c( field1, 0.0_r_def ), & - setval_c( field2, 1.0_r_def ) & - ) - call invoke( name = 'testkern_w0', testkern_w0_kernel_type(field1, field2) ) + + call invoke( name = 'Initialise_fields', & + setval_c( field1, 0.1_r_def ), & + setval_c( field2, 0.2_r_def ) & + ) + + do istp = 1, 5 + + call invoke( name = 'testkern_w0', & + testkern_w0_kernel_type(field1, field2) ) + + ! Call an algorithm in a separate module + call my_alg(field1, chksm) + + chksm = 1.0E-6 * chksm + write(*,*) "Step ",istp,": chksm = ", chksm + + call invoke(setval_c(field2, chksm)) + + end do call field1%log_minmax(LOG_LEVEL_ALWAYS, "minmax of field1") + call field2%log_minmax(LOG_LEVEL_ALWAYS, "minmax of field2") end program main diff --git a/examples/lfric/eg14/other_alg_mod.x90 b/examples/lfric/eg14/other_alg_mod.x90 new file mode 100644 index 00000000000..bcfcae72aa7 --- /dev/null +++ b/examples/lfric/eg14/other_alg_mod.x90 @@ -0,0 +1,53 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Modifications copyright (c) 2023, Science and Technology Facilities Council +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author: A. R. Porter, STFC Daresbury Lab + +! A module containing an Algorithm subroutine that contains an invoke of a +! built-in kernel that performs a reduction. + +module other_alg_mod + use field_mod, only: field_type + use mesh_mod, only: mesh_type + use constants_mod, only: r_def + implicit none + +contains + + subroutine my_alg(field_1, chksum1) + type( field_type ), intent( inout ) :: field_1 + real(r_def), intent(out) :: chksum1 + + call invoke( X_innerproduct_X(chksum1, field_1) ) + + end subroutine my_alg + +end module other_alg_mod diff --git a/examples/lfric/scripts/async_halo_exchanges.py b/examples/lfric/scripts/async_halo_exchanges.py new file mode 100644 index 00000000000..64a6265dfc8 --- /dev/null +++ b/examples/lfric/scripts/async_halo_exchanges.py @@ -0,0 +1,77 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2018-2022, Science and Technology Facilities Council +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Authors: R. Ford, A. R. Porter and S. Siso, STFC Daresbury Laboratory + +'''File containing a PSyclone transformation script for the Dynamo0p3 +API to make asynchronous halo exchanges and overlap their +communication with computation. This can be applied via the -s option +in the generator.py script. + +''' + +from psyclone.dynamo0p3 import DynHaloExchange, DynHaloExchangeStart +from psyclone.transformations import Dynamo0p3AsyncHaloExchangeTrans, \ + MoveTrans, TransformationError + + +def trans(psy): + '''A transformation script to use asynchronous halo exchanges with + overlapping compute and communication for the LFRic model. ''' + + for invoke in psy.invokes.invoke_list: + schedule = invoke.schedule + + # This transformation splits the three synchronous halo exchanges + ahex_trans = Dynamo0p3AsyncHaloExchangeTrans() + for h_ex in schedule.walk(DynHaloExchange): + ahex_trans.apply(h_ex) + + # This transformation moves the start of the halo exchanges as far + # as possible offering the potential for overlap between communication + # and computation. + mtrans = MoveTrans() + location_cursor = 0 + for ahex in schedule.walk(DynHaloExchangeStart): + if ahex.position <= location_cursor: + continue + try: + mtrans.apply(ahex, schedule.children[location_cursor]) + location_cursor += 1 + except TransformationError: + pass + + print(f"{location_cursor} AsyncHaloExchanges have been rearranged" + f" in {invoke.name}") + + return psy diff --git a/examples/lfric/scripts/everything_everywhere_all_at_once.py b/examples/lfric/scripts/everything_everywhere_all_at_once.py new file mode 100644 index 00000000000..e781f069fc4 --- /dev/null +++ b/examples/lfric/scripts/everything_everywhere_all_at_once.py @@ -0,0 +1,143 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, Science and Technology Facilities Council +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Authors: R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# I. Kavcic, Met Office +# J. Henrichs, Bureau of Meteorology + +'''PSyclone transformation script for the LFRic API to apply all the +DistibutedMemory, OpenMP coloring and serial transformations possible. + +''' +from psyclone.domain.common.transformations import KernelModuleInlineTrans +from psyclone.domain.lfric import LFRicConstants +from psyclone.dynamo0p3 import DynHaloExchange, DynHaloExchangeStart +from psyclone.psyir.transformations import Matmul2CodeTrans +from psyclone.psyir.nodes import BinaryOperation, Container, KernelSchedule +from psyclone.transformations import Dynamo0p3ColourTrans, \ + Dynamo0p3OMPLoopTrans, \ + OMPParallelTrans, \ + Dynamo0p3RedundantComputationTrans, \ + Dynamo0p3AsyncHaloExchangeTrans, \ + MoveTrans, \ + TransformationError + +ENABLE_REDUNDANT_COMPUTATION = True +ENABLE_ASYNC_HALOS = True +ENABLE_OMP_COLOURING = True +ENABLE_INTRINSIC_INLINING = True +# LFRicLoopFuseTrans and DynKernelConstTrans could also be included but there +# are some issues to overcome, e.g. TODO #2232 + + +def trans(psy): + ''' Apply all possible LFRic transformations. ''' + rtrans = Dynamo0p3RedundantComputationTrans() + ctrans = Dynamo0p3ColourTrans() + otrans = Dynamo0p3OMPLoopTrans() + oregtrans = OMPParallelTrans() + inline_trans = KernelModuleInlineTrans() + matmul_trans = Matmul2CodeTrans() + const = LFRicConstants() + ahex_trans = Dynamo0p3AsyncHaloExchangeTrans() + mtrans = MoveTrans() + + # Loop over all of the Invokes in the PSy object + for invoke in psy.invokes.invoke_list: + schedule = invoke.schedule + + if ENABLE_REDUNDANT_COMPUTATION: + # Make setval_* compute redundantly to the level 1 halo if it + # is in its own loop + for loop in schedule.loops(): + if loop.iteration_space == "dof": + if len(loop.kernels()) == 1: + if loop.kernels()[0].name in ["setval_c", "setval_x"]: + rtrans.apply(loop, options={"depth": 1}) + + if ENABLE_ASYNC_HALOS: + # This transformation splits all synchronous halo exchanges + for h_ex in schedule.walk(DynHaloExchange): + ahex_trans.apply(h_ex) + + # This transformation moves the start of the halo exchanges as + # far as possible offering the potential for overlap between + # communication and computation + location_cursor = 0 + for ahex in schedule.walk(DynHaloExchangeStart): + if ahex.position <= location_cursor: + continue + try: + mtrans.apply(ahex, schedule.children[location_cursor]) + location_cursor += 1 + except TransformationError: + pass + + if ENABLE_OMP_COLOURING: + # Colour loops over cells unless they are on discontinuous + # spaces or over dofs + for loop in schedule.loops(): + if loop.iteration_space == "cell_column" \ + and loop.field_space.orig_name \ + not in const.VALID_DISCONTINUOUS_NAMES: + ctrans.apply(loop) + + # Add OpenMP to loops unless they are over colours or are null + for loop in schedule.loops(): + if loop.loop_type not in ["colours", "null"]: + oregtrans.apply(loop) + otrans.apply(loop, options={"reprod": True}) + + # Transformations that modify kernel code will need to have the + # kernels inlined first + if ENABLE_INTRINSIC_INLINING: + for kernel in schedule.coded_kernels(): + try: + inline_trans.apply(kernel) + except TransformationError: + pass + + # Then transform all the kernels inlined into the module + if psy.invokes.invoke_list: + root = psy.invokes.invoke_list[0].schedule.ancestor(Container) + for kschedule in root.walk(KernelSchedule): + if ENABLE_INTRINSIC_INLINING: + # Expand MATMUL intrinsic + for bop in kschedule.walk(BinaryOperation): + if bop.operator == BinaryOperation.Operator.MATMUL: + try: + matmul_trans.apply(bop) + except TransformationError: + pass + + return psy diff --git a/examples/lfric/scripts/inline_kernels_and_intrinsics.py b/examples/lfric/scripts/inline_kernels_and_intrinsics.py new file mode 100644 index 00000000000..5d3ccdb737b --- /dev/null +++ b/examples/lfric/scripts/inline_kernels_and_intrinsics.py @@ -0,0 +1,82 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, Science and Technology Facilities Council +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Authors: S. Siso, STFC Daresbury Lab + + +'''PSyclone transformation script for the LFRic API to apply serial +optimisations: inline kernels into modules, expand intrinsics into code. +''' + +from psyclone.domain.common.transformations import KernelModuleInlineTrans +from psyclone.psyir.nodes import BinaryOperation, Container, KernelSchedule +from psyclone.psyir.transformations import Matmul2CodeTrans +from psyclone.transformations import TransformationError + + +def trans(psy): + '''Applies LFRic serial optimisations''' + + matmul_trans = Matmul2CodeTrans() + inline_trans = KernelModuleInlineTrans() + + # Loop over all of the Invokes in the PSy object + for invoke in psy.invokes.invoke_list: + schedule = invoke.schedule + + # Try to Inline all kernels into the PSy module + for kernel in schedule.coded_kernels(): + try: + inline_trans.apply(kernel) + print(f"Inline transformation was successful for " + f"'{kernel.name}' in '{invoke.name}'.") + except TransformationError as err: + print(f"Inline transformation failed for " + f"'{kernel.name}' in '{invoke.name}' because:") + print(str(err)) + + # Then we transform all the kernels inlined into the module + if psy.invokes.invoke_list: + root = psy.invokes.invoke_list[0].schedule.ancestor(Container) + for kschedule in root.walk(KernelSchedule): + # Expand MATMUL intrinsic + for bop in kschedule.walk(BinaryOperation): + if bop.operator == BinaryOperation.Operator.MATMUL: + try: + matmul_trans.apply(bop) + except TransformationError as err: + print(f"Inline MATMUL failed for '{kschedule.name}' " + "because:") + print(str(err)) + + return psy diff --git a/examples/lfric/scripts/kernel_print.py b/examples/lfric/scripts/kernel_print.py index 1c19541fe6f..21f3684afab 100644 --- a/examples/lfric/scripts/kernel_print.py +++ b/examples/lfric/scripts/kernel_print.py @@ -38,7 +38,6 @@ using the FortranWriter class. ''' -from __future__ import print_function from psyclone.psyir.backend.fortran import FortranWriter @@ -46,14 +45,22 @@ def trans(psy): '''Print out Fortran versions of all kernels found in this file.''' fortran_writer = FortranWriter() + already_printed = [] + # Loop over all of the Invokes in the PSy object. for invoke in psy.invokes.invoke_list: schedule = invoke.schedule # Loop over all of the Kernels in this Schedule. for kernel in schedule.coded_kernels(): - kernel_schedule = kernel.get_kernel_schedule() - kern = fortran_writer(kernel_schedule) - print(kern) + try: + kernel_schedule = kernel.get_kernel_schedule() + if kernel_schedule not in already_printed: + kern = fortran_writer(kernel_schedule) + print(kern) + already_printed.append(kernel_schedule) + except Exception as err: # pylint: disable=broad-except + print(f"Code of '{kernel.name}' in '{invoke.name}' " + f"cannot be printed because:\n{err}") return psy diff --git a/examples/line_length/longlines.f90 b/examples/line_length/longlines.f90 index dffddeb75fa..66c5484b94f 100644 --- a/examples/line_length/longlines.f90 +++ b/examples/line_length/longlines.f90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2022, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! ! Redistribution and use in source and binary forms, with or without ! modification, are permitted provided that the following conditions are met: @@ -31,14 +31,14 @@ ! POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Authors R. W. Ford and A. R. Porter, STFC Daresbury Lab -! Modified I. Kavcic, Met Office +! Modified I. Kavcic and L. Turner, Met Office program long_lines - use constants_mod, only : r_def, i_def - use field_mod, only : field_type + use constants_mod, only : r_def, i_def + use field_mod, only : field_type use quadrature_xyoz_mod, only: quadrature_xyoz_type - use testkern_qr, only : testkern_qr_type + use testkern_qr_mod, only : testkern_qr_type real(r_def) :: rdt integer(i_def) :: iflag diff --git a/examples/line_length/testkern_qr.F90 b/examples/line_length/testkern_qr_mod.F90 similarity index 80% rename from examples/line_length/testkern_qr.F90 rename to examples/line_length/testkern_qr_mod.F90 index 6a13a1a2872..c94e19b2617 100644 --- a/examples/line_length/testkern_qr.F90 +++ b/examples/line_length/testkern_qr_mod.F90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2021, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -30,7 +30,8 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author R. W. Ford STFC Daresbury Lab -! Modified I. Kavcic Met Office +! Modified I. Kavcic and L. Turner, Met Office + module testkern_qr use constants_mod @@ -70,15 +71,16 @@ subroutine testkern_qr_code(nlayers, f1, f2, f3, ascalar, f4, iscalar, & implicit none - integer(kind=i_def) :: nlayers, iscalar - integer(kind=i_def) :: ndf_w1, undf_w1, ndf_w2, undf_w2, ndf_w3, undf_w3 - integer(kind=i_def) :: nqp_h, nqp_v - integer(kind=i_def), dimension(:) :: map_w1, map_w2, map_w3 - real(kind=r_def) :: ascalar - real(kind=r_def), dimension(:) :: f1, f2, f3, f4 - real(kind=r_def), dimension(:) :: wh, wv - real(kind=r_def), dimension(:,:,:,:) :: basis_w1, diff_basis_w2 - real(kind=r_def), dimension(:,:,:,:) :: basis_w3, diff_basis_w3 + integer(kind=i_def), intent(in) :: nlayers, iscalar + integer(kind=i_def), intent(in) :: ndf_w1, undf_w1, ndf_w2, undf_w2, ndf_w3, undf_w3 + integer(kind=i_def), intent(in) :: nqp_h, nqp_v + integer(kind=i_def), intent(in), dimension(:) :: map_w1, map_w2, map_w3 + real(kind=r_def), intent(in) :: ascalar + real(kind=r_def), dimension(:), intent(inout) :: f1 + real(kind=r_def), dimension(:), intent(in) :: f2, f3, f4 + real(kind=r_def), dimension(:), intent(in) :: wh, wv + real(kind=r_def), dimension(:,:,:,:), intent(in) :: basis_w1, diff_basis_w2 + real(kind=r_def), dimension(:,:,:,:), intent(in) :: basis_w3, diff_basis_w3 end subroutine testkern_qr_code diff --git a/examples/nemo/scripts/kernels_trans.py b/examples/nemo/scripts/kernels_trans.py index 50dc08d7a58..d86ebbf17bc 100755 --- a/examples/nemo/scripts/kernels_trans.py +++ b/examples/nemo/scripts/kernels_trans.py @@ -54,7 +54,7 @@ routine) then the script moves a level down the tree and then repeats the process of attempting to create the largest possible Kernel region. -Tested with the NVIDIA HPC SDK version 22.5. +Tested with the NVIDIA HPC SDK version 23.7. ''' import logging @@ -63,7 +63,7 @@ from psyclone.nemo import NemoInvokeSchedule, NemoKern, NemoLoop from psyclone.psyGen import TransInfo from psyclone.psyir.nodes import IfBlock, CodeBlock, Schedule, \ - ArrayReference, Assignment, BinaryOperation, Loop, \ + ArrayReference, Assignment, BinaryOperation, Loop, WhileLoop, \ Literal, Return, Call, ACCLoopDirective from psyclone.psyir.transformations import TransformationError, ProfileTrans, \ ACCUpdateTrans @@ -184,11 +184,11 @@ def valid_acc_kernel(node): # Rather than walk the tree multiple times, look for both excluded node # types and possibly problematic operations - excluded_node_types = (CodeBlock, Return, Call, IfBlock, NemoLoop) - excluded_nodes = node.walk(excluded_node_types) + excluded_types = (CodeBlock, Return, Call, IfBlock, NemoLoop, WhileLoop) + excluded_nodes = node.walk(excluded_types) for enode in excluded_nodes: - if isinstance(enode, (CodeBlock, Return, Call)): + if isinstance(enode, (CodeBlock, Return, Call, WhileLoop)): log_msg(routine_name, f"region contains {type(enode).__name__}", enode) return False diff --git a/examples/psyir/create.py b/examples/psyir/create.py index 8ea5c6d9bdf..ba62ccb1384 100644 --- a/examples/psyir/create.py +++ b/examples/psyir/create.py @@ -46,7 +46,6 @@ C representation of the PSyIR. ''' -from __future__ import print_function from psyclone.psyir.nodes import Reference, Literal, UnaryOperation, \ BinaryOperation, NaryOperation, Assignment, IfBlock, Loop, \ Container, ArrayReference, Call, Routine, FileContainer @@ -80,7 +79,8 @@ def create_psyir_tree(): real_kind = symbol_table.new_symbol(root_name="RKIND", symbol_type=DataSymbol, datatype=INTEGER_TYPE, - constant_value=8) + is_constant=True, + initial_value=8) routine_symbol = RoutineSymbol("my_sub") # Array using precision defined by another symbol diff --git a/examples/psyir/create_structure_types.py b/examples/psyir/create_structure_types.py index eb0786378ed..ee0860bb2a4 100644 --- a/examples/psyir/create_structure_types.py +++ b/examples/psyir/create_structure_types.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council +# Copyright (c) 2020-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -43,7 +43,6 @@ >>> python create_structure_types.py ''' -from __future__ import print_function from psyclone.psyir.nodes import Literal, KernelSchedule, Container, \ StructureReference, ArrayOfStructuresReference, Assignment, \ BinaryOperation, Range @@ -58,7 +57,7 @@ CONTAINER_SYMBOL_TABLE = SymbolTable() REAL_KIND = CONTAINER_SYMBOL_TABLE.new_symbol( root_name="RKIND", symbol_type=DataSymbol, datatype=INTEGER_TYPE, - constant_value=8) + is_constant=True, initial_value=8) # Shorthand for a scalar type with REAL_KIND precision SCALAR_TYPE = ScalarType(ScalarType.Intrinsic.REAL, REAL_KIND) diff --git a/lib/extract/netcdf/Makefile b/lib/extract/netcdf/Makefile index 3e73e679e79..3baced155cc 100644 --- a/lib/extract/netcdf/Makefile +++ b/lib/extract/netcdf/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -53,7 +53,7 @@ F90FLAGS += $$(nf-config --fflags) # The extract library is implemented for int, real and # double scalars and 1- to 4-dimensional arrays -PROCESS_ARGS = -prefix=extract_ -types=int,long,logical,real,double \ +PROCESS_ARGS = -prefix=extract_ -types=char,int,long,logical,real,double \ -dims=1,2,3,4 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py @@ -90,7 +90,7 @@ read_kernel_data_mod.f90: read_kernel_data_mod.jinja Makefile $(F90) $(F90FLAGS) -c $< clean: - rm -f extract_netcdf_base.f90 psy_data_base.f90 read_kernel_data_mod.f90 + rm -f extract_netcdf_base.f90 psy_data_base.f90 rm -f *.o *.mod allclean: diff --git a/lib/extract/netcdf/extract_netcdf_base.jinja b/lib/extract/netcdf/extract_netcdf_base.jinja index b79091d69af..2c580d162d2 100644 --- a/lib/extract/netcdf/extract_netcdf_base.jinja +++ b/lib/extract/netcdf/extract_netcdf_base.jinja @@ -35,6 +35,7 @@ {% if ALL_TYPES is not defined -%} {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), ("Real", "real(kind=real32)", 32), + ("Character", "character(*)", 32), ("Long", "real(kind=int64)", 64), ("Int", "integer(kind=int32)", 32) ] %} {% endif -%} @@ -279,6 +280,7 @@ contains {% set NCDF_TYPE_MAPPING = { "Double": "NF90_DOUBLE", "Real": "NF90_REAL", "Logical":"NF90_INT", + "Char": "NF90_CHAR", "Long": "NF90_INT64", "Int": "NF90_INT"} -%} diff --git a/lib/extract/netcdf/lfric/Makefile b/lib/extract/netcdf/lfric/Makefile index 5c98bade9eb..3d889788a5d 100644 --- a/lib/extract/netcdf/lfric/Makefile +++ b/lib/extract/netcdf/lfric/Makefile @@ -65,7 +65,7 @@ LFRIC_INCLUDE_FLAGS += $$(nf-config --fflags) PSYDATA_LIB_NAME = _extract PSYDATA_LIB = lib$(PSYDATA_LIB_NAME).a -PROCESS_ARGS = -prefix=extract_ -types=int,logical,real,double \ +PROCESS_ARGS = -prefix=extract_ -types=char,int,logical,real,double \ -dims=1,2,3,4 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py diff --git a/lib/extract/netcdf/read_kernel_data_mod.f90 b/lib/extract/netcdf/read_kernel_data_mod.f90 index cacc131bf20..aad87ddf86e 100644 --- a/lib/extract/netcdf/read_kernel_data_mod.f90 +++ b/lib/extract/netcdf/read_kernel_data_mod.f90 @@ -66,6 +66,11 @@ module read_kernel_data_mod ! The various procedures used procedure :: OpenRead + procedure :: ReadScalarChar + procedure :: ReadArray1dChar + procedure :: ReadArray2dChar + procedure :: ReadArray3dChar + procedure :: ReadArray4dChar procedure :: ReadScalarInt procedure :: ReadArray1dInt procedure :: ReadArray2dInt @@ -96,6 +101,11 @@ module read_kernel_data_mod !! This is not part of the official PSyData API, but is used in !! the drivers created by PSyclone. generic, public :: ReadVariable => & + ReadScalarChar, & + ReadArray1dChar, & + ReadArray2dChar, & + ReadArray3dChar, & + ReadArray4dChar, & ReadScalarInt, & ReadArray1dInt, & ReadArray2dInt, & @@ -177,6 +187,253 @@ subroutine OpenRead(this, module_name, region_name) end subroutine OpenRead + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the value of a scalar character(*) + !! variable from the NetCDF file and returns it to the user. Note that + !! this function is not part of the PSyData API, but it is convenient to + !! have these functions together here. The driver can then be linked with + !! this PSyData library and will be able to read the files. + !! @param[in,out] this The instance of the ReadKernelDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value The read value is stored here. + subroutine ReadScalarChar(this, name, value) + + use netcdf, only : nf90_inq_varid, nf90_get_var + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), intent(out) :: value + + integer :: retval, varid + + retval = CheckError(nf90_inq_varid(this%ncid, name, varid)) + retval = CheckError(nf90_get_var(this%ncid, varid, value)) + + end subroutine ReadScalarChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 1D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray1dChar(this, name, value) + + use netcdf + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1 + integer :: ierr + + ! First query the dimensions of the original array from the + ! NetCDF file + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%1"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size1)) + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1, & + " in ReadArray1dChar." + stop + endif + + retval = CheckError(nf90_inq_varid(this%ncid, name, varid)) + ! Initialise the whole array with "". + value = "" + retval = CheckError(nf90_get_var(this%ncid, varid, value)) + + end subroutine ReadArray1dChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 2D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray2dChar(this, name, value) + + use netcdf + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2 + integer :: ierr + + ! First query the dimensions of the original array from the + ! NetCDF file + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%1"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size1)) + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%2"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size2)) + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2, & + " in ReadArray2dChar." + stop + endif + + retval = CheckError(nf90_inq_varid(this%ncid, name, varid)) + ! Initialise the whole array with "". + value = "" + retval = CheckError(nf90_get_var(this%ncid, varid, value)) + + end subroutine ReadArray2dChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 3D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray3dChar(this, name, value) + + use netcdf + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:,:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2,dim_size3 + integer :: ierr + + ! First query the dimensions of the original array from the + ! NetCDF file + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%1"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size1)) + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%2"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size2)) + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%3"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size3)) + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2,dim_size3), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2,dim_size3, & + " in ReadArray3dChar." + stop + endif + + retval = CheckError(nf90_inq_varid(this%ncid, name, varid)) + ! Initialise the whole array with "". + value = "" + retval = CheckError(nf90_get_var(this%ncid, varid, value)) + + end subroutine ReadArray3dChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 4D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray4dChar(this, name, value) + + use netcdf + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:,:,:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2,dim_size3,dim_size4 + integer :: ierr + + ! First query the dimensions of the original array from the + ! NetCDF file + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%1"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size1)) + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%2"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size2)) + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%3"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size3)) + retval = CheckError(nf90_inq_dimid(this%ncid, trim(name//"dim%4"), & + dim_id)) + retval = CheckError(nf90_inquire_dimension(this%ncid, dim_id, & + len=dim_size4)) + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2,dim_size3,dim_size4), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2,dim_size3,dim_size4, & + " in ReadArray4dChar." + stop + endif + + retval = CheckError(nf90_inq_varid(this%ncid, name, varid)) + ! Initialise the whole array with "". + value = "" + retval = CheckError(nf90_get_var(this%ncid, varid, value)) + + end subroutine ReadArray4dChar + + ! ------------------------------------------------------------------------- !> @brief This subroutine reads the value of a scalar integer(kind=int32) !! variable from the NetCDF file and returns it to the user. Note that diff --git a/lib/extract/netcdf/read_kernel_data_mod.jinja b/lib/extract/netcdf/read_kernel_data_mod.jinja index 41aab93a7f3..7328c8976f5 100644 --- a/lib/extract/netcdf/read_kernel_data_mod.jinja +++ b/lib/extract/netcdf/read_kernel_data_mod.jinja @@ -28,6 +28,7 @@ {% if ALL_TYPES is not defined -%} {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), ("Real", "real(kind=real32)", 32), + ("Char", "character(*)", 8), ("Logical","integer(kind=real32)",32), ("Int", "integer(kind=int32)", 32) ] %} {% endif -%} @@ -288,11 +289,16 @@ contains value = tmp == 1 deallocate(tmp) {% else %} + {% if name == "Char" %} + ! Initialise the whole array with "". + value = "" + {% else %} ! Initialise it with 0, so that an array comparison will work ! even though e.g. boundary areas or so might not be set at all. ! The compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 + {% endif %} retval = CheckError(nf90_get_var(this%ncid, varid, value)) {% endif %} diff --git a/lib/extract/standalone/Makefile b/lib/extract/standalone/Makefile index ed01182acd6..7dda790ac5b 100644 --- a/lib/extract/standalone/Makefile +++ b/lib/extract/standalone/Makefile @@ -51,7 +51,7 @@ PSYDATA_LIB_DIR ?= ./../.. # The extract library is implemented for int, real and # double scalars and 1- to 4-dimensional arrays -PROCESS_ARGS = -prefix=extract_ -types=int,logical,real,double \ +PROCESS_ARGS = -prefix=extract_ -types=char,int,long,logical,real,double \ -dims=1,2,3,4 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py @@ -88,7 +88,7 @@ read_kernel_data_mod.f90: read_kernel_data_mod.jinja Makefile $(F90) $(F90FLAGS) -c $< clean: - rm -f extract_standalone_base.f90 psy_data_base.f90 read_kernel_data_mod.f90 + rm -f extract_standalone_base.f90 psy_data_base.f90 rm -f *.o *.mod allclean: diff --git a/lib/extract/standalone/extract_standalone_base.jinja b/lib/extract/standalone/extract_standalone_base.jinja index 7acbf32db91..93188106454 100644 --- a/lib/extract/standalone/extract_standalone_base.jinja +++ b/lib/extract/standalone/extract_standalone_base.jinja @@ -35,6 +35,8 @@ {% if ALL_TYPES is not defined -%} {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), ("Real", "real(kind=real32)", 32), + ("Character", "character(*)", 8), + ("Long", "real(kind=int64)", 64), ("Int", "integer(kind=int32)", 32) ] %} {% endif -%} @@ -42,7 +44,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2022, Science and Technology Facilities Council. +! Copyright (c) 2022-2023, Science and Technology Facilities Council. ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without diff --git a/lib/extract/standalone/lfric/Makefile b/lib/extract/standalone/lfric/Makefile index 4dbcd324c79..fae082a3fb2 100644 --- a/lib/extract/standalone/lfric/Makefile +++ b/lib/extract/standalone/lfric/Makefile @@ -61,7 +61,7 @@ include $(LFRIC_PATH)/lfric_include_flags.inc PSYDATA_LIB_NAME = _extract PSYDATA_LIB = lib$(PSYDATA_LIB_NAME).a -PROCESS_ARGS = -prefix=extract_ -types=int,logical,real,double \ +PROCESS_ARGS = -prefix=extract_ -types=char,int,logical,real,double \ -dims=1,2,3,4 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py diff --git a/lib/extract/standalone/read_kernel_data_mod.f90 b/lib/extract/standalone/read_kernel_data_mod.f90 index 83aeed3b61b..50cffd3b9f7 100644 --- a/lib/extract/standalone/read_kernel_data_mod.f90 +++ b/lib/extract/standalone/read_kernel_data_mod.f90 @@ -68,11 +68,21 @@ module read_kernel_data_mod ! The various procedures used procedure :: OpenRead + procedure :: ReadScalarChar + procedure :: ReadArray1dChar + procedure :: ReadArray2dChar + procedure :: ReadArray3dChar + procedure :: ReadArray4dChar procedure :: ReadScalarInt procedure :: ReadArray1dInt procedure :: ReadArray2dInt procedure :: ReadArray3dInt procedure :: ReadArray4dInt + procedure :: ReadScalarLong + procedure :: ReadArray1dLong + procedure :: ReadArray2dLong + procedure :: ReadArray3dLong + procedure :: ReadArray4dLong procedure :: ReadScalarLogical procedure :: ReadArray1dLogical procedure :: ReadArray2dLogical @@ -93,11 +103,21 @@ module read_kernel_data_mod !! This is not part of the official PSyData API, but is used in !! the drivers created by PSyclone. generic, public :: ReadVariable => & + ReadScalarChar, & + ReadArray1dChar, & + ReadArray2dChar, & + ReadArray3dChar, & + ReadArray4dChar, & ReadScalarInt, & ReadArray1dInt, & ReadArray2dInt, & ReadArray3dInt, & ReadArray4dInt, & + ReadScalarLong, & + ReadArray1dLong, & + ReadArray2dLong, & + ReadArray3dLong, & + ReadArray4dLong, & ReadScalarLogical, & ReadArray1dLogical, & ReadArray2dLogical, & @@ -143,6 +163,208 @@ subroutine OpenRead(this, module_name, region_name) end subroutine OpenRead + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the value of a scalar character(*) + !! variable from the binary file and returns it to the user. Note that + !! this function is not part of the PSyData API, but it is convenient to + !! have these functions together here. The driver can then be linked with + !! this PSyData library and will be able to read the files. + !! @param[in,out] this The instance of the ReadKernelDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value The read value is stored here. + subroutine ReadScalarChar(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), intent(out) :: value + + integer :: retval, varid + + read(this%unit_number) value + + end subroutine ReadScalarChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 1D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray1dChar(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1, & + " in ReadArray1dChar." + stop + endif + + ! Initialise it with "", so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. + value = "" + read(this%unit_number) value + + end subroutine ReadArray1dChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 2D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray2dChar(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + read(this%unit_number) dim_size2 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2, & + " in ReadArray2dChar." + stop + endif + + ! Initialise it with "", so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. + value = "" + read(this%unit_number) value + + end subroutine ReadArray2dChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 3D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray3dChar(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:,:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2,dim_size3 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + read(this%unit_number) dim_size2 + read(this%unit_number) dim_size3 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2,dim_size3), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2,dim_size3, & + " in ReadArray3dChar." + stop + endif + + ! Initialise it with "", so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. + value = "" + read(this%unit_number) value + + end subroutine ReadArray3dChar + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 4D array of character(*) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray4dChar(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + character(*), dimension(:,:,:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2,dim_size3,dim_size4 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + read(this%unit_number) dim_size2 + read(this%unit_number) dim_size3 + read(this%unit_number) dim_size4 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2,dim_size3,dim_size4), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2,dim_size3,dim_size4, & + " in ReadArray4dChar." + stop + endif + + ! Initialise it with "", so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. + value = "" + read(this%unit_number) value + + end subroutine ReadArray4dChar + + ! ------------------------------------------------------------------------- !> @brief This subroutine reads the value of a scalar integer(kind=int32) !! variable from the binary file and returns it to the user. Note that @@ -202,9 +424,9 @@ subroutine ReadArray1dInt(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -248,9 +470,9 @@ subroutine ReadArray2dInt(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -295,9 +517,9 @@ subroutine ReadArray3dInt(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -343,9 +565,9 @@ subroutine ReadArray4dInt(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -353,6 +575,216 @@ subroutine ReadArray4dInt(this, name, value) end subroutine ReadArray4dInt + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the value of a scalar integer(kind=int64) + !! variable from the binary file and returns it to the user. Note that + !! this function is not part of the PSyData API, but it is convenient to + !! have these functions together here. The driver can then be linked with + !! this PSyData library and will be able to read the files. + !! @param[in,out] this The instance of the ReadKernelDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value The read value is stored here. + subroutine ReadScalarLong(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + integer(kind=int64), intent(out) :: value + + integer :: retval, varid + + read(this%unit_number) value + + end subroutine ReadScalarLong + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 1D array of integer(kind=int64) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray1dLong(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + integer(kind=int64), dimension(:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1, & + " in ReadArray1dLong." + stop + endif + + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right + ! type (e.g. int or single precision). + value = 0.0d0 + read(this%unit_number) value + + end subroutine ReadArray1dLong + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 2D array of integer(kind=int64) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray2dLong(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + integer(kind=int64), dimension(:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + read(this%unit_number) dim_size2 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2, & + " in ReadArray2dLong." + stop + endif + + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right + ! type (e.g. int or single precision). + value = 0.0d0 + read(this%unit_number) value + + end subroutine ReadArray2dLong + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 3D array of integer(kind=int64) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray3dLong(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + integer(kind=int64), dimension(:,:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2,dim_size3 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + read(this%unit_number) dim_size2 + read(this%unit_number) dim_size3 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2,dim_size3), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2,dim_size3, & + " in ReadArray3dLong." + stop + endif + + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right + ! type (e.g. int or single precision). + value = 0.0d0 + read(this%unit_number) value + + end subroutine ReadArray3dLong + + + + ! ------------------------------------------------------------------------- + !> @brief This subroutine reads the values of a 4D array of integer(kind=int64) + !! It allocates memory for the allocatable parameter 'value' to store the + !! read values which is then returned to the caller. If the memory for the + !! array cannot be allocated, the application will be stopped. + !! @param[in,out] this The instance of the extract_PsyDataType. + !! @param[in] name The name of the variable (string). + !! @param[out] value An allocatable, unallocated 2d-double precision array + !! which is allocated here and stores the values read. + subroutine ReadArray4dLong(this, name, value) + + implicit none + + class(ReadKernelDataType), intent(inout), target :: this + character(*), intent(in) :: name + integer(kind=int64), dimension(:,:,:,:), allocatable, intent(out) :: value + + integer :: retval, varid + integer :: dim_id + integer :: dim_size1,dim_size2,dim_size3,dim_size4 + integer :: ierr + + ! First read in the sizes: + read(this%unit_number) dim_size1 + read(this%unit_number) dim_size2 + read(this%unit_number) dim_size3 + read(this%unit_number) dim_size4 + + ! Allocate enough space to store the values to be read: + allocate(value(dim_size1,dim_size2,dim_size3,dim_size4), Stat=ierr) + if (ierr /= 0) then + write(stderr,*) "Cannot allocate array for ", name, & + " of size ", dim_size1,dim_size2,dim_size3,dim_size4, & + " in ReadArray4dLong." + stop + endif + + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right + ! type (e.g. int or single precision). + value = 0.0d0 + read(this%unit_number) value + + end subroutine ReadArray4dLong + + ! ------------------------------------------------------------------------- !> @brief This subroutine reads the value of a scalar Logical(kind=4) !! variable from the binary file and returns it to the user. Note that @@ -412,10 +844,8 @@ subroutine ReadArray1dLogical(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work + ! Initialise it with false, so that an array comparison will work ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right - ! type (e.g. int or single precision). value = .false. read(this%unit_number) value @@ -458,10 +888,8 @@ subroutine ReadArray2dLogical(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work + ! Initialise it with false, so that an array comparison will work ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right - ! type (e.g. int or single precision). value = .false. read(this%unit_number) value @@ -505,10 +933,8 @@ subroutine ReadArray3dLogical(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work + ! Initialise it with false, so that an array comparison will work ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right - ! type (e.g. int or single precision). value = .false. read(this%unit_number) value @@ -553,10 +979,8 @@ subroutine ReadArray4dLogical(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work + ! Initialise it with false, so that an array comparison will work ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right - ! type (e.g. int or single precision). value = .false. read(this%unit_number) value @@ -622,9 +1046,9 @@ subroutine ReadArray1dReal(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -668,9 +1092,9 @@ subroutine ReadArray2dReal(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -715,9 +1139,9 @@ subroutine ReadArray3dReal(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -763,9 +1187,9 @@ subroutine ReadArray4dReal(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -832,9 +1256,9 @@ subroutine ReadArray1dDouble(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -878,9 +1302,9 @@ subroutine ReadArray2dDouble(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -925,9 +1349,9 @@ subroutine ReadArray3dDouble(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value @@ -973,9 +1397,9 @@ subroutine ReadArray4dDouble(this, name, value) stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right ! type (e.g. int or single precision). value = 0.0d0 read(this%unit_number) value diff --git a/lib/extract/standalone/read_kernel_data_mod.jinja b/lib/extract/standalone/read_kernel_data_mod.jinja index 1182efe3297..390e3053297 100644 --- a/lib/extract/standalone/read_kernel_data_mod.jinja +++ b/lib/extract/standalone/read_kernel_data_mod.jinja @@ -28,6 +28,8 @@ {% if ALL_TYPES is not defined -%} {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), ("Real", "real(kind=real32)", 32), + ("Char", "character(*)", 8), + ("Logical","integer(kind=real32)",32), ("Int", "integer(kind=int32)", 32) ] %} {% endif -%} @@ -215,15 +217,23 @@ contains stop endif - ! Initialise it with 0, so that an array comparison will work - ! even though e.g. boundary areas or so might not be set at all. - ! The compiler will convert the double precision value to the right - ! type (e.g. int or single precision). {% if name == "Logical" %} + ! Initialise it with false, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. value = .false. {% else %} + {% if name == "Char" %} + ! Initialise it with "", so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. + value = "" + {% else %} + ! Initialise it with 0.0d0, so that an array comparison will work + ! even though e.g. boundary areas or so might not be set at all. Note + ! that the compiler will convert the double precision value to the right + ! type (e.g. int or single precision). value = 0.0d0 {% endif %} + {% endif %} read(this%unit_number) value end subroutine ReadArray{{dim}}d{{name}} diff --git a/lib/nan_test/lfric/Makefile b/lib/nan_test/lfric/Makefile index 7ae9cbd7366..c8f69bf76f2 100644 --- a/lib/nan_test/lfric/Makefile +++ b/lib/nan_test/lfric/Makefile @@ -61,7 +61,7 @@ INF_INC = $(LFRIC_INF_DIR) INF_LIB_NAME = lfric INF_LIB = $(LFRIC_INF_DIR)/lib$(INF_LIB_NAME).a -F90FLAGS += -I$(INF_INC)/field +F90FLAGS += -I$(INF_INC)/field -I$(INF_INC)/function_space -I$(INF_INC)/mesh -I$(INF_INC)/utilities -I$(INF_INC)/scalar -I$(INF_INC)/configuration PSYDATA_LIB_NAME = _nan_test PSYDATA_LIB = lib$(PSYDATA_LIB_NAME).a diff --git a/lib/process.py b/lib/process.py index be538728b9f..fbed6ea70cd 100755 --- a/lib/process.py +++ b/lib/process.py @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -46,7 +46,6 @@ used to add a prefix to static functions defined in the template. ''' -from __future__ import print_function import argparse import sys from jinja2 import Environment @@ -59,7 +58,8 @@ parser.add_argument('template_name', help="Name of the template file to process.") parser.add_argument('-types', help="Comma-separated list of types, " - "e.g. real,int,double (no spaces).", + "e.g. real,int,double,char,logical,long " + "(no spaces).", default="real,int,double") parser.add_argument("-dims", help="Comma-separated list of dimensions, " "e.g. 1,2,4 (no spaces)", @@ -105,6 +105,7 @@ TYPE_DATA = {"real": ("Real", "real(kind=real32)", 32), "double": ("Double", "real(kind=real64)", 64), "int": ("Int", "integer(kind=int32)", 32), + "char": ("Char", "character(*)", 8), "logical": ("Logical", "Logical(kind=4)", 4), "long": ("Long", "integer(kind=int64)", 64)} diff --git a/psyclone.pdf b/psyclone.pdf index 321f561fcca..868102f765d 100644 Binary files a/psyclone.pdf and b/psyclone.pdf differ diff --git a/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py b/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py index c5fd8f6e09d..a4ffe6274b4 100644 --- a/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py +++ b/src/psyclone/domain/common/transformations/kernel_module_inline_trans.py @@ -42,7 +42,7 @@ from psyclone.psyGen import Transformation, CodedKern from psyclone.psyir.transformations import TransformationError from psyclone.psyir.symbols import RoutineSymbol, DataSymbol, \ - DataTypeSymbol, Symbol, ContainerSymbol + DataTypeSymbol, Symbol, ContainerSymbol, DefaultModuleInterface from psyclone.psyir.nodes import Container, ScopingNode, Reference, Routine, \ Literal, CodeBlock, Call @@ -277,7 +277,9 @@ def apply(self, node, options=None): if not existing_symbol: # If it doesn't exist already, module-inline the subroutine by: # 1) Registering the subroutine symbol in the Container - node.ancestor(Container).symbol_table.add(RoutineSymbol(name)) + node.ancestor(Container).symbol_table.add(RoutineSymbol( + name, interface=DefaultModuleInterface() + )) # 2) Insert the relevant code into the tree. node.ancestor(Container).addchild(code_to_inline.detach()) else: diff --git a/src/psyclone/domain/lfric/__init__.py b/src/psyclone/domain/lfric/__init__.py index edd04efca55..fc2eeb3547a 100644 --- a/src/psyclone/domain/lfric/__init__.py +++ b/src/psyclone/domain/lfric/__init__.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author J. Henrichs, Bureau of Meteorology -# Modified: I. Kavcic and L. Turner, Met Office +# Modified: I. Kavcic, L. Turner and O. Brunt, Met Office # R. W. Ford and A. R. Porter, STFC Daresbury Lab '''Module for the LFRic domain. @@ -63,6 +63,7 @@ from psyclone.domain.lfric.arg_index_to_metadata_index import \ ArgIndexToMetadataIndex from psyclone.domain.lfric.lfric_collection import LFRicCollection +from psyclone.domain.lfric.lfric_loop_bounds import LFRicLoopBounds __all__ = [ @@ -77,4 +78,5 @@ 'LFRicConstants', 'LFRicExtractDriverCreator', 'LFRicInvoke', - 'LFRicSymbolTable'] + 'LFRicLoopBounds', + 'LFRicSymbolTable'] \ No newline at end of file diff --git a/src/psyclone/domain/lfric/algorithm/psyir/lfric_alg_invoke_call.py b/src/psyclone/domain/lfric/algorithm/psyir/lfric_alg_invoke_call.py index 7521680605d..fb7e08de7ba 100644 --- a/src/psyclone/domain/lfric/algorithm/psyir/lfric_alg_invoke_call.py +++ b/src/psyclone/domain/lfric/algorithm/psyir/lfric_alg_invoke_call.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021-2022, Science and Technology Facilities Council. +# Copyright (c) 2021-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -38,7 +38,7 @@ ''' from psyclone.domain.common.algorithm import AlgorithmInvokeCall from psyclone.domain.lfric.algorithm.psyir.lfric_kernel_functor import ( - LFRicFunctor) + LFRicFunctor, LFRicBuiltinFunctor) class LFRicAlgorithmInvokeCall(AlgorithmInvokeCall): @@ -68,6 +68,20 @@ def _def_container_root_name(node): ''' return f"{node.name}_psy" + def _def_routine_root_name(self): + ''' + :returns: the proposed processed routine name for this invoke. + :rtype: str + + ''' + if (len(self.children) == 1 and + isinstance(self.children[0], LFRicBuiltinFunctor)): + # By default the name of the kernel is added if there is + # only one functor. However we don't add this in LFRic if + # the functor is a builtin. + return f"invoke_{self._index}" + return super()._def_routine_root_name() + # For AutoAPI documentation generation. __all__ = ['LFRicAlgorithmInvokeCall'] diff --git a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py index 0813bd7ae5f..f30ff2590c9 100644 --- a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py +++ b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py @@ -477,7 +477,7 @@ def _validate_cma_assembly_kernel(self): # All arguments except the CMA argument must be read-only. # pylint: disable=unidiomatic-typecheck for meta_arg in self.meta_args: - if type(meta_arg) != ColumnwiseOperatorArgMetadata: + if type(meta_arg) is not ColumnwiseOperatorArgMetadata: if meta_arg.access != "gh_read": raise ParseError(self._validation_error_str( f"A CMA assembly kernel should have all arguments as " @@ -590,7 +590,7 @@ def _validate_cma_matrix_matrix_kernel(self): # Exactly one of the CMA arguments must be written to. # pylint: disable=unidiomatic-typecheck cma_writers = [meta_arg for meta_arg in self.meta_args if - type(meta_arg) == ColumnwiseOperatorArgMetadata + type(meta_arg) is ColumnwiseOperatorArgMetadata and meta_arg.access in lfric_constants.WRITE_ACCESSES] # pylint: enable=unidiomatic-typecheck if len(cma_writers) != 1: diff --git a/src/psyclone/domain/lfric/lfric_invoke.py b/src/psyclone/domain/lfric/lfric_invoke.py index bdb82a50f3c..1ce212e1410 100644 --- a/src/psyclone/domain/lfric/lfric_invoke.py +++ b/src/psyclone/domain/lfric/lfric_invoke.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab -# Modified I. Kavcic, A. Coughtrie and L. Turner, Met Office +# Modified I. Kavcic, A. Coughtrie, L. Turner and O. Brunt, Met Office # Modified J. Henrichs, Bureau of Meteorology # Modified A. B. G. Chalk and N. Nobre, STFC Daresbury Lab @@ -100,8 +100,8 @@ def __init__(self, alg_invocation, idx, invokes): DynMeshes, DynBoundaryConditions, DynProxies, LFRicRunTimeChecks, DynCellIterators, DynReferenceElement, - LFRicMeshProperties, LFRicLoopBounds, - DynGlobalSum) + LFRicMeshProperties, DynGlobalSum) + from psyclone.domain.lfric import LFRicLoopBounds self.scalar_args = LFRicScalarArgs(self) # Initialise our Invoke stencil information diff --git a/src/psyclone/domain/lfric/lfric_loop_bounds.py b/src/psyclone/domain/lfric/lfric_loop_bounds.py new file mode 100644 index 00000000000..f7708820e73 --- /dev/null +++ b/src/psyclone/domain/lfric/lfric_loop_bounds.py @@ -0,0 +1,115 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2023, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified I. Kavcic, A. Coughtrie, L. Turner and O. Brunt, Met Office +# Modified J. Henrichs, Bureau of Meteorology +# Modified A. B. G. Chalk and N. Nobre, STFC Daresbury Lab + +''' This module provides the LFRicLoopBounds Class that handles all variables + required for specifying loop limits within an LFRic PSy-layer routine.''' + +# Imports +from psyclone.configuration import Config +from psyclone.domain.lfric import LFRicCollection +from psyclone.f2pygen import AssignGen, CommentGen, DeclGen + + +class LFRicLoopBounds(LFRicCollection): + + ''' + Handles all variables required for specifying loop limits within + an LFRic PSy-layer routine. + ''' + + def _invoke_declarations(self, parent): + ''' + Only needed because method is virtual in parent class. + + :param parent: the f2pygen node representing the PSy-layer routine. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + + ''' + + def initialise(self, parent): + ''' + Updates the f2pygen AST so that all of the variables holding the lower + and upper bounds of all loops in an Invoke are initialised. + + :param parent: the f2pygen node representing the PSy-layer routine. + :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` + + ''' + loops = self._invoke.schedule.loops() + + if not loops: + return + + parent.add(CommentGen(parent, "")) + parent.add(CommentGen(parent, " Set-up all of the loop bounds")) + parent.add(CommentGen(parent, "")) + + sym_table = self._invoke.schedule.symbol_table + config = Config.get() + api_config = config.api_conf("dynamo0.3") + + for idx, loop in enumerate(loops): + + if loop.loop_type == "null": + # 'null' loops don't need any bounds. + continue + + root_name = f"loop{idx}_start" + lbound = sym_table.find_or_create_integer_symbol(root_name, + tag=root_name) + parent.add(AssignGen(parent, lhs=lbound.name, + rhs=loop._lower_bound_fortran())) + entities = [lbound.name] + + if loop.loop_type != "colour": + root_name = f"loop{idx}_stop" + ubound = sym_table.find_or_create_integer_symbol(root_name, + tag=root_name) + entities.append(ubound.name) + parent.add(AssignGen(parent, lhs=ubound.name, + rhs=loop._upper_bound_fortran())) + + parent.add(DeclGen(parent, datatype="integer", + kind=api_config.default_kind["integer"], + entity_decls=entities)) + + +# ---------- Documentation utils -------------------------------------------- # +# The list of module members that we wish AutoAPI to generate +# documentation for. (See https://psyclone-ref.readthedocs.io) +__all__ = ['LFRicLoopBounds'] diff --git a/src/psyclone/domain/lfric/metadata_to_arguments_rules.py b/src/psyclone/domain/lfric/metadata_to_arguments_rules.py index 72e70e1b23c..989cbc0d6ca 100644 --- a/src/psyclone/domain/lfric/metadata_to_arguments_rules.py +++ b/src/psyclone/domain/lfric/metadata_to_arguments_rules.py @@ -464,11 +464,11 @@ def _generate(cls): cls._stencil_cross2d(meta_arg) else: cls._stencil(meta_arg) - elif type(meta_arg) == OperatorArgMetadata: + elif type(meta_arg) is OperatorArgMetadata: cls._operator(meta_arg) - elif type(meta_arg) == ColumnwiseOperatorArgMetadata: + elif type(meta_arg) is ColumnwiseOperatorArgMetadata: cls._cma_operator(meta_arg) - elif type(meta_arg) == ScalarArgMetadata: + elif type(meta_arg) is ScalarArgMetadata: cls._scalar(meta_arg) else: raise InternalError( diff --git a/src/psyclone/dynamo0p3.py b/src/psyclone/dynamo0p3.py index ea111598f0a..6bba02d9cba 100644 --- a/src/psyclone/dynamo0p3.py +++ b/src/psyclone/dynamo0p3.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab -# Modified I. Kavcic, A. Coughtrie and L. Turner, Met Office +# Modified I. Kavcic, A. Coughtrie, L. Turner and O. Brunt, Met Office # Modified J. Henrichs, Bureau of Meteorology # Modified A. B. G. Chalk and N. Nobre, STFC Daresbury Lab @@ -3286,69 +3286,6 @@ def initialise(self, parent): self._first_var.ref_name() + "%get_nlayers()")) -class LFRicLoopBounds(LFRicCollection): - ''' - Handles all variables required for specifying loop limits within a - PSy-layer routine. - - ''' - def _invoke_declarations(self, parent): - ''' - Only needed because method is virtual in parent class. - - :param parent: the f2pygen node representing the PSy-layer routine. - :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` - - ''' - - def initialise(self, parent): - ''' - Updates the f2pygen AST so that all of the variables holding the lower - and upper bounds of all loops in an Invoke are initialised. - - :param parent: the f2pygen node representing the PSy-layer routine. - :type parent: :py:class:`psyclone.f2pygen.SubroutineGen` - - ''' - loops = self._invoke.schedule.loops() - - if not loops: - return - - parent.add(CommentGen(parent, "")) - parent.add(CommentGen(parent, " Set-up all of the loop bounds")) - parent.add(CommentGen(parent, "")) - - sym_table = self._invoke.schedule.symbol_table - config = Config.get() - api_config = config.api_conf("dynamo0.3") - - for idx, loop in enumerate(loops): - - if loop.loop_type == "null": - # 'null' loops don't need any bounds. - continue - - root_name = f"loop{idx}_start" - lbound = sym_table.find_or_create_integer_symbol(root_name, - tag=root_name) - parent.add(AssignGen(parent, lhs=lbound.name, - rhs=loop._lower_bound_fortran())) - entities = [lbound.name] - - if loop.loop_type != "colour": - root_name = f"loop{idx}_stop" - ubound = sym_table.find_or_create_integer_symbol(root_name, - tag=root_name) - entities.append(ubound.name) - parent.add(AssignGen(parent, lhs=ubound.name, - rhs=loop._upper_bound_fortran())) - - parent.add(DeclGen(parent, datatype="integer", - kind=api_config.default_kind["integer"], - entity_decls=entities)) - - class LFRicScalarArgs(LFRicCollection): ''' Handles the declarations of scalar kernel arguments appearing in either diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index c3d2aafc39d..69dc40bdf2b 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -61,6 +61,7 @@ from psyclone.domain.gocean.transformations import ( RaisePSyIR2GOceanKernTrans, GOceanAlgInvoke2PSyCallTrans) from psyclone.domain.lfric.algorithm import LFRicBuiltinFunctor +from psyclone.domain.lfric.lfric_builtins import BUILTIN_MAP from psyclone.domain.lfric.transformations import ( LFRicAlgTrans, RaisePSyIR2LFRicKernTrans, LFRicAlgInvoke2PSyCallTrans) from psyclone.errors import GenerationError, InternalError @@ -75,6 +76,7 @@ from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.nodes import Loop, Container, Routine +from psyclone.psyir.symbols import UnresolvedInterface from psyclone.psyir.transformations import TransformationError from psyclone.version import __VERSION__ @@ -92,7 +94,6 @@ def handle_script(script_name, info, function_name, is_optional=False): - # pylint: disable=too-many-locals '''Loads and applies the specified script to the given algorithm or psy layer. The relevant script function (in 'function_name') is called with 'info' as the argument. @@ -188,34 +189,39 @@ def generate(filename, api="", kernel_paths=None, script_name=None, :param str filename: the file containing the algorithm specification. :param str api: the name of the API to use. Defaults to empty string. - :param kernel_paths: the directories from which to recursively \ - search for the files containing the kernel source (if \ - different from the location of the algorithm specification). \ + :param kernel_paths: the directories from which to recursively + search for the files containing the kernel source (if + different from the location of the algorithm specification). Defaults to None. :type kernel_paths: Optional[List[str]] - :param str script_name: a script file that can apply optimisations \ - to the PSy layer (can be a path to a file or a filename that \ + :param str script_name: a script file that can apply optimisations + to the PSy layer (can be a path to a file or a filename that relies on the PYTHONPATH to find the module). Defaults to None. - :param bool line_length: a logical flag specifying whether we care \ - about line lengths being longer than 132 characters. If so, \ - the input (algorithm and kernel) code is checked to make sure \ + :param bool line_length: a logical flag specifying whether we care + about line lengths being longer than 132 characters. If so, + the input (algorithm and kernel) code is checked to make sure that it conforms. The default is False. - :param bool distributed_memory: a logical flag specifying whether \ - to generate distributed memory code. The default is set in the \ + :param bool distributed_memory: a logical flag specifying whether + to generate distributed memory code. The default is set in the 'config.py' file. - :param str kern_out_path: directory to which to write transformed \ + :param str kern_out_path: directory to which to write transformed kernel code. Defaults to empty string. - :param bool kern_naming: the scheme to use when re-naming transformed \ + :param bool kern_naming: the scheme to use when re-naming transformed kernels. Defaults to "multiple". - :return: 2-tuple containing the fparser1 AST for the algorithm code and \ + :return: 2-tuple containing the fparser1 AST for the algorithm code and the fparser1 AST or a string (for NEMO) of the psy code. - :rtype: Tuple[:py:class:`fparser.one.block_statements.BeginSource`, \ - :py:class:`fparser.one.block_statements.Module`] | \ - Tuple[:py:class:`fparser.one.block_statements.BeginSource`, str] + :rtype: Tuple[:py:class:`fparser.one.block_statements.BeginSource`, + :py:class:`fparser.one.block_statements.Module`] | + Tuple[:py:class:`fparser.one.block_statements.BeginSource`, str] :raises GenerationError: if an invalid API is specified. :raises GenerationError: if an invalid kernel-renaming scheme is specified. + :raises GenerationError: if there is an error raising the PSyIR to + domain-specific PSyIR. + :raises GenerationError: if a kernel functor is not named in a use + statement. :raises IOError: if the filename or search path do not exist. + :raises NoInvokesError: if no invokes are found in the algorithm file. For example: @@ -318,6 +324,27 @@ def generate(filename, api="", kernel_paths=None, script_name=None, if isinstance(kern, LFRicBuiltinFunctor): # Skip builtins continue + if isinstance(kern.symbol.interface, UnresolvedInterface): + # This kernel functor is not specified in a use statement. + # Find all container symbols that are in scope. + st_ref = kern.scope.symbol_table + container_symbols = [ + symbol.name for symbol in st_ref.containersymbols] + while st_ref.parent_symbol_table(): + st_ref = st_ref.parent_symbol_table() + container_symbols += [ + symbol.name for symbol in st_ref.containersymbols] + message = ( + f"Kernel functor '{kern.symbol.name}' in routine " + f"'{kern.scope.name}' from algorithm file " + f"'{filename}' must be named in a use " + f"statement (found {container_symbols})") + if api == "dynamo0.3": + message += ( + f" or be a recognised built-in (one of " + f"{list(BUILTIN_MAP.keys())})") + message += "." + raise GenerationError(message) container_symbol = kern.symbol.interface.container_symbol # Find the kernel file containing the container diff --git a/src/psyclone/line_length.py b/src/psyclone/line_length.py index abc87c4b11d..e97c8e8770c 100644 --- a/src/psyclone/line_length.py +++ b/src/psyclone/line_length.py @@ -1,27 +1,78 @@ # ----------------------------------------------------------------------------- -# (c) The copyright relating to this work is owned jointly by the Crown, -# Met Office and NERC 2015. -# However, it has been created with the help of the GungHo Consortium, -# whose members are identified at https://puma.nerc.ac.uk/trac/GungHo/wiki +# BSD 3-Clause License +# +# Copyright (c) 2017-2023, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author R. Ford STFC Daresbury Lab +# Modified by A. B. G. Chalk, STFC Daresbury Lab +# ----------------------------------------------------------------------------- ''' Provides support for breaking long fortran lines into smaller ones to allow the code to conform to the maximum line length limits (132 for f90 free format is the default)''' +import re + +from psyclone.errors import InternalError -def find_break_point(line, max_index, key_list): - ''' find the most appropriate break point for a fortran line ''' +def find_break_point(line, max_index, key_list): + ''' Finds the most appropriate line break point for the Fortran code in + line. + + :param str line: the Fortran code string to find the line break point for. + :param int max_index: the maximum index in line to search for the line + break point. + :param key_list: list of potential symbols to break the line at. The + members of the list early in the ordering have priority + for breaking the line, i.e. if the list contains multiple + elements, any possible position of the first element will + be found before trying any other element of the list. + :type key_list: List[str] + + :returns: index to break the line into multiple lines. + :rtype: int + + :raises InternalError: if no suitable break point is found in line. + ''' + # We should never break the line before the first element on the + # line. + first_non_whitespace = len(line) - len(line.lstrip()) for key in key_list: - idx = line.rfind(key, 0, max_index) + idx = line.rfind(key, first_non_whitespace+1, max_index) if idx > 0: return idx+len(key) - raise Exception( - "Error in find_break_point. No suitable break point found" - " for line '" + line[:max_index] + "' and keys '" + - str(key_list) + "'") + raise InternalError( + f"Error in find_break_point. No suitable break point found" + f" for line '{line[:max_index]}' and keys '{str(key_list)}'") class FortLineLength(): @@ -48,7 +99,6 @@ def __init__(self, line_length=132): "openacc_directive": [" ", ",", ")", "="], "comment": [" ", ".", ","], "unknown": [" ", ",", "=", "+", ")"]} - import re self._stat = re.compile(r'^\s*(INTEGER|REAL|TYPE|CALL|SUBROUTINE|USE)', flags=re.I) self._omp = re.compile(r'^\s*!\$OMP', flags=re.I) @@ -69,9 +119,15 @@ def length(self): return self._line_length def process(self, fortran_in): - ''' takes fortran code as a string as input and output fortran - code as a string with any long lines wrapped appropriately ''' + ''' Processes unlimited line-length Fortran code into Fortran + code with long lines wrapped appropriately. + + :param str fortran_in: Fortran code to be line wrapped. + :returns: line wrapped Fortran code. + :rtype: str + + ''' fortran_out = "" for line in fortran_in.split('\n'): if len(line) > self._line_length: @@ -81,8 +137,19 @@ def process(self, fortran_in): c_end = self._cont_end[line_type] key_list = self._key_lists[line_type] - break_point = find_break_point( - line, self._line_length-len(c_end), key_list) + try: + break_point = find_break_point( + line, self._line_length-len(c_end), key_list) + except InternalError: + # Couldn't find a valid point to break the line. + # Remove indentation and try again. + line = line.lstrip() + if len(line) < self._line_length: + fortran_out += line + "\n" + continue + break_point = find_break_point( + line, self._line_length-len(c_end), key_list) + fortran_out += line[:break_point] + c_end + "\n" line = line[break_point:] while len(line) + len(c_start) > self._line_length: diff --git a/src/psyclone/psyad/domain/common/adjoint_utils.py b/src/psyclone/psyad/domain/common/adjoint_utils.py index 7647f6efb76..84b1bc4d112 100644 --- a/src/psyclone/psyad/domain/common/adjoint_utils.py +++ b/src/psyclone/psyad/domain/common/adjoint_utils.py @@ -100,7 +100,8 @@ def create_real_comparison(sym_table, kernel, var1, var2): overall_tol = sym_table.new_symbol("overall_tolerance", symbol_type=DataSymbol, datatype=var1.datatype, - constant_value=INNER_PRODUCT_TOLERANCE) + is_constant=True, + initial_value=INNER_PRODUCT_TOLERANCE) # TODO #1161 - the PSyIR does not support `SPACING` assign = freader.psyir_from_statement( f"MachineTol = SPACING ( MAX( ABS({var1.name}), ABS({var2.name}) ) )", diff --git a/src/psyclone/psyad/tl2ad.py b/src/psyclone/psyad/tl2ad.py index cde28bf01d5..d51a891f979 100644 --- a/src/psyclone/psyad/tl2ad.py +++ b/src/psyclone/psyad/tl2ad.py @@ -279,9 +279,9 @@ def _add_precision_symbol(symbol, table): if symbol.name in table: return - if symbol.is_automatic or symbol.is_modulevar: - table.add(symbol.copy()) - elif symbol.is_import: + if symbol.is_import: + # Handle imported symbols first because they may also be constants + # while the reverse is not true. contr_sym = symbol.interface.container_symbol try: kind_contr_sym = table.lookup(contr_sym.name) @@ -292,6 +292,8 @@ def _add_precision_symbol(symbol, table): kind_symbol = symbol.copy() kind_symbol.interface = ImportInterface(kind_contr_sym) table.add(kind_symbol) + elif symbol.is_automatic or symbol.is_modulevar or symbol.is_constant: + table.add(symbol.copy()) else: raise NotImplementedError( f"One or more variables have a precision specified by symbol " @@ -396,7 +398,8 @@ def generate_adjoint_test(tl_psyir, ad_psyir, dim_size_sym = symbol_table.new_symbol("array_extent", symbol_type=DataSymbol, datatype=INTEGER_TYPE, - constant_value=TEST_ARRAY_DIM_SIZE) + is_constant=True, + initial_value=TEST_ARRAY_DIM_SIZE) # Create symbols for the results of the inner products inner1 = symbol_table.new_symbol("inner1", symbol_type=DataSymbol, @@ -439,7 +442,8 @@ def generate_adjoint_test(tl_psyir, ad_psyir, new_dim_args_map[arg] = symbol_table.new_symbol( arg.name, symbol_type=DataSymbol, datatype=arg.datatype, - constant_value=Reference(dim_size_sym)) + is_constant=True, + initial_value=Reference(dim_size_sym)) # Create necessary variables for the kernel arguments. inputs = [] diff --git a/src/psyclone/psyad/transformations/assignment_trans.py b/src/psyclone/psyad/transformations/assignment_trans.py index 3aebf909ef0..7e91fddd7b4 100644 --- a/src/psyclone/psyad/transformations/assignment_trans.py +++ b/src/psyclone/psyad/transformations/assignment_trans.py @@ -212,7 +212,7 @@ def _array_ranges_match(self, assign, active_variable): # TODO #1537. This is a workaround until the SymbolicMaths # class supports the comparison of array ranges. # pylint: disable=unidiomatic-typecheck - if not (type(idx) == type(lhs_idx) and + if not (type(idx) is type(lhs_idx) and sym_maths.equal(idx.start, lhs_idx.start) and sym_maths.equal(idx.stop, diff --git a/src/psyclone/psyad/transformations/preprocess.py b/src/psyclone/psyad/transformations/preprocess.py index e5ce43fdb49..a4b48da5c24 100644 --- a/src/psyclone/psyad/transformations/preprocess.py +++ b/src/psyclone/psyad/transformations/preprocess.py @@ -40,9 +40,8 @@ ''' from psyclone.core import SymbolicMaths -from psyclone.psyad.utils import node_is_active, node_is_passive -from psyclone.psyir.nodes import (BinaryOperation, Assignment, Reference, - StructureReference) +from psyclone.psyad.utils import node_is_passive +from psyclone.psyir.nodes import (Assignment, BinaryOperation, Reference) from psyclone.psyir.transformations import (DotProduct2CodeTrans, Matmul2CodeTrans, ArrayRange2LoopTrans, @@ -101,94 +100,5 @@ def preprocess_trans(kernel_psyir, active_variable_names): # Deal with any associativity issues here as AssignmentTrans # is not able to. for assignment in kernel_psyir.walk(Assignment): - if assignment.walk(StructureReference): - # SymbolicMaths currently does not work if the expression - # contains user-defined types, see issue #2166. - associativity(assignment, active_variable_names) - else: - sym_maths = SymbolicMaths.get() - sym_maths.expand(assignment.rhs) - - -def associativity(assignment, active_variable_names): - '''Repeatedly look for the patterns x * (a +- b) or (a +- b) */ x on - the rhs of this assignment where x is an inactive expression and a - and b are active expressions, replacing these patterns with x*a +- - x*b and a*/x +- b*/x respectively. - - This function can be removed when support for Range nodes is added - to the SymbolicMaths expand function, see issue #1655. - - :param assignment: the Assignment Node that we are looking at. - :type assignment: :py:class:`psyclone.psyir.nodes.Assignment` - :param active_variable_names: list of active variable names. - :type active_variable_names: list of str - - ''' - if node_is_active(assignment.rhs, active_variable_names): - while True: - for oper in assignment.rhs.walk(BinaryOperation): - # pylint: disable=too-many-boolean-expressions - if oper.operator == BinaryOperation.Operator.MUL and \ - node_is_passive( - oper.children[0], active_variable_names) and \ - isinstance(oper.children[1], BinaryOperation) and \ - oper.children[1].operator in [ - BinaryOperation.Operator.ADD, - BinaryOperation.Operator.SUB] and \ - node_is_active( - oper.children[1].children[0], - active_variable_names) and \ - node_is_active( - oper.children[1].children[1], - active_variable_names): - # Matched one of the patterns we are looking for - # x * (a +- b) - inactive = oper.children[0] - active0 = oper.children[1].children[0] - active1 = oper.children[1].children[1] - binary_op = oper.children[1] - # Restructure to x*a +- x*b - mult0 = BinaryOperation.create( - BinaryOperation.Operator.MUL, inactive.detach(), - active0.detach()) - mult1 = BinaryOperation.create( - BinaryOperation.Operator.MUL, inactive.copy(), - active1.detach()) - binary_op.children.extend([mult0, mult1]) - oper.replace_with(binary_op.detach()) - break - - if oper.operator in [ - BinaryOperation.Operator.MUL, - BinaryOperation.Operator.DIV] and \ - node_is_passive( - oper.children[1], active_variable_names) and \ - isinstance(oper.children[0], BinaryOperation) and \ - oper.children[0].operator in [ - BinaryOperation.Operator.ADD, - BinaryOperation.Operator.SUB] and \ - node_is_active( - oper.children[0].children[0], - active_variable_names) and \ - node_is_active( - oper.children[0].children[1], - active_variable_names): - # Matched one of the patterns we are looking - # for: (a +- b) */ x - inactive = oper.children[1] - active0 = oper.children[0].children[0] - active1 = oper.children[0].children[1] - binary_op = oper.children[0] - # Restructure to a */ x +- b */ x - op0 = BinaryOperation.create( - oper.operator, active0.detach(), inactive.detach()) - op1 = BinaryOperation.create( - oper.operator, active1.detach(), inactive.copy()) - binary_op.children.extend([op0, op1]) - oper.replace_with(binary_op.detach()) - break - - else: - # No matching pattern so break out of while loop - break + sym_maths = SymbolicMaths.get() + sym_maths.expand(assignment.rhs) diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 3947ec3e2d2..0863cb2eee3 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -553,15 +553,17 @@ def gen_vardecl(self, symbol, include_visibility=False): :rtype: str :raises VisitorError: if the symbol is of DeferredType. - :raises VisitorError: if the symbol is of UnknownType other than \ + :raises VisitorError: if the symbol is of UnknownType other than UnknownFortranType. - :raises VisitorError: if the symbol is of known type but does not \ - specify a variable declaration (it is not a local declaration or \ + :raises VisitorError: if the symbol is of known type but does not + specify a variable declaration (it is not a local declaration or an argument declaration). + :raises VisitorError: if the symbol is a runtime constant but does not + have a StaticInterface. :raises InternalError: if the symbol is a ContainerSymbol or an import. - :raises InternalError: if the symbol is a RoutineSymbol other than \ + :raises InternalError: if the symbol is a RoutineSymbol other than UnknownFortranType. - :raises InternalError: if visibility is to be included but is not \ + :raises InternalError: if visibility is to be included but is not either PUBLIC or PRIVATE. ''' @@ -644,9 +646,15 @@ def gen_vardecl(self, symbol, include_visibility=False): # Specify name result += f" :: {symbol.name}" - # Specify initialization expression - if isinstance(symbol, DataSymbol) and symbol.is_constant: - result += " = " + self._visit(symbol.constant_value) + # Specify initialisation expression + if isinstance(symbol, DataSymbol) and symbol.initial_value: + if not symbol.is_static: + raise VisitorError( + f"{type(symbol).__name__} '{symbol.name}' has an initial " + f"value ({self._visit(symbol.initial_value)}) and " + f"therefore (in Fortran) must have a StaticInterface. " + f"However it has an interface of '{symbol.interface}'.") + result += " = " + self._visit(symbol.initial_value) return result + "\n" @@ -848,10 +856,10 @@ def _gen_parameter_decls(self, symbol_table, is_module_scope=False): decln_inputs[symbol.name] = set() read_write_info = ReadWriteInfo() self._dep_tools.get_input_parameters(read_write_info, - symbol.constant_value) + symbol.initial_value) # The dependence analysis tools do not include symbols used to # define precision so check for those here. - for lit in symbol.constant_value.walk(Literal): + for lit in symbol.initial_value.walk(Literal): if isinstance(lit.datatype.precision, DataSymbol): read_write_info.add_read( Signature(lit.datatype.precision.name)) @@ -893,23 +901,23 @@ def gen_decls(self, symbol_table, is_module_scope=False): :param symbol_table: the SymbolTable instance. :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable` - :param bool is_module_scope: whether or not the declarations are in \ - a module scoping unit. Default is False. + :param bool is_module_scope: whether or not the declarations are in + a module scoping unit. Default is False. :returns: the Fortran declarations for the table. :rtype: str - :raises VisitorError: if one of the symbols is a RoutineSymbol which \ - does not have an ImportInterface or UnresolvedInterface ( \ - representing named and unqualified imports respectively) or \ - ModuleDefaultInterface (representing routines declared in the \ + :raises VisitorError: if one of the symbols is a RoutineSymbol which + does not have an ImportInterface or UnresolvedInterface ( + representing named and unqualified imports respectively) or + ModuleDefaultInterface (representing routines declared in the same module) or is not a Fortran intrinsic. - :raises VisitorError: if args_allowed is False and one or more \ + :raises VisitorError: if args_allowed is False and one or more argument declarations exist in symbol_table. - :raises VisitorError: if there are any symbols (other than \ - RoutineSymbols) in the supplied table that do not have an \ - explicit declaration (UnresolvedInterface) and there are no \ - wildcard imports. + :raises VisitorError: if there are any symbols (other than + RoutineSymbols) in the supplied table that do not have an + explicit declaration (UnresolvedInterface) and there are no + wildcard imports or unknown interfaces. ''' # pylint: disable=too-many-branches @@ -936,16 +944,25 @@ def gen_decls(self, symbol_table, is_module_scope=False): isinstance(sym.interface, UnresolvedInterface)): all_symbols.remove(sym) - # If the symbol table contain any symbols with an UnresolvedInterface - # interface (they are not explicitly declared), we need to check that - # we have at least one wildcard import which could be bringing them - # into this scope. + # If the symbol table contains any symbols with an + # UnresolvedInterface interface (they are not explicitly + # declared), we need to check that we have at least one + # wildcard import which could be bringing them into this + # scope, or an unknown interface which could be declaring + # them. unresolved_symbols = [] for sym in all_symbols[:]: if isinstance(sym.interface, UnresolvedInterface): unresolved_symbols.append(sym) all_symbols.remove(sym) - if unresolved_symbols and not symbol_table.has_wildcard_imports(): + try: + internal_interface_symbol = symbol_table.lookup( + "_psyclone_internal_interface") + except KeyError: + internal_interface_symbol = None + if unresolved_symbols and not ( + symbol_table.has_wildcard_imports() or + internal_interface_symbol): symbols_txt = ", ".join( ["'" + sym.name + "'" for sym in unresolved_symbols]) raise VisitorError( @@ -1126,7 +1143,6 @@ def routine_node(self, node): node is empty or None. ''' - # pylint: disable=too-many-branches if not node.name: raise VisitorError("Expected node name to have a value.") diff --git a/src/psyclone/psyir/backend/sympy_writer.py b/src/psyclone/psyir/backend/sympy_writer.py index d0f5a741eea..faf4c6a35ce 100644 --- a/src/psyclone/psyir/backend/sympy_writer.py +++ b/src/psyclone/psyir/backend/sympy_writer.py @@ -35,21 +35,24 @@ # Modified: S. Siso, STFC Daresbury Lab -'''PSyIR backend to create expressions that are handled by sympy. +'''PSyIR backend to create expressions that are handled by SymPy. ''' +import keyword + from sympy import Function, Symbol from sympy.parsing.sympy_parser import parse_expr from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.backend.visitor import VisitorError +from psyclone.psyir.frontend.sympy_reader import SymPyReader from psyclone.psyir.nodes import (BinaryOperation, DataNode, NaryOperation, Range, Reference, UnaryOperation) from psyclone.psyir.symbols import (ArrayType, ScalarType, SymbolTable) class SymPyWriter(FortranWriter): - '''Implements a PSyIR-to-sympy writer, which is used to create a + '''Implements a PSyIR-to-SymPy writer, which is used to create a representation of the PSyIR tree that can be understood by SymPy. Most Fortran expressions work as expected without modification. This class implements special handling for constants (which can have a precision @@ -90,6 +93,11 @@ class SymPyWriter(FortranWriter): # is set to True as the modifications will persist after the Writer! _DISABLE_LOWERING = True + # A list of all reserved Python keywords (Fortran variables that are the + # same as a reserved name must be renamed, otherwise parsing will fail). + # This class attribute will get initialised in __init__: + _RESERVED_NAMES = set() + def __init__(self): super().__init__() @@ -106,7 +114,24 @@ def __init__(self): self._lower_bound = "sympy_lower" self._upper_bound = "sympy_upper" + if not SymPyWriter._RESERVED_NAMES: + # Get the list of all reserved Python words from the + # keyword module: + for reserved in keyword.kwlist: + # Some Python keywords are capitalised (True, False, None). + # Since all symbols in PSyclone are lowercase, these can + # never clash when using SymPy. So only take keywords that + # are in lowercase: + if reserved.lower() == reserved: + SymPyWriter._RESERVED_NAMES.add(reserved) + + # This dictionary will be supplied when parsing a string by SymPy + # and defines which symbols in the parsed expressions are scalars + # (SymPy symbols) or arrays (SymPy functions). self._sympy_type_map = {} + + # The set of intrinsic Fortran operations that need a rename or + # are case sensitive in SymPy: self._intrinsic = set() self._op_to_str = {} @@ -122,7 +147,7 @@ def __init__(self): (BinaryOperation.Operator.REM, "Mod"), # exp is needed for a test case only, in # general the maths functions can just be - # handled as unknown sympy functions. + # handled as unknown SymPy functions. (UnaryOperation.Operator.EXP, "exp"), ]: self._intrinsic.add(op_str) @@ -174,6 +199,55 @@ def __getitem__(self, _): raise NotImplementedError("__getitem__ for a SymPyWriter should " "never be called.") + # ------------------------------------------------------------------------- + def _create_sympy_array_function(self, name, sig=None, num_dims=None): + '''Creates a Function class with the given name to be used for SymPy + parsing. This Function overwrites the conversion to string, and will + replace the triplicated array indices back to the normal Fortran + syntax. If the signature Sig and number of dimensions for each + component of the signature are given, it will add this information + to the object, so that the SymPyReader can recreate the proper + access to a user-defined type. + + :param str name: name of the function class to create. + :param sig: the signature of the variable, which is required + to convert user defined types back properly. Only defined for + user-defined types. + :type sig: Optional[:py:class:`psyclone.core.Signature`] + :param num_dims: the number of dimensions for each component of a + user defined type. + :type num_dims: Optional[List[int]] + + :returns: a SymPy function, which has a special ``_sympystr`` function + defined as attribute to print user-defined types.. + :rtype: :py:class:`sympy.Function` + ''' + + # Now a new Fortran array is used. Create a new function + # instance, and overwrite how this function is converted back + # into a string by defining the ``_sympystr`` attribute, + # which points to a function that controls how this object + # is converted into a string. Use the ``print_fortran_array`` + # function from the SymPyReader for this. Note that we cannot + # create a derived class based on ``Function`` and define + # this function there: SymPy tests internally if the type is a + # Function (not if it is an instance), therefore, SymPy's + # behaviour would change if we used a derived class: + # https://docs.sympy.org/latest/modules/functions/index.html: + # "It [Function class] also serves as a constructor for undefined + # function classes." + new_func = Function(name) + # pylint: disable=protected-access + new_func._sympystr = SymPyReader.print_fortran_array + + # Store the signature and the number of dimensions of each + # component, so that SymPyReader.print_fortran_array can match + # the indices back to the user defined types. + new_func._sig = sig + new_func._num_dims = num_dims + # pylint: enable=protected-access + return new_func + # ------------------------------------------------------------------------- def _create_type_map(self, list_of_expressions): '''This function creates a dictionary mapping each Reference in any @@ -188,20 +262,30 @@ def _create_type_map(self, list_of_expressions): it is important to provide all expressions at once for the symbol table to avoid name clashes in any expression. + This function also handles reserved names like 'lambda' which might + occur in one of the Fortran expressions. These reserved names must be + renamed (otherwise SymPy parsing, which uses ``eval`` internally, + fails). A symbol table is used which is initially filled with all + reserved names. Then for each reference accounted, a unique name is + generated using this symbol table - so if the reference is a reserved + name, a new name will be created (e.g. ``lambda`` might become + ``lambda_1``). The SymPyWriter (e.g. in ``reference_node``) will + use the renamed value when creating the string representation. + :param list_of_expressions: the list of expressions from which all references are taken and added to a symbol table to avoid renaming any symbols (so that only member names will be renamed). :type list_of_expressions: List[:py:class:`psyclone.psyir.nodes.Node`] ''' - # Avoid circular dependency - # pylint: disable=import-outside-toplevel - from psyclone.psyir.frontend.sympy_reader import SymPyReader - # Create a new symbol table, so previous symbol will not affect this # new conversion (i.e. this avoids name clashes with a previous - # conversion). + # conversion). First add all reserved names so that these names will + # automatically be renamed. The symbol table is used later to also + # create guaranteed unique names for lower and upper bounds. self._symbol_table = SymbolTable() + for reserved in SymPyWriter._RESERVED_NAMES: + self._symbol_table.new_symbol(reserved) # Find each reference in each of the expression, and declare this name # as either a SymPy Symbol (scalar reference), or a SymPy Function @@ -209,35 +293,29 @@ def _create_type_map(self, list_of_expressions): for expr in list_of_expressions: for ref in expr.walk(Reference): name = ref.name - if name in self._symbol_table: - # The name has already been declared, ignore it now + # The reserved Python keywords do not have tags, so they + # will not be found. + if name in self._symbol_table.tags_dict: continue - # Add the new name to the symbol table to mark it - # as done - self._symbol_table.find_or_create(name) - + # Any symbol from the list of expressions to be handled + # will be created with a tag, so if the same symbol is + # used more than once, the previous test will prevent + # calling new_symbol again. If the name is a Python + # reserved symbol, a new unique name will be created by + # the symbol table. + unique_sym = self._symbol_table.new_symbol(name, tag=name) # Test if an array or an array expression is used: if not ref.is_array: - # A simple scalar, create a SymPy symbol - self._sympy_type_map[name] = Symbol(name) + self._sympy_type_map[unique_sym.name] = Symbol(name) continue - # Now a new Fortran array is used. Create a new function - # instance, and overwrite how this function is converted back - # into a string by defining the ``_sympystr`` attribute, - # which points to a function that controls how this object - # is converted into a string. Use the ``print_fortran_array`` - # function from the SymPyReader for this. Note that we cannot - # create a derived class based on ``Function`` and define - # this function there: SymPy tests internally if the type is a - # Function (not if it is an instance), therefore, SymPy's - # behaviour would change if we used a derived class. - array_func = Function(name) - # pylint: disable=protected-access - array_func._sympystr = SymPyReader.print_fortran_array - # pylint: enable=protected-access - self._sympy_type_map[name] = array_func + # A Fortran array is used which has not been seen before. + # Declare a new SymPy function for it. This SymPy function + # will convert array expressions back into the original + # Fortran code. + self._sympy_type_map[unique_sym.name] = \ + self._create_sympy_array_function(name) # Now all symbols have been added to the symbol table, create # unique names for the lower- and upper-bounds using special tags: @@ -354,60 +432,89 @@ def __call__(self, list_of_expressions): return result[0] # ------------------------------------------------------------------------- - def member_node(self, node): - '''In SymPy an access to a member ``b`` of a structure ``a`` - (i.e. ``a%b`` in Fortran) is handled as the ``MOD`` function - ``MOD(a, b)``. We must therefore make sure that a member - access is unique (e.g. ``b`` could already be a scalar variable). - This is done by creating a new name, which replaces the ``%`` - with an ``_``. So ``a%b`` becomes ``MOD(a, a_b)``. This makes it easier - to see where the function names come from. - Additionally, we still need to avoid a name clash, e.g. there - could already be a variable ``a_b``. This is done by using a symbol - table, which was prefilled with all references (``a`` in the example - above) in the constructor. We use the string containing the ``%`` as - a unique tag and get a new, unique symbol from the symbol table - based on the new name using ``_``. For example, the access to member - ``b`` in ``a(i)%b`` would result in a new symbol with tag ``a%b`` and a - name like ``a_b`, `a_b_1``, ... - - :param node: a Member PSyIR node. - :type node: :py:class:`psyclone.psyir.nodes.Member` - - :returns: the SymPy representation of this member access. + def arrayreference_node(self, node): + '''The implementation of the method handling a + ArrayOfStructureReference is generic enough to also handle + non-structure arrays. So just use it. + + :param node: a ArrayReference PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.ArrayReference` + + :returns: the code as string. :rtype: str ''' - # We need to find the parent reference in order to make a new - # name (a%b%c --> a_b_c). Collect the names of members and the - # symbol in a list. - parent = node - name_list = [node.name] - while not isinstance(parent, Reference): - parent = parent.parent - name_list.append(parent.name) - name_list.reverse() - - # The root name uses _, the tag uses % (which are guaranteed - # to be unique, the root_name might clash with a user defined - # variable otherwise). - root_name = "_".join(name_list) - sig_name = "%".join(name_list) - new_sym = self._symbol_table.find_or_create_tag(tag=sig_name, - root_name=root_name) - new_name = new_sym.name - if new_name not in self._sympy_type_map: - if node.is_array: - self._sympy_type_map[new_name] = Function(new_name) - else: - self._sympy_type_map[new_name] = Symbol(new_name) + return self.arrayofstructuresreference_node(node) - # Now get the original string that this node produces: - original_name = super().member_node(node) + # ------------------------------------------------------------------------- + def structurereference_node(self, node): + '''The implementation of the method handling a + ArrayOfStructureReference is generic enough to also handle non-arrays. + So just use it. + + :param node: a StructureReference PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.StructureReference` + + :returns: the code as string. + :rtype: str + + ''' + return self.arrayofstructuresreference_node(node) - # And replace the `node.name` (which must be at the beginning since - # it is a member) with the new name from the symbol table: - return new_name + original_name[len(node.name):] + # ------------------------------------------------------------------------- + def arrayofstructuresreference_node(self, node): + '''This handles ArrayOfStructureReferences (and also simple + StructureReferences). An access like ``a(i)%b(j)`` is converted to + the string ``a_b(i,i,1,j,j,1)`` (also handling name clashes in case + that the user code already contains a symbol ``a_b``). The SymPy + function created for this new symbol will store the original signature + and the number of indices for each member (so in the example above + that would be ``Signature("a%b")`` and ``(1,1)``. This information + is sufficient to convert the SymPy symbol back to the correct Fortran + representation + + :param node: a StructureReference PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.StructureReference` + + :returns: the code as string. + :rtype: str + + ''' + sig, indices = node.get_signature_and_indices() + + out = [] + num_dims = [] + all_dims = [] + is_array = False + for i, name in enumerate(sig): + num_dims.append(len(indices[i])) + for index in indices[i]: + all_dims.append(index) + is_array = True + out.append(name) + flat_name = "_".join(out) + + # Find (or create) a unique variable name: + try: + unique_name = self._symbol_table.lookup_with_tag(str(sig)).name + except KeyError: + unique_name = self._symbol_table.new_symbol(flat_name, + tag=str(sig)).name + if is_array: + indices_str = self.gen_indices(all_dims) + # Create the corresponding SymPy function, which will store + # the signature and num_dims, so that the correct Fortran + # representation can be recreated later. + self._sympy_type_map[unique_name] = \ + self._create_sympy_array_function(unique_name, sig, num_dims) + return f"{unique_name}({','.join(indices_str)})" + + # Not an array access. We use the unique name for the string, + # but the required symbol is mapped to the original name, which means + # if the SymPy expression is converted to a string (in order to be + # parsed), it will use the original structure reference syntax: + self._sympy_type_map[unique_name] = Symbol(sig.to_language()) + return unique_name # ------------------------------------------------------------------------- def literal_node(self, node): @@ -498,10 +605,13 @@ def reference_node(self, node): :rtype: str ''' + # Support renaming a symbol (e.g. if it is a reserved Python name). + # Look up with the name as tag, which will return the symbol with + # a unique name (e.g. lambda --> lambda_1): + name = self._symbol_table.lookup_with_tag(node.name).name if not node.is_array: - # This reference is not an array, handle its conversion to - # string in the FortranWriter base class - return super().reference_node(node) + # This reference is not an array, just return the name + return name # Now this must be an array expression without parenthesis. Add # the triple-array indices to represent `lower:upper:1` for each @@ -510,7 +620,7 @@ def reference_node(self, node): result = [f"{self.lower_bound}," f"{self.upper_bound},1"]*len(shape) - return (f"{node.name}{self.array_parenthesis[0]}" + return (f"{name}{self.array_parenthesis[0]}" f"{','.join(result)}{self.array_parenthesis[1]}") # ------------------------------------------------------------------------ diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 2242317b3e7..6b3287ab633 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -222,57 +222,60 @@ def _first_type_match(nodelist, typekind): raise ValueError # Type not found -def _find_or_create_imported_symbol(location, name, scope_limit=None, - **kargs): +def _find_or_create_unresolved_symbol(location, name, scope_limit=None, + **kargs): '''Returns the symbol with the name 'name' from a symbol table associated with this node or one of its ancestors. If a symbol is found and the `symbol_type` keyword argument is supplied then the type of the existing symbol is compared with the specified type. If it is not already an instance of this type, then the symbol is specialised (in place). - If the symbol is not found and there are no ContainerSymbols with wildcard - imports then an exception is raised. However, if there are one or more + If the symbol is not found and there are no ContainerSymbols with + wildcard imports and no interfaces with unknown content then an + exception is raised. However, if there are one or more ContainerSymbols with wildcard imports (which could therefore be - bringing the symbol into scope) then a new Symbol with the - specified visibility but of unknown interface is created and - inserted in the most local SymbolTable that has such an import. - The scope_limit variable further limits the symbol table search so - that the search through ancestor nodes stops when the scope_limit node - is reached i.e. ancestors of the scope_limit node are not searched. + bringing the symbol into scope) or one or more interfaces with + unknown content then a new Symbol with the specified visibility + but of unknown interface is created and inserted in the most local + SymbolTable that has such an import. The scope_limit variable + further limits the symbol table search so that the search through + ancestor nodes stops when the scope_limit node is reached + i.e. ancestors of the scope_limit node are not searched. :param location: PSyIR node from which to operate. :type location: :py:class:`psyclone.psyir.nodes.Node` :param str name: the name of the symbol. - :param scope_limit: optional Node which limits the symbol \ - search space to the symbol tables of the nodes within the \ - given scope. If it is None (the default), the whole \ - scope (all symbol tables in ancestor nodes) is searched \ - otherwise ancestors of the scope_limit node are not \ + :param scope_limit: optional Node which limits the symbol + search space to the symbol tables of the nodes within the + given scope. If it is None (the default), the whole + scope (all symbol tables in ancestor nodes) is searched + otherwise ancestors of the scope_limit node are not searched. - :type scope_limit: :py:class:`psyclone.psyir.nodes.Node` or \ + :type scope_limit: :py:class:`psyclone.psyir.nodes.Node` or `NoneType` :returns: the matching symbol. :rtype: :py:class:`psyclone.psyir.symbols.Symbol` :raises TypeError: if the supplied scope_limit is not a Node. - :raises ValueError: if the supplied scope_limit node is not an \ + :raises ValueError: if the supplied scope_limit node is not an ancestor of the supplied node. - :raises SymbolError: if no matching symbol is found and there are \ - no ContainerSymbols from which it might be brought into scope. + :raises SymbolError: if no matching symbol is found and there are + no ContainerSymbols from which it might be brought into scope + or unknown interfaces which might contain its declaration. ''' if not isinstance(location, Node): raise TypeError( f"The location argument '{location}' provided to " - f"_find_or_create_imported_symbol() is not of type `Node`.") + f"_find_or_create_unresolved_symbol() is not of type `Node`.") if scope_limit is not None: # Validate the supplied scope_limit if not isinstance(scope_limit, Node): raise TypeError( f"The scope_limit argument '{scope_limit}' provided to " - f"_find_or_create_imported_symbol() is not of type `Node`.") + f"_find_or_create_unresolved_symbol() is not of type `Node`.") # Check that the scope_limit Node is an ancestor of this # Reference Node and raise an exception if not. @@ -288,8 +291,8 @@ def _find_or_create_imported_symbol(location, name, scope_limit=None, # supplied node so raise an exception. raise ValueError( f"The scope_limit node '{scope_limit}' provided to " - f"_find_or_create_imported_symbol() is not an ancestor of this" - f" node '{location}'.") + f"_find_or_create_unresolved_symbol() is not an ancestor of " + f"this node '{location}'.") # Keep a reference to the most local SymbolTable with a wildcard # import in case we need to create a Symbol. @@ -341,11 +344,92 @@ def _find_or_create_imported_symbol(location, name, scope_limit=None, return first_symbol_table.new_symbol( name, interface=UnresolvedInterface(), **kargs) + # Are there any interfaces that might be hiding the symbol declaration? + symbol_table = location.scope.symbol_table + try: + _ = symbol_table.lookup( + "_psyclone_internal_interface", scope_limit=scope_limit) + # There is an unknown interface so add this symbol. + return location.scope.symbol_table.new_symbol( + name, interface=UnresolvedInterface(), **kargs) + except KeyError: + pass + # All requested Nodes have been checked but there has been no - # match and there are no wildcard imports so raise an exception. + # match and there are no wildcard imports or unknown interfaces so + # raise an exception. raise SymbolError(f"No Symbol found for name '{name}'.") +def _find_or_create_psyclone_internal_cmp(node): + ''' + Utility routine to return a symbol of the generic psyclone comparison + interface. If the interface does not exist in the scope it first adds + the necessary code to the parent module. + + :param node: location where the comparison interface is needed. + :type node: :py:class:`psyclone.psyir.nodes.Node` + :returns: the comparison interface symbol. + :rtype: :py:class:`psyclone.psyir.symbols.Symbol` + + :raises NotImplementedError: if there is no ancestor module container + on which to add the interface code into. + ''' + try: + return node.scope.symbol_table.lookup_with_tag("psyclone_internal_cmp") + except KeyError: + container = node.ancestor(Container) + if container and not isinstance(container, FileContainer): + # pylint: disable=import-outside-toplevel + from psyclone.psyir.frontend.fortran import FortranReader + name_interface = node.scope.symbol_table.next_available_name( + "psyclone_internal_cmp") + name_f_int = node.scope.symbol_table.next_available_name( + "psyclone_cmp_int") + name_f_logical = node.scope.symbol_table.next_available_name( + "psyclone_cmp_logical") + name_f_char = node.scope.symbol_table.next_available_name( + "psyclone_cmp_char") + fortran_reader = FortranReader() + dummymod = fortran_reader.psyir_from_source(f''' + module dummy + implicit none + interface {name_interface} + procedure {name_f_int} + procedure {name_f_logical} + procedure {name_f_char} + end interface {name_interface} + private {name_interface} + private {name_f_int}, {name_f_logical}, {name_f_char} + contains + logical pure function {name_f_int}(op1, op2) + integer, intent(in) :: op1, op2 + {name_f_int} = op1.eq.op2 + end function + logical pure function {name_f_logical}(op1, op2) + logical, intent(in) :: op1, op2 + {name_f_logical} = op1.eqv.op2 + end function + logical pure function {name_f_char}(op1, op2) + character(*), intent(in) :: op1, op2 + {name_f_char} = op1.eq.op2 + end function + end module dummy + ''').children[0] # We skip the top FileContainer + + # Add the new functions and interface to the ancestor container + container.children.extend(dummymod.pop_all_children()) + container.symbol_table.merge(dummymod.symbol_table) + symbol = container.symbol_table.lookup(name_interface) + # Add the appropriate tag to find it regardless of the name + container.symbol_table.tags_dict['psyclone_internal_cmp'] = symbol + return symbol + + raise NotImplementedError( + "Could not find the generic comparison interface and the scope does " + "not have an ancestor container in which to add it.") + + def _check_args(array, dim): '''Utility routine used by the _check_bound_is_full_extent and _check_array_range_literal functions to check common arguments. @@ -648,7 +732,7 @@ def _kind_find_or_create(name, symbol_table): try: kind_symbol = symbol_table.lookup(lower_name) # pylint: disable=unidiomatic-typecheck - if type(kind_symbol) == Symbol: + if type(kind_symbol) is Symbol: # There is an existing entry but it's only a generic Symbol # so we need to replace it with a DataSymbol of integer type. # Since the lookup() above looks through *all* ancestor symbol @@ -686,7 +770,7 @@ def _kind_find_or_create(name, symbol_table): except KeyError: # The SymbolTable does not contain an entry for this kind parameter # so look to see if it is imported and if not create one. - kind_symbol = _find_or_create_imported_symbol( + kind_symbol = _find_or_create_unresolved_symbol( symbol_table.node, lower_name, symbol_type=DataSymbol, datatype=default_integer_type(), @@ -1341,7 +1425,7 @@ def _process_bound(bound_expr): try: sym = symbol_table.lookup(dim_name) # pylint: disable=unidiomatic-typecheck - if type(sym) == Symbol: + if type(sym) is Symbol: # An entry for this symbol exists but it's only a # generic Symbol and we now know it must be a # DataSymbol. @@ -1720,9 +1804,9 @@ def _process_type_spec(self, parent, type_spec): f"other than 'type' are not yet supported.") type_name = str(walk(type_spec, Fortran2003.Type_Name)[0]) # Do we already have a Symbol for this derived type? - type_symbol = _find_or_create_imported_symbol(parent, type_name) + type_symbol = _find_or_create_unresolved_symbol(parent, type_name) # pylint: disable=unidiomatic-typecheck - if type(type_symbol) == Symbol: + if type(type_symbol) is Symbol: # We do but we didn't know what kind of symbol it was. Create # a DataTypeSymbol to replace it. new_symbol = DataTypeSymbol(type_name, DeferredType(), @@ -1771,8 +1855,6 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): a non-array declaration. :raises InternalError: if an array with defined extent has the \ allocatable attribute. - :raises NotImplementedError: if an initialisation expression is found \ - for a variable declaration. :raises NotImplementedError: if an unsupported initialisation \ expression is found for a parameter declaration. :raises NotImplementedError: if a character-length specification is \ @@ -1876,7 +1958,7 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): interface = DefaultModuleInterface() else: interface = AutomaticInterface() - # This might still be redifined as Argument later if it + # This might still be redefined as Argument later if it # appears in the argument list, but we don't know at this # point. @@ -1884,7 +1966,7 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): # parent symbol table for each entity found. for entity in entities.items: (name, array_spec, char_len, initialisation) = entity.items - ct_expr = None + init_expr = None # If the entity has an array-spec shape, it has priority. # Otherwise use the declaration attribute shape. @@ -1916,19 +1998,12 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): f"extent cannot have the ALLOCATABLE attribute.") if initialisation: - if has_constant_value: - # If it is a parameter parse its initialization into - # a dummy Assignment (but connected to the current scope - # since symbols must be resolved) - dummynode = Assignment(parent=scope) - expr = initialisation.items[1] - self.process_nodes(parent=dummynode, nodes=[expr]) - ct_expr = dummynode.children[0].detach() - else: - raise NotImplementedError( - f"Could not process {decl.items}. Initialisations on " - f"the declaration statements are only supported for " - f"parameter declarations.") + # If the variable or parameter has an initial value then + # parse its initialization into a dummy Assignment. + dummynode = Assignment(parent=scope) + expr = initialisation.items[1] + self.process_nodes(parent=dummynode, nodes=[expr]) + init_expr = dummynode.children[0].detach() if char_len is not None: raise NotImplementedError( @@ -1975,7 +2050,8 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): try: sym = DataSymbol(sym_name, datatype, visibility=visibility, - constant_value=ct_expr) + is_constant=has_constant_value, + initial_value=init_expr) except ValueError: # Error setting initial value have to be raised as # NotImplementedError in order to create an UnknownType @@ -1994,7 +2070,14 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None): # We use copies of the interface object because we will reuse the # interface for each entity if there are multiple in the same # declaration statement. - sym.interface = interface.copy() + if init_expr: + # In Fortran, an initialisation expression on a declaration of + # a symbol (whether in a routine or a module) implies that the + # symbol is static (endures for the lifetime of the program) + # unless it is a pointer initialisation. + sym.interface = StaticInterface() + else: + sym.interface = interface.copy() def _process_derived_type_decln(self, parent, decl, visibility_map): ''' @@ -2108,27 +2191,23 @@ def _get_partial_datatype(self, node, scope, visibility_map): :type visibility_map: dict with str keys and values of type :py:class:`psyclone.psyir.symbols.Symbol.Visibility` - :returns: a PSyIR datatype, or datatype symbol, containing - partial datatype information for the declaration statement - in the cases where it is possible to extract this - information and None otherwise. - :rtype: Optional[:py:class:`psyclone.psyir.symbols.DataType` or - :py:class:`psyclone.psyir.symbols.DataTypeSymbol`] + :returns: a 2-tuple containing a PSyIR datatype, or datatype symbol, + containing partial datatype information for the declaration + statement and the PSyIR for any initialisation expression. + When it is not possible to extract partial datatype information + then (None, None) is returned. + :rtype: Tuple[ + Optional[:py:class:`psyclone.psyir.symbols.DataType` | + :py:class:`psyclone.psyir.symbols.DataTypeSymbol`], + Optional[:py:class:`psyclone.psyir.nodes.Node`]] ''' - # 1: Remove any initialisation and additional variables. TODO: - # This won't be needed when #1419 is implemented (assuming the - # implementation supports both assignments and pointer - # assignments). + # 1: Remove any additional variables. entity_decl_list = node.children[2] orig_entity_decl_list = list(entity_decl_list.children[:]) entity_decl_list.items = tuple(entity_decl_list.children[0:1]) entity_decl = entity_decl_list.children[0] orig_entity_decl_children = list(entity_decl.children[:]) - if isinstance(entity_decl.children[3], Fortran2003.Initialization): - entity_decl.items = ( - entity_decl.items[0], entity_decl.items[1], - entity_decl.items[2], None) # 2: Remove any unsupported attributes unsupported_attribute_names = ["pointer", "target"] @@ -2153,9 +2232,12 @@ def _get_partial_datatype(self, node, scope, visibility_map): visibility_map) symbol_name = node.children[2].children[0].children[0].string symbol_name = symbol_name.lower() - datatype = symbol_table.lookup(symbol_name).datatype + new_sym = symbol_table.lookup(symbol_name) + datatype = new_sym.datatype + init_expr = new_sym.initial_value except NotImplementedError: datatype = None + init_expr = None # Restore the fparser2 parse tree node.items = tuple(orig_node_children) @@ -2164,7 +2246,7 @@ def _get_partial_datatype(self, node, scope, visibility_map): node.children[2].items = tuple(orig_entity_decl_list) node.children[2].children[0].items = tuple(orig_entity_decl_children) - return datatype + return datatype, init_expr def process_declarations(self, parent, nodes, arg_list, visibility_map=None): @@ -2316,7 +2398,7 @@ def process_declarations(self, parent, nodes, arg_list, pass # Try to extract partial datatype information. - datatype = self._get_partial_datatype( + datatype, init = self._get_partial_datatype( node, parent, visibility_map) # If a declaration declares multiple entities, it's @@ -2329,7 +2411,8 @@ def process_declarations(self, parent, nodes, arg_list, str(node), partial_datatype=datatype), interface=UnknownInterface(), - visibility=vis), + visibility=vis, + initial_value=init), tag=tag) except KeyError as err: @@ -2382,7 +2465,10 @@ def process_declarations(self, parent, nodes, arg_list, # Add the initialization expression in the symbol # constant_value attribute ct_expr = dummynode.children[0].detach() - symbol.constant_value = ct_expr + symbol.initial_value = ct_expr + symbol.is_constant = True + # Ensure the interface to this Symbol is static + symbol.interface = StaticInterface() else: # TODO #1254: We currently silently ignore the rest of # the Implicit_Part statements @@ -2407,8 +2493,8 @@ def process_declarations(self, parent, nodes, arg_list, # If a suitable unqualified use statement is found then # this call creates a Symbol and inserts it in the # appropriate symbol table. - _find_or_create_imported_symbol(parent, name, - visibility=vis) + _find_or_create_unresolved_symbol(parent, name, + visibility=vis) except SymbolError as err: # Improve the error message with context-specific info raise SymbolError( @@ -2493,12 +2579,15 @@ def _process_common_blocks(nodes, psyir_parent): :param nodes: fparser2 AST nodes containing declaration statements. :type nodes: List[:py:class:`fparser.two.utils.Base`] - :param psyir_parent: the PSyIR Node with a symbol table in which to \ + :param psyir_parent: the PSyIR Node with a symbol table in which to add the Common Blocks and update the symbols interfaces. :type psyir_parent: :py:class:`psyclone.psyir.nodes.ScopingNode` - :raises NotImplementedError: if it is unable to find one of the \ - CommonBlock expressions in the symbol table (because it has not \ + :raises NotImplementedError: if one of the Symbols in a common block + has initialisation (including when it is a parameter). This is not + valid Fortran. + :raises NotImplementedError: if it is unable to find one of the + CommonBlock expressions in the symbol table (because it has not been declared yet or when it is not just the symbol name). ''' @@ -2523,6 +2612,13 @@ def _process_common_blocks(nodes, psyir_parent): for symbol_name in cb_object[1].items: sym = psyir_parent.symbol_table.lookup( str(symbol_name)) + if sym.initial_value: + # This is C506 of the F2008 standard. + raise NotImplementedError( + f"Symbol '{sym.name}' has an initial value" + f" ({sym.initial_value.debug_string()}) " + f"but appears in a common block. This is " + f"not valid Fortran.") sym.interface = CommonBlockInterface() except KeyError as error: raise NotImplementedError( @@ -2931,7 +3027,7 @@ def _do_construct_handler(self, node, parent): loop_var = str(ctrl[0].items[1][0]) variable_name = str(loop_var) try: - data_symbol = _find_or_create_imported_symbol( + data_symbol = _find_or_create_unresolved_symbol( parent, variable_name, symbol_type=DataSymbol, datatype=DeferredType()) except SymbolError as err: @@ -3317,53 +3413,33 @@ def _process_case_value(self, selector, node, parent): new_parent.addchild(leop) else: # The case value is some scalar expression - bop = BinaryOperation(BinaryOperation.Operator.EQ, - parent=parent) - self.process_nodes(parent=bop, nodes=[selector]) - self.process_nodes(parent=bop, nodes=[node]) - # TODO #1799 when generic child.datatype is supported we can - # remove the conditional inside the loop and support full - # expressions - - # Keep track of whether we know if the operator should be EQ/EQV - operator_known = False - for child in bop.children: - if (isinstance(child, Literal) and - isinstance(child.datatype, ScalarType)): - # We know the operator for all literals - operator_known = True - if (child.datatype.intrinsic == + fake_parent = Assignment(parent=parent) + self.process_nodes(parent=fake_parent, nodes=[selector]) + self.process_nodes(parent=fake_parent, nodes=[node]) + + for operand in fake_parent.lhs, fake_parent.rhs: + # If any of the operands has a datatype we can distinguish + # between boolean (which in Fortran and PSyIR uses the EQV + # operator) or not-boolean (which uses the EQ operator) + if (hasattr(operand, "datatype") and + isinstance(operand.datatype, ScalarType)): + if (operand.datatype.intrinsic == ScalarType.Intrinsic.BOOLEAN): - rhs = bop.children[1].detach() - lhs = bop.children[0].detach() bop = BinaryOperation(BinaryOperation.Operator.EQV, parent=parent) - bop.addchild(lhs) - bop.addchild(rhs) - elif (isinstance(child, Reference) and - isinstance(child.symbol, DataSymbol) and - not isinstance(child.symbol.datatype, - UnknownFortranType)): - # We know the operator for all known reference types. - operator_known = True - if (child.symbol.datatype.intrinsic == - ScalarType.Intrinsic.BOOLEAN): - rhs = bop.children[1].detach() - lhs = bop.children[0].detach() - bop = BinaryOperation(BinaryOperation.Operator.EQV, + else: + bop = BinaryOperation(BinaryOperation.Operator.EQ, parent=parent) - bop.addchild(lhs) - bop.addchild(rhs) - - if operator_known: - parent.addchild(bop) + parent.addchild(bop) + bop.children.extend(fake_parent.pop_all_children()) + break else: - raise NotImplementedError(f"PSyclone can't determine if this " - f"case should be '==' or '.EQV.' " - f"because it can't figure out if " - f"{bop.children[0].debug_string} " - f"or {bop.children[1].debug_string}" - f" are logical expressions.") + # If the loop did not encounter a break, we don't know which + # operator is needed, so we use the generic interface instead + cmp_symbol = _find_or_create_psyclone_internal_cmp(parent) + call = Call(cmp_symbol, parent=parent) + parent.addchild(call) + call.children.extend(fake_parent.pop_all_children()) @staticmethod def _array_notation_rank(node): @@ -3488,7 +3564,7 @@ def _array_syntax_to_indexed(self, parent, loop_vars): range_idx = 0 for idx, child in enumerate(array.indices): if isinstance(child, Range): - symbol = _find_or_create_imported_symbol( + symbol = _find_or_create_unresolved_symbol( array, loop_vars[range_idx], symbol_type=DataSymbol, datatype=DeferredType()) array.children[idx] = Reference(symbol) @@ -3634,7 +3710,7 @@ def _where_construct_handler(self, node, parent): parent=member.parent) else: # The array access is to a symbol of ArrayType - symbol = _find_or_create_imported_symbol( + symbol = _find_or_create_unresolved_symbol( size_node, first_array.name, symbol_type=DataSymbol, datatype=DeferredType()) new_ref = Reference(symbol) @@ -3812,7 +3888,7 @@ def _data_ref_handler(self, node, parent): # that first. if isinstance(node.children[0], Fortran2003.Name): # Base of reference is a scalar entity and must be a DataSymbol. - base_sym = _find_or_create_imported_symbol( + base_sym = _find_or_create_unresolved_symbol( parent, node.children[0].string.lower(), symbol_type=DataSymbol, datatype=DeferredType()) base_indices = [] @@ -3822,7 +3898,7 @@ def _data_ref_handler(self, node, parent): # Base of reference is an array access. Lookup the corresponding # symbol. part_ref = node.children[0] - base_sym = _find_or_create_imported_symbol( + base_sym = _find_or_create_unresolved_symbol( parent, part_ref.children[0].string.lower(), symbol_type=DataSymbol, datatype=DeferredType()) # Processing the array-index expressions requires access to the @@ -4128,7 +4204,7 @@ def _name_handler(self, node, parent): :rtype: :py:class:`psyclone.psyir.nodes.Reference` ''' - symbol = _find_or_create_imported_symbol(parent, node.string) + symbol = _find_or_create_unresolved_symbol(parent, node.string) return Reference(symbol, parent=parent) def _parenthesis_handler(self, node, parent): @@ -4172,7 +4248,7 @@ def _part_ref_handler(self, node, parent): # We can't say for sure that the symbol we create here should be a # DataSymbol as fparser2 often identifies function calls as # part-references instead of function-references. - symbol = _find_or_create_imported_symbol(parent, reference_name) + symbol = _find_or_create_unresolved_symbol(parent, reference_name) if isinstance(symbol, RoutineSymbol): call_or_array = Call(symbol, parent=parent) diff --git a/src/psyclone/psyir/frontend/sympy_reader.py b/src/psyclone/psyir/frontend/sympy_reader.py index 90ae3909b3a..5f9198ada3d 100644 --- a/src/psyclone/psyir/frontend/sympy_reader.py +++ b/src/psyclone/psyir/frontend/sympy_reader.py @@ -124,7 +124,7 @@ def psyir_from_expression(self, sympy_expr, symbol_table): return reader.psyir_from_expression(str(sympy_expr), symbol_table) # ------------------------------------------------------------------------- - # pylint: disable=no-self-argument + # pylint: disable=no-self-argument, too-many-branches def print_fortran_array(function, printer): '''A custom print function to convert a modified Fortran array access back to standard Fortran. This function is set as ``_sympystr_`` method @@ -188,4 +188,29 @@ def print_fortran_array(function, printer): # a(i,j,k) --> a(i:j:k) new_args.append(f"{args[i]}:{args[i+1]}:" f"{args[i+2]}") - return f"{name}({','.join(new_args)})" + + if function._sig is None: + # It's not a user defined type, just create the array access: + return f"{name}({','.join(new_args)})" + + # It is a user defined type. Re-assemble the original call by + # putting the corresponding indices to the individual members, + # based on the information of the stored signature and number + # of array indices for each member: + + result = [] + # This points at the next index to use from new_args, which + # contains the indices converted back into Fortran: + index_cursor = 0 + for i, member in enumerate(function._sig): + # Get the number of indices this member had: + num_dims = function._num_dims[i] + indx = [] + for i in range(num_dims): + indx.append(new_args[index_cursor]) + index_cursor += 1 + if indx: + result.append(f"{member}({','.join(indx)})") + else: + result.append(member) + return "%".join(result) diff --git a/src/psyclone/psyir/nodes/acc_directives.py b/src/psyclone/psyir/nodes/acc_directives.py index 0dfaf911d3f..d589ca469ca 100644 --- a/src/psyclone/psyir/nodes/acc_directives.py +++ b/src/psyclone/psyir/nodes/acc_directives.py @@ -37,6 +37,7 @@ # J. Henrichs, Bureau of Meteorology # Modified A. B. G. Chalk, STFC Daresbury Lab # S. Valat, INRIA / LJK +# J. G. Wallwork, Met Office # ----------------------------------------------------------------------------- ''' This module contains the implementation of the various OpenACC Directive @@ -400,26 +401,33 @@ class ACCLoopDirective(ACCRegionDirective): ''' Class managing the creation of a '!$acc loop' OpenACC directive. - :param int collapse: Number of nested loops to collapse into a single \ + :param int collapse: Number of nested loops to collapse into a single iteration space or None. - :param bool independent: Whether or not to add the `independent` clause \ + :param bool independent: Whether or not to add the `independent` clause to the loop directive. - :param bool sequential: whether or not to add the `seq` clause to the \ + :param bool sequential: whether or not to add the `seq` clause to the loop directive. + :param bool gang: whether or not to add the `gang` clause to the + loop directive. + :param bool vector: whether or not to add the `vector` clause to the + loop directive. :param kwargs: additional keyword arguments provided to the super class. :type kwargs: unwrapped dict. ''' def __init__(self, collapse=None, independent=True, sequential=False, - **kwargs): + gang=False, vector=False, **kwargs): self.collapse = collapse self._independent = independent self._sequential = sequential + self._gang = gang + self._vector = vector super().__init__(**kwargs) def __eq__(self, other): ''' Checks whether two nodes are equal. Two ACCLoopDirective nodes are - equal if their collapse, independent and sequential members are equal. + equal if their collapse, independent, sequential, gang, and vector + members are equal. :param object other: the object to check equality to. @@ -430,6 +438,8 @@ def __eq__(self, other): is_eq = is_eq and self.collapse == other.collapse is_eq = is_eq and self.independent == other.independent is_eq = is_eq and self.sequential == other.sequential + is_eq = is_eq and self.gang == other.gang + is_eq = is_eq and self.vector == other.vector return is_eq @@ -486,6 +496,24 @@ def sequential(self): ''' return self._sequential + @property + def gang(self): + ''' + :returns: whether or not the `gang` clause is added to this loop + directive. + :rtype: bool + ''' + return self._gang + + @property + def vector(self): + ''' + :returns: whether or not the `vector` clause is added to this loop + directive. + :rtype: bool + ''' + return self._vector + def node_str(self, colour=True): ''' Returns the name of this node with (optional) control codes @@ -498,6 +526,8 @@ def node_str(self, colour=True): ''' text = self.coloured_name(colour) text += f"[sequential={self._sequential}," + text += f"gang={self._gang}," + text += f"vector={self._vector}," text += f"collapse={self._collapse}," text += f"independent={self._independent}]" return text @@ -563,6 +593,10 @@ def begin_string(self, leading_acc=True): if self._sequential: clauses += ["seq"] else: + if self._gang: + clauses += ["gang"] + if self._vector: + clauses += ["vector"] if self._independent: clauses += ["independent"] if self._collapse: diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 1c538eaa989..085ae9e5325 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -38,8 +38,10 @@ import re +from psyclone.core import AccessType from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.symbols import RoutineSymbol from psyclone.errors import GenerationError @@ -294,6 +296,45 @@ def _validate_child(position, child): ''' return isinstance(child, DataNode) + def reference_accesses(self, var_accesses): + ''' + Updates the supplied var_accesses object with information on the + arguments passed to this call. + + TODO #446 - all arguments that are passed by reference are currently + marked as having READWRITE access (unless we know that the routine is + PURE). We could do better than this if we have the PSyIR of the called + Routine. + + :param var_accesses: VariablesAccessInfo instance that stores the + information about variable accesses. + :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` + + ''' + if self.is_pure: + # If the called routine is pure then any arguments are only + # read. + default_access = AccessType.READ + else: + # We conservatively default to READWRITE otherwise (TODO #446). + default_access = AccessType.READWRITE + + for arg in self.children: + if isinstance(arg, Reference): + # This argument is pass-by-reference. + sig, indices_list = arg.get_signature_and_indices() + var_accesses.add_access(sig, default_access, arg) + # Any symbols referenced in any index expressions are READ. + for indices in indices_list: + for idx in indices: + idx.reference_accesses(var_accesses) + else: + # This argument is not a Reference so continue to walk down the + # tree. (e.g. it could be/contain a Call to + # an impure routine in which case any arguments to that Call + # will have READWRITE access.) + arg.reference_accesses(var_accesses) + @property def routine(self): ''' diff --git a/src/psyclone/psyir/nodes/loop.py b/src/psyclone/psyir/nodes/loop.py index e14d90fb00d..97b83e9ddb1 100644 --- a/src/psyclone/psyir/nodes/loop.py +++ b/src/psyclone/psyir/nodes/loop.py @@ -111,7 +111,10 @@ def __eq__(self, other): :rtype: bool ''' is_eq = super().__eq__(other) - is_eq = is_eq and self.variable == other.variable + # Similar to Reference equality, it is enough to compare the name + # since if the same-named symbols represent the same is already + # done in their respective scope symbol_table equality check. + is_eq = is_eq and self.variable.name == other.variable.name return is_eq diff --git a/src/psyclone/psyir/nodes/node.py b/src/psyclone/psyir/nodes/node.py index b138f87e4cd..db3d2fdc6bc 100644 --- a/src/psyclone/psyir/nodes/node.py +++ b/src/psyclone/psyir/nodes/node.py @@ -1502,6 +1502,41 @@ def debug_string(self): from psyclone.psyir.backend.debug_writer import DebugWriter return DebugWriter()(self) + def path_from(self, ancestor): + ''' Find the path in the psyir tree between ancestor and node and + returns a list containing the path. + + The result of this method can be used to find the node from its + ancestor for example by: + + >>> index_list = node.path_from(ancestor) + >>> cursor = ancestor + >>> for index in index_list: + >>> cursor = cursor.children[index] + >>> assert cursor is node + + :param ancestor: an ancestor node of self to find the path from. + :type ancestor: :py:class:`psyclone.psyir.nodes.Node` + + :raises ValueError: if ancestor is not an ancestor of self. + + :returns: a list of child indices representing the path between + ancestor and self. + :rtype: List[int] + ''' + result_list = [] + current_node = self + while current_node is not ancestor and current_node.parent is not None: + result_list.append(current_node.position) + current_node = current_node.parent + + if current_node is not ancestor: + raise ValueError(f"Attempted to find path_from a non-ancestor " + f"'{type(ancestor).__name__}' node.") + + result_list.reverse() + return result_list + # For automatic documentation generation # TODO #913 the 'colored' routine shouldn't be in this module. diff --git a/src/psyclone/psyir/nodes/omp_directives.py b/src/psyclone/psyir/nodes/omp_directives.py index 377c36a9fdc..9a409302db0 100644 --- a/src/psyclone/psyir/nodes/omp_directives.py +++ b/src/psyclone/psyir/nodes/omp_directives.py @@ -56,7 +56,7 @@ from psyclone.psyir.nodes.while_loop import WhileLoop from psyclone.psyir.nodes.literal import Literal from psyclone.psyir.nodes.omp_clauses import OMPGrainsizeClause, \ - OMPNowaitClause, OMPNogroupClause, OMPNumTasksClause, OMPPrivateClause,\ + OMPNowaitClause, OMPNogroupClause, OMPNumTasksClause, OMPPrivateClause, \ OMPDefaultClause, OMPReductionClause, OMPScheduleClause, \ OMPFirstprivateClause from psyclone.psyir.nodes.reference import Reference diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 26713325914..505e1b93b21 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -38,8 +38,8 @@ ''' This module contains the DataSymbol and its interfaces.''' -from __future__ import absolute_import from psyclone.psyir.symbols.typed_symbol import TypedSymbol +from psyclone.psyir.symbols.interfaces import StaticInterface class DataSymbol(TypedSymbol): @@ -51,126 +51,196 @@ class DataSymbol(TypedSymbol): :param str name: name of the symbol. :param datatype: data type of the symbol. :type datatype: :py:class:`psyclone.psyir.symbols.DataType` - :param constant_value: sets a fixed known expression as a permanent \ - value for this DataSymbol. If the value is None then this \ - symbol does not have a fixed constant. Otherwise it can receive \ - PSyIR expressions or Python intrinsic types available in the \ - TYPE_MAP_TO_PYTHON map. By default it is None. - :type constant_value: NoneType, item of TYPE_MAP_TO_PYTHON or \ - :py:class:`psyclone.psyir.nodes.Node` - :param kwargs: additional keyword arguments provided by \ + :param bool is_constant: whether this DataSymbol is a compile-time + constant (default is False). If True then an `initial_value` must + also be provided. + :param initial_value: sets a fixed known expression as an initial + value for this DataSymbol. If `is_constant` is True then this + Symbol will always have this value. If the value is None then this + symbol does not have an initial value (and cannot be a constant). + Otherwise it can receive PSyIR expressions or Python intrinsic types + available in the TYPE_MAP_TO_PYTHON map. By default it is None. + :type initial_value: Optional[item of TYPE_MAP_TO_PYTHON | + :py:class:`psyclone.psyir.nodes.Node`] + :param kwargs: additional keyword arguments provided by :py:class:`psyclone.psyir.symbols.TypedSymbol` :type kwargs: unwrapped dict. ''' - def __init__(self, name, datatype, constant_value=None, **kwargs): - super(DataSymbol, self).__init__(name, datatype) - self._constant_value = None - self._process_arguments(constant_value=constant_value, + def __init__(self, name, datatype, is_constant=False, initial_value=None, + **kwargs): + super().__init__(name, datatype) + self._is_constant = False + self._initial_value = None + self._process_arguments(is_constant=is_constant, + initial_value=initial_value, **kwargs) def _process_arguments(self, **kwargs): ''' Process the arguments for the constructor and the specialise - methods. In this case the constant_value argument. + methods. In this case the initial_value and is_constant arguments. :param kwargs: keyword arguments which can be:\n - :param constant_value: sets a fixed known expression as a \ - permanent value for this DataSymbol. If the value is None \ - then this symbol does not have a fixed constant. Otherwise \ - it can receive PSyIR expressions or Python intrinsic types \ - available in the TYPE_MAP_TO_PYTHON map. By default it is \ - set to None. \n - :type constant_value: NoneType, item of TYPE_MAP_TO_PYTHON or \ - :py:class:`psyclone.psyir.nodes.Node`\n + :param bool is_constant: whether this DataSymbol is a compile-time + constant (default is False). If True then an `initial_value` + must also be provided.\n + :param initial_value: sets a fixed known expression as an initial + value for this DataSymbol. If `is_constant` is True then this + Symbol will always have this value. If the value is None then + this symbol does not have an initial value (and cannot be a + constant). Otherwise it can receive PSyIR expressions or Python + intrinsic types available in the TYPE_MAP_TO_PYTHON map. By + default it is None.\n + :type initial_value: Optional[item of TYPE_MAP_TO_PYTHON | + :py:class:`psyclone.psyir.nodes.Node`]\n and the arguments in :py:class:`psyclone.psyir.symbols.TypedSymbol` :type kwargs: unwrapped dict. + :raises ValueError: if the symbol is a run-time constant but is not + given an initial value. + :raises ValueError: if the symbol is a run-time constant and an + interface other than StaticInterface is specified. ''' - new_constant_value = None - if "constant_value" in kwargs: - new_constant_value = kwargs.pop("constant_value") - elif not hasattr(self, '_constant_value'): + new_initial_value = None + new_is_constant_value = None + + # We need to consume 'initial_value' and 'is_constant' before calling + # the super because otherwise there will be an unknown argument in + # kwargs. However, we can only call the 'initial_value' setter after + # the super because it uses self.datatype which in turn is set in + # the super. + + if "initial_value" in kwargs: + new_initial_value = kwargs.pop("initial_value") + elif not hasattr(self, '_initial_value'): + # Initialise this attribute if we reach this point and this object + # doesn't already have it. + self._initial_value = None + + if "is_constant" in kwargs: + new_is_constant_value = kwargs.pop("is_constant") + elif not hasattr(self, '_is_constant'): # At least initialise it if we reach this point and it doesn't # exist - self._constant_value = None + self._is_constant = False + + # Record whether an explicit value has been supplied for 'interface' + interface_supplied = "interface" in kwargs - # We need to consume the 'constant_value' before calling the super - # because otherwise there will be an unknown argument in kwargs but - # we need to call the 'constant_value' setter after this because it - # uses the self.datatype which is in turn set in the super. - super(DataSymbol, self)._process_arguments(**kwargs) + super()._process_arguments(**kwargs) - # Now that we have a datatype we can use the constant_value setter - # with proper error checking - if new_constant_value: - self.constant_value = new_constant_value + # Now that we have a datatype we can use initial_value setter + # with proper error checking. + if new_initial_value is not None: + self.initial_value = new_initial_value + + # Now that we know whether or not we have an intial_value, we can + # call the is_constant setter. + if new_is_constant_value is not None: + self.is_constant = new_is_constant_value + + # A run-time constant must have a StaticInterface or an + # ImportInterface. If the user did not supply an explicit interface + # then default to StaticInterface. If they did supply + # one then we check it is valid. + if self.is_constant: + if interface_supplied: + if not (self.is_static or self.is_import): + raise ValueError( + f"A DataSymbol representing a constant must have " + f"either a StaticInterface or an ImportInterface but " + f"'{self.name}' has interface '{self.interface}'.") + else: + # No explicit interface was supplied and this Symbol represents + # a runtime constant so change its interface to be static. + self.interface = StaticInterface() @property def is_constant(self): ''' - :returns: Whether the symbol is a constant with a fixed known \ - value (True) or not (False). + :returns: Whether the symbol is a compile-time constant (True) or + not (False). :rtype: bool + ''' + return self._is_constant + @is_constant.setter + def is_constant(self, value): ''' - return self._constant_value is not None + :param bool value: whether or not this symbol is a compile-time + constant. + + :raises ValueError: if `value` is True but this symbol does not have an + initial value set and does not have an ImportInterface. + + ''' + if value and not self.is_import and self.initial_value is None: + raise ValueError( + f"DataSymbol '{self.name}' does not have an initial value set " + f"and is not imported and therefore cannot be a constant.") + self._is_constant = value @property - def constant_value(self): + def initial_value(self): ''' - :returns: the fixed known value of this symbol. + :returns: the initial value associated with this symbol (if any). :rtype: :py:class:`psyclone.psyir.nodes.Node` ''' - return self._constant_value + return self._initial_value - @constant_value.setter - def constant_value(self, new_value): + @initial_value.setter + def initial_value(self, new_value): ''' - :param new_value: set or change the fixed known value of the \ - constant for this DataSymbol. If the value is None then this \ - symbol does not have a fixed constant. Otherwise it can receive \ - PSyIR expressions or Python intrinsic types available in the \ - TYPE_MAP_TO_PYTHON map. - :type new_value: NoneType, item of TYPE_MAP_TO_PYTHON or \ - :py:class:`psyclone.psyir.nodes.Node` - - :raises ValueError: if a non-None value is provided and 1) this \ - DataSymbol instance does not have local scope, or 2) this \ - DataSymbol instance is not a scalar (as the shape attribute is \ - not empty), or 3) a constant value is provided but the type of \ - the value does is not supported, or 4) the type of the value \ - provided is not compatible with the datatype of this DataSymbol \ + :param new_value: set or change the initial value associated + with this DataSymbol. If the value is None then this symbol does + not have an initial value (and cannot be a constant). Otherwise it + can receive PSyIR expressions or Python intrinsic types available + in the TYPE_MAP_TO_PYTHON map. + :type new_value: Optional[item of TYPE_MAP_TO_PYTHON | + :py:class:`psyclone.psyir.nodes.Node`] + + :raises ValueError: if a non-None value is provided and 1) this + DataSymbol instance represents an argument, or 2) this + DataSymbol instance is not a scalar (as the shape attribute is + not empty), or 3) an initial value is provided but the type of + the value is not supported, or 4) the type of the value + provided is not compatible with the datatype of this DataSymbol instance, or 5) the provided PSyIR expression is unsupported. + :raises ValueError: if a None value is provided and this DataSymbol + represents a constant and is not imported. ''' # pylint: disable=import-outside-toplevel from psyclone.psyir.nodes import (Node, Literal, Operation, Reference, CodeBlock) - from psyclone.psyir.symbols.datatypes import ScalarType, ArrayType + from psyclone.psyir.symbols.datatypes import (ScalarType, ArrayType, + UnknownType) if new_value is not None: if self.is_argument: raise ValueError( - f"Error setting constant value for symbol '{self.name}'. " - f"A DataSymbol with an ArgumentInterface can not have a " - f"constant value.") - if not isinstance(self.datatype, (ScalarType, ArrayType)): + f"Error setting initial value for symbol '{self.name}'. " + f"A DataSymbol with an ArgumentInterface can not have an " + f"initial value.") + if not isinstance(self.datatype, + (ScalarType, ArrayType, UnknownType)): raise ValueError( - f"Error setting constant value for symbol '{self.name}'. " - f"A DataSymbol with a constant value must be a scalar or " - f"an array but found '{type(self.datatype).__name__}'.") + f"Error setting initial value for symbol '{self.name}'. " + f"A DataSymbol with an initial value must be a scalar or " + f"an array or of UnknownType but found " + f"'{type(self.datatype).__name__}'.") if isinstance(new_value, Node): for node in new_value.walk(Node): if not isinstance(node, (Literal, Operation, Reference, CodeBlock)): raise ValueError( - f"Error setting constant value for symbol " + f"Error setting initial value for symbol " f"'{self.name}'. PSyIR static expressions can only" f" contain PSyIR Literal, Operation, Reference or " f"CodeBlock nodes but found: {node}") - self._constant_value = new_value + self._initial_value = new_value else: from psyclone.psyir.symbols.datatypes import TYPE_MAP_TO_PYTHON # No need to check that self.datatype has an intrinsic @@ -179,30 +249,36 @@ def constant_value(self, new_value): lookup = TYPE_MAP_TO_PYTHON[self.datatype.intrinsic] if not isinstance(new_value, lookup): raise ValueError( - f"Error setting constant value for symbol " + f"Error setting initial value for symbol " f"'{self.name}'. This DataSymbol instance datatype is " - f"'{self.datatype}' meaning the constant value should " + f"'{self.datatype}' meaning the initial value should " f"be '{lookup}' but found '{type(new_value)}'.") if self.datatype.intrinsic == ScalarType.Intrinsic.BOOLEAN: # In this case we know new_value is a Python boolean as it # has passed the isinstance(new_value, lookup) check. if new_value: - self._constant_value = Literal('true', self.datatype) + self._initial_value = Literal('true', self.datatype) else: - self._constant_value = Literal('false', self.datatype) + self._initial_value = Literal('false', self.datatype) else: # Otherwise we convert the Python intrinsic to a PSyIR # Literal using its string representation. - self._constant_value = Literal(str(new_value), - self.datatype) + self._initial_value = Literal(str(new_value), + self.datatype) else: - self._constant_value = None + if self.is_constant and not self.is_import: + raise ValueError( + f"DataSymbol '{self.name}' is a constant and not imported " + f"and therefore must have an initial value but got None") + self._initial_value = None def __str__(self): ret = self.name + ": DataSymbol<" + str(self.datatype) ret += ", " + str(self._interface) + if self.initial_value is not None: + ret += f", initial_value={self.initial_value}" if self.is_constant: - ret += f", constant_value={self.constant_value}" + ret += ", constant=True" return ret + ">" def copy(self): @@ -216,7 +292,8 @@ def copy(self): ''' return DataSymbol(self.name, self.datatype, visibility=self.visibility, interface=self.interface, - constant_value=self.constant_value) + is_constant=self.is_constant, + initial_value=self.initial_value) def copy_properties(self, symbol_in): '''Replace all properties in this object with the properties from @@ -231,5 +308,6 @@ def copy_properties(self, symbol_in): if not isinstance(symbol_in, DataSymbol): raise TypeError(f"Argument should be of type 'DataSymbol' but " f"found '{type(symbol_in).__name__}'.") - super(DataSymbol, self).copy_properties(symbol_in) - self._constant_value = symbol_in.constant_value + super().copy_properties(symbol_in) + self._is_constant = symbol_in.is_constant + self._initial_value = symbol_in.initial_value diff --git a/src/psyclone/psyir/symbols/symbol_table.py b/src/psyclone/psyir/symbols/symbol_table.py index c482d9ad2c2..3af263bcba0 100644 --- a/src/psyclone/psyir/symbols/symbol_table.py +++ b/src/psyclone/psyir/symbols/symbol_table.py @@ -649,7 +649,9 @@ def _add_container_symbols_from_table(self, other_table): other_sym, self.next_available_name( other_sym.name, other_table=other_table)) - isym.interface = ImportInterface(self.lookup(csym.name)) + isym.interface = ImportInterface( + self.lookup(csym.name), + orig_name=isym.interface.orig_name) def _add_symbols_from_table(self, other_table, include_arguments=True): ''' @@ -1005,7 +1007,7 @@ def remove(self, symbol): # pylint: disable=unidiomatic-typecheck if not (isinstance(symbol, (ContainerSymbol, RoutineSymbol)) or - type(symbol) == Symbol): + type(symbol) is Symbol): raise NotImplementedError( f"remove() currently only supports generic Symbol, " f"ContainerSymbol and RoutineSymbol types but got: " @@ -1429,7 +1431,7 @@ def resolve_imports(self, container_symbols=None, symbol_target=None): # otherwise ignore this step. if isinstance(symbol, type(symbol_match)): # pylint: disable=unidiomatic-typecheck - if type(symbol) != type(symbol_match): + if type(symbol) is not type(symbol_match): if isinstance(symbol, TypedSymbol): # All TypedSymbols have a mandatory datatype # argument @@ -1663,7 +1665,7 @@ def __eq__(self, other): :rtype: bool ''' # pylint: disable=unidiomatic-typecheck - if type(self) != type(other): + if type(self) is not type(other): return False this_lines = self.view().split('\n') other_lines = other.view().split('\n') diff --git a/src/psyclone/psyir/tools/dependency_tools.py b/src/psyclone/psyir/tools/dependency_tools.py index 53e6cf9d4e2..761bea47661 100644 --- a/src/psyclone/psyir/tools/dependency_tools.py +++ b/src/psyclone/psyir/tools/dependency_tools.py @@ -383,12 +383,19 @@ def _get_dependency_distance(var_name, index_read, index_written): return None symbol_map = sympy_writer.type_map - # If the subscripts do not even depend on the specified variable, - # any dependency distance is possible (e.g. `do i ... a(j)=a(j)+1`) - if var_name not in symbol_map: + # Find the SymPy symbol that has the same name as the var name. We + # cannot use the dictionary key, since a symbol might be renamed + # (e.g. if a Fortran variable 'lambda' is used, it will be + # renamed to lambda_1, and the type map will have lambda_1 as key + # and the SymPy symbol for 'lambda' as value). + for var in symbol_map.values(): + if str(var) == var_name: + break + else: + # If the subscripts do not even depend on the specified variable, + # any dependency distance is possible (e.g. `do i ... a(j)=a(j)+1`) return None - var = symbol_map[var_name] # Create a unique 'd_x' variable name if 'x' is the variable. d_var_name = "d_"+var_name idx = 1 @@ -423,7 +430,7 @@ def _get_dependency_distance(var_name, index_read, index_written): # evaluated here. We then also need to check if `i+di` (i.e. # the iteration to which the dependency is) is a valid # iteration. E.g. in case of a(i^2)=a(i^2) --> di=0 or di=-2*i - # --> # i+di = -i < 0 for i>0. Since this is not a valid loop + # --> i+di = -i < 0 for i>0. Since this is not a valid loop # iteration that means no dependencies. return None diff --git a/src/psyclone/psyir/transformations/inline_trans.py b/src/psyclone/psyir/transformations/inline_trans.py index cb40bf7b551..3e5a3ec0a4b 100644 --- a/src/psyclone/psyir/transformations/inline_trans.py +++ b/src/psyclone/psyir/transformations/inline_trans.py @@ -41,8 +41,8 @@ from psyclone.psyGen import Transformation from psyclone.psyir.nodes import ( ArrayReference, ArrayOfStructuresReference, BinaryOperation, Call, - CodeBlock, Container, IntrinsicCall, Range, Routine, Reference, Return, - Literal, Assignment, StructureMember, StructureReference) + CodeBlock, Container, IntrinsicCall, Node, Range, Routine, Reference, + Return, Literal, Assignment, StructureMember, StructureReference) from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.symbols import ( ArgumentInterface, ArrayType, DataSymbol, DeferredType, INTEGER_TYPE, @@ -650,7 +650,8 @@ def validate(self, node, options=None): f" '{sym.datatype.declaration}'") # Check that there are no static variables in the routine (because # we don't know whether the routine is called from other places). - if isinstance(sym.interface, StaticInterface): + if (isinstance(sym.interface, StaticInterface) and + not sym.is_constant): raise TransformationError( f"Routine '{routine.name}' cannot be inlined because it " f"has a static (Fortran SAVE) interface for Symbol " @@ -674,16 +675,21 @@ def validate(self, node, options=None): # table. If a precision symbol is only used within Statements then we # don't currently capture the fact that it is a precision symbol. ref_or_lits = routine.walk((Reference, Literal)) - # Check for symbols in any constant-value expressions - # (Fortran parameters) or array dimensions. - for sym in routine_table.automatic_datasymbols: - if sym.is_constant: + # Check for symbols in any initial-value expressions + # (including Fortran parameters) or array dimensions. + for sym in routine_table.datasymbols: + if sym.initial_value: ref_or_lits.extend( - sym.constant_value.walk((Reference, Literal))) + sym.initial_value.walk((Reference, Literal))) if isinstance(sym.datatype, ArrayType): for dim in sym.shape: - ref_or_lits.extend(dim.lower.walk(Reference, Literal)) - ref_or_lits.extend(dim.upper.walk(Reference, Literal)) + if isinstance(dim, ArrayType.ArrayBounds): + if isinstance(dim.lower, Node): + ref_or_lits.extend(dim.lower.walk(Reference, + Literal)) + if isinstance(dim.upper, Node): + ref_or_lits.extend(dim.upper.walk(Reference, + Literal)) # Keep a reference to each Symbol that we check so that we can avoid # repeatedly checking the same Symbol. _symbol_cache = set() diff --git a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py index 59c6bb9f864..1472bc4637c 100644 --- a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py @@ -166,7 +166,7 @@ class Matmul2CodeTrans(Operator2CodeTrans): ''' def __init__(self): - super(Matmul2CodeTrans, self).__init__() + super().__init__() self._operator_name = "MATMUL" self._classes = (BinaryOperation,) self._operators = (BinaryOperation.Operator.MATMUL,) @@ -186,7 +186,7 @@ def validate(self, node, options=None): operation is not an assignment. :raises TransformationError: if the matmul arguments are not in \ the required form. - :raises NotImplementedError: if sub-sections of an array are present \ + :raises TransformationError: if sub-sections of an array are present \ in the arguments. ''' @@ -196,7 +196,7 @@ def validate(self, node, options=None): # pylint: disable=import-outside-toplevel from psyclone.psyir.transformations import TransformationError - super(Matmul2CodeTrans, self).validate(node, options) + super().validate(node, options) # Check the matmul is the only code on the rhs of an assignment # i.e. ... = matmul(a,b) @@ -208,23 +208,23 @@ def validate(self, node, options=None): matrix1 = node.children[0] matrix2 = node.children[1] + result = node.parent.lhs # The children of matvec should be References if not (isinstance(matrix1, Reference) and - isinstance(matrix2, Reference)): + isinstance(matrix2, Reference) and + isinstance(result, Reference)): raise TransformationError( - f"Expected children of a MATMUL BinaryOperation to be " - f"references, but found '{type(matrix1).__name__}', " - f"'{type(matrix2).__name__}'.") + f"Expected result and operands of MATMUL BinaryOperation to " + f"be references, but found: '{node.parent.debug_string()}'.") # The children of matvec should be References to arrays - if not (matrix1.symbol.shape or matrix2.symbol.shape): + if (len(matrix1.symbol.shape) == 0 or len(matrix2.symbol.shape) == 0 or + len(result.symbol.shape) == 0): raise TransformationError( - f"Expected children of a MATMUL BinaryOperation to be " - f"references to arrays but found " - f"'{type(matrix1.symbol).__name__}', " - f"'{type(matrix2.symbol).__name__}' for " - f"'{matrix1.symbol.name}', '{matrix2.symbol.name}'.") + f"Expected result and operands of MATMUL BinaryOperation to " + f"be references to arrays but found " + f"'{result.symbol}', {matrix1.symbol} and {matrix2.symbol}.") # The first child (matrix1) should be declared as an array # with at least 2 dimensions. @@ -256,7 +256,7 @@ def validate(self, node, options=None): # limited to Ranges which specify the full extent of the # dimension. if not (matrix1.is_full_range(0) and matrix1.is_full_range(1)): - raise NotImplementedError( + raise TransformationError( f"To use matmul2code_trans on matmul, the first two " f"indices of the 1st argument '{matrix1.name}' must be " f"full ranges.") @@ -265,7 +265,7 @@ def validate(self, node, options=None): # The 3rd index and onwards must not be ranges. for (count, index) in enumerate(matrix1.children[2:]): if isinstance(index, Range): - raise NotImplementedError( + raise TransformationError( f"To use matmul2code_trans on matmul, only the " f"first two indices of the 1st argument are " f"permitted to be Ranges but found " @@ -291,7 +291,7 @@ def validate(self, node, options=None): # transformation is currently limited to Ranges which # specify the full extent of the dimension. if not matrix2.is_full_range(0): - raise NotImplementedError( + raise TransformationError( f"To use matmul2code_trans on matmul, the first index of " f"the 2nd argument '{matrix2.name}' must be a full range.") # Check that the second dimension is a full range if it is @@ -299,7 +299,7 @@ def validate(self, node, options=None): if (len(matrix2.symbol.shape) > 1 and isinstance(matrix2.children[1], Range) and not matrix2.is_full_range(1)): - raise NotImplementedError( + raise TransformationError( f"To use matmul2code_trans on matmul for a matrix-matrix " f"multiplication, the second index of the 2nd " f"argument '{matrix2.name}' must be a full range.") @@ -307,12 +307,27 @@ def validate(self, node, options=None): # The 3rd index and onwards must not be ranges. for (count, index) in enumerate(matrix2.children[2:]): if isinstance(index, Range): - raise NotImplementedError( + raise TransformationError( f"To use matmul2code_trans on matmul, only the " f"first two indices of the 2nd argument are " f"permitted to be a Range but found " f"{type(index).__name__} at index {count+2}.") + # Make sure the result has as many full range as needed + if result.children: + for idx, child in enumerate(result.children): + if isinstance(child, Range) and not result.is_full_range(idx): + raise TransformationError( + f"To use matmul2code_trans on matmul, each range on " + f"the result variable '{result.name}' must be a full " + f"range but found {result.debug_string()}") + + # Make sure the result is not one of the MATMUL operands + if result.symbol in (matrix1.symbol, matrix2.symbol): + raise TransformationError( + f"'{result.symbol.name}' is the result location and one of " + f"the MATMUL operators. This is not supported.") + def apply(self, node, options=None): '''Apply the MATMUL intrinsic conversion transformation to the specified node. This node must be a MATMUL BinaryOperation. The first @@ -454,18 +469,19 @@ def _apply_matrix_matrix(node): assign = Assignment.create(result_ref.copy(), rhs) # Create ii loop and add the above code as a child # Work out the bounds - lower_bound, upper_bound, step = _get_array_bound(matrix1, 0) + lower_bound, upper_bound, step = _get_array_bound(matrix1, 1) + # Must be the same as _get_array_bound(matrix2, 0) iiloop = Loop.create(ii_loop_sym, lower_bound, upper_bound, step, [assign]) # Create "result(i,j) = 0.0" assign = Assignment.create(result_ref.copy(), Literal("0.0", REAL_TYPE)) # Create i loop and add assignment and ii loop as children. - lower_bound, upper_bound, step = _get_array_bound(matrix2, 0) + lower_bound, upper_bound, step = _get_array_bound(matrix1, 0) iloop = Loop.create(i_loop_sym, lower_bound, upper_bound, step, [assign, iiloop]) # Create j loop and add i loop as child. - lower_bound, upper_bound, step = _get_array_bound(matrix1, 1) + lower_bound, upper_bound, step = _get_array_bound(matrix2, 1) jloop = Loop.create(j_loop_sym, lower_bound, upper_bound, step, [iloop]) # Replace the original assignment with the new loop. diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py index a7b60183bd1..b2b6b0f50e8 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2code_trans.py @@ -222,7 +222,7 @@ def validate(self, node, options=None): # pylint: disable=unidiomatic-typecheck if not (isinstance(array_ref, ArrayReference) or - type(array_ref) == Reference): + type(array_ref) is Reference): raise TransformationError( f"Sum2CodeTrans only support arrays for the first argument, " f"but found '{type(array_ref).__name__}'.") @@ -280,7 +280,7 @@ def apply(self, node, options=None): dimension_literal = dimension_ref elif (isinstance(dimension_ref, Reference) and dimension_ref.symbol.is_constant): - dimension_literal = dimension_ref.symbol.constant_value + dimension_literal = dimension_ref.symbol.initial_value # else exception is handled by the validate method. # Determine the dimension and extent of the array diff --git a/src/psyclone/tests/alggen_test.py b/src/psyclone/tests/alggen_test.py index 89f9b4c2414..f626186f71b 100644 --- a/src/psyclone/tests/alggen_test.py +++ b/src/psyclone/tests/alggen_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # # All rights reserved. # @@ -33,7 +33,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors: R. W. Ford and A. R. Porter, STFC Daresbury Lab -# Modified: I. Kavcic, Met Office +# Modified: I. Kavcic and L. Turner, Met Office ''' Tests for the algorithm generation (re-writing) as implemented in alg_gen.py ''' @@ -162,7 +162,7 @@ def test_single_function_invoke_qr(): "1.1.0_single_invoke_xyoz_qr.f90"), api="dynamo0.3") gen = str(alg).lower() - assert "use testkern_qr" not in gen + assert "use testkern_qr_mod" not in gen # TODO issue #1618 Split test into two as while there are # different implementations we may or may not output a space # before the ':' @@ -192,7 +192,7 @@ def test_single_function_multi_invokes(): gen = str(alg).lower() # Use statements for kernels should have been removed. assert "use testkern_mod" not in gen - assert "use testkern_qr" not in gen + assert "use testkern_qr_mod" not in gen # TODO issue #1618 Split test into two as while there are # different implementations we may or may not output a space # before the ':' and may have multiple only names @@ -217,7 +217,7 @@ def test_named_multi_invokes(): gen = str(alg).lower() # Use statements for kernels should have been removed. assert "use testkern_mod" not in gen - assert "use testkern_qr" not in gen + assert "use testkern_qr_mod" not in gen # TODO issue #1618 Split test into two as while there are # different implementations we may or may not output a space # before the ':' and may have multiple only names. @@ -252,7 +252,7 @@ def test_multi_function_invoke_qr(): BASE_PATH, "1.3_multi_invoke_qr.f90"), api="dynamo0.3") gen = str(alg).lower() # Use statements for kernels should have been removed. - assert "use testkern_qr" not in gen + assert "use testkern_qr_mod" not in gen assert "use testkern_mod" not in gen # TODO issue #1618 Split test into two as while there are # different implementations we may or may not output a space diff --git a/src/psyclone/tests/core/symbolic_maths_test.py b/src/psyclone/tests/core/symbolic_maths_test.py index 38291e7aa3c..f5ea86a6630 100644 --- a/src/psyclone/tests/core/symbolic_maths_test.py +++ b/src/psyclone/tests/core/symbolic_maths_test.py @@ -356,6 +356,8 @@ def test_symbolic_math_functions_with_constants(fortran_reader, expressions): @pytest.mark.parametrize("expressions", [("field(1+i)", "field(i+1)"), + ("lambda", "lambda"), + ("lambda(1+i)", "lambda(i+1)"), ("a%field(b+1)", "a%field(1+b)"), ("a%b%c(a_b+1)", "a%b%c(1+a_b)"), ("a%field(field+1)", @@ -366,14 +368,16 @@ def test_symbolic_math_functions_with_constants(fortran_reader, expressions): def test_symbolic_math_use_reserved_names(fortran_reader, expressions): '''Test that reserved names are handled as expected. The SymPy parser uses 'eval' internally, so if a Fortran variable name should be the - same as a SymPy function (e.g. 'field'), parsing will fail. + same as a SymPy function (e.g. 'field'), parsing will fail. Similarly, + a Python reserved name (like 'lambda') would cause a parsing error. ''' # A dummy program to easily create the PSyIR for the # expressions we need. We just take the RHS of the assignments source = f'''program test_prog use some_mod - integer :: field(10), i + integer :: field(10) + integer :: i, x type(my_mod_type) :: a, b x = {expressions[0]} x = {expressions[1]} @@ -422,6 +426,7 @@ def test_symbolic_math_use_range(fortran_reader, expressions): @pytest.mark.parametrize("expr,expected", [ + ("lambda + 1", "lambda + 1"), ("1.0", "1.0"), ("a", "a"), ("a*b+c", "a * b + c"), diff --git a/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py b/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py index 6eea1ef41d5..4bbf466e1fe 100644 --- a/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py +++ b/src/psyclone/tests/domain/common/transformations/kernel_module_inline_trans_test.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab -# Modified I. Kavcic and A. Coughtrie, Met Office +# Modified I. Kavcic, A. Coughtrie and L. Turner, Met Office # C.M. Maynard, Met Office / University of Reading # Modified J. Henrichs, Bureau of Meteorology # Modified A. B. G. Chalk, STFC Daresbury Lab @@ -278,6 +278,8 @@ def test_module_inline_apply_transformation(tmpdir, fortran_writer): # The new inlined routine must now exist assert kern_call.ancestor(Container).symbol_table.lookup("compute_cv_code") assert kern_call.ancestor(Container).children[1].name == "compute_cv_code" + assert (kern_call.ancestor(Container).symbol_table. + lookup("compute_cv_code").is_modulevar) # We should see it in the output of both: # - the backend @@ -307,14 +309,14 @@ def test_module_inline_apply_transformation(tmpdir, fortran_writer): def test_module_inline_apply_kernel_in_multiple_invokes(tmpdir): ''' Check that module-inline works as expected when the same kernel is provided in different invokes''' - # Use LFRic example with the kernel 'testkern_qr' repeated once in + # Use LFRic example with the kernel 'testkern_qr_mod' repeated once in # the first invoke and 3 times in the second invoke. psy, _ = get_invoke("3.1_multi_functions_multi_invokes.f90", "dynamo0.3", idx=0, dist_mem=False) # By default the kernel is imported once per invoke gen = str(psy.gen) - assert gen.count("USE testkern_qr, ONLY: testkern_qr_code") == 2 + assert gen.count("USE testkern_qr_mod, ONLY: testkern_qr_code") == 2 assert gen.count("END SUBROUTINE testkern_qr_code") == 0 # Module inline kernel in invoke 1 @@ -327,7 +329,7 @@ def test_module_inline_apply_kernel_in_multiple_invokes(tmpdir): # After this, one invoke uses the inlined top-level subroutine # and the other imports it (shadowing the top-level symbol) - assert gen.count("USE testkern_qr, ONLY: testkern_qr_code") == 1 + assert gen.count("USE testkern_qr_mod, ONLY: testkern_qr_code") == 1 assert gen.count("END SUBROUTINE testkern_qr_code") == 1 # Module inline kernel in invoke 2 @@ -338,7 +340,7 @@ def test_module_inline_apply_kernel_in_multiple_invokes(tmpdir): gen = str(psy.gen) # After this, no imports are remaining and both use the same # top-level implementation - assert gen.count("USE testkern_qr, ONLY: testkern_qr_code") == 0 + assert gen.count("USE testkern_qr_mod, ONLY: testkern_qr_code") == 0 assert gen.count("END SUBROUTINE testkern_qr_code") == 1 # And it is valid code @@ -414,6 +416,52 @@ def test_module_inline_apply_bring_in_non_local_symbols( assert "not_needed" not in result assert "not_used" not in result + # Also if they are imports with 'only' and '=>' keywords + psyir = fortran_reader.psyir_from_source(''' + module my_mod + use external_mod1, only: a + use external_mod2, only: b => var1, c => var2 + use not_needed, only: not_used + implicit none + contains + subroutine code() + a = b + c + end subroutine code + end module my_mod + ''') + + routine = psyir.walk(Routine)[0] + inline_trans._prepare_code_to_inline(routine) + result = fortran_writer(routine) + assert "use external_mod1, only : a" in result + assert "use external_mod2, only : b=>var1, c=>var2" in result + assert "not_needed" not in result + assert "not_used" not in result + + # Same but now with some pre-existing module clashes + psyir = fortran_reader.psyir_from_source(''' + module my_mod + use external_mod1, only: a + use external_mod2, only: b => var1, c => var2 + use not_needed, only: not_used + implicit none + contains + subroutine code() + use external_mod1, only : d + use external_mod2, only : var1 + a = b + c + end subroutine code + end module my_mod + ''') + + routine = psyir.walk(Routine)[0] + inline_trans._prepare_code_to_inline(routine) + result = fortran_writer(routine) + assert "use external_mod1, only : a, d" in result + assert "use external_mod2, only : b=>var1, c=>var2, var1" in result + assert "not_needed" not in result + assert "not_used" not in result + # Also, if they are in datatype precision expressions psyir = fortran_reader.psyir_from_source(''' module my_mod diff --git a/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py b/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py index b4e2240b5cf..d3b571c9628 100644 --- a/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py +++ b/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ ''' Tests the KernelImportsToArguments Transformation for the GOcean 1.0 API.''' -from __future__ import absolute_import, print_function import os import pytest from psyclone.parse.algorithm import parse @@ -210,7 +209,8 @@ def test_kernelimportstoargumentstrans_constant(monkeypatch): def create_data_symbol(arg): symbol = DataSymbol(arg.name, INTEGER_TYPE, interface=arg.interface, - constant_value=Literal("1", INTEGER_TYPE)) + is_constant=True, + initial_value=Literal("1", INTEGER_TYPE)) return symbol monkeypatch.setattr(DataSymbol, "resolve_deferred", create_data_symbol) diff --git a/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py b/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py index 1ae320a10c7..b0446423e24 100644 --- a/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py +++ b/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py @@ -551,21 +551,21 @@ def test_psy_init_defaults(kernel_outputdir): otrans.apply(sched) generated_code = str(psy.gen) expected = ''' - SUBROUTINE psy_init() - USE fortcl, ONLY: add_kernels, ocl_env_init - CHARACTER(LEN=30) kernel_names(1) - INTEGER :: ocl_device_num = 1 - LOGICAL, SAVE :: initialised = .FALSE. + subroutine psy_init() + use fortcl, only: add_kernels, ocl_env_init + character(len=30) kernel_names(1) + integer, save :: ocl_device_num = 1 + logical, save :: initialised = .false. - IF (.NOT.initialised) THEN + if (.not.initialised) then initialised = .true. - CALL ocl_env_init(1, ocl_device_num, .false., .false.) + call ocl_env_init(1, ocl_device_num, .false., .false.) kernel_names(1) = 'compute_cu_code' - CALL add_kernels(1, kernel_names) - END IF + call add_kernels(1, kernel_names) + end if - END SUBROUTINE psy_init''' - assert expected in generated_code + end subroutine psy_init''' + assert expected in generated_code.lower() assert GOceanOpenCLBuild(kernel_outputdir).code_compiles(psy) @@ -628,23 +628,23 @@ def test_psy_init_multiple_devices_per_node(kernel_outputdir, monkeypatch): generated_code = str(psy.gen) expected = ''' - SUBROUTINE psy_init() - USE parallel_mod, ONLY: get_rank - USE fortcl, ONLY: add_kernels, ocl_env_init - CHARACTER(LEN=30) kernel_names(1) - INTEGER :: ocl_device_num = 1 - LOGICAL, SAVE :: initialised = .FALSE. - - IF (.NOT.initialised) THEN + subroutine psy_init() + use parallel_mod, only: get_rank + use fortcl, only: add_kernels, ocl_env_init + character(len=30) kernel_names(1) + integer, save :: ocl_device_num = 1 + logical, save :: initialised = .false. + + if (.not.initialised) then initialised = .true. - ocl_device_num = MOD(get_rank() - 1, 2) + 1 - CALL ocl_env_init(1, ocl_device_num, .false., .false.) + ocl_device_num = mod(get_rank() - 1, 2) + 1 + call ocl_env_init(1, ocl_device_num, .false., .false.) kernel_names(1) = 'compute_cu_code' - CALL add_kernels(1, kernel_names) - END IF + call add_kernels(1, kernel_names) + end if - END SUBROUTINE psy_init''' - assert expected in generated_code + end subroutine psy_init''' + assert expected in generated_code.lower() assert GOceanOpenCLBuild(kernel_outputdir).code_compiles(psy) diff --git a/src/psyclone/tests/domain/lfric/algorithm/psyir/lfric_alg_invoke_call_test.py b/src/psyclone/tests/domain/lfric/algorithm/psyir/lfric_alg_invoke_call_test.py index 0f0aec10270..262afba7f93 100644 --- a/src/psyclone/tests/domain/lfric/algorithm/psyir/lfric_alg_invoke_call_test.py +++ b/src/psyclone/tests/domain/lfric/algorithm/psyir/lfric_alg_invoke_call_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2021-2022, Science and Technology Facilities Council +# Copyright (c) 2021-2023, Science and Technology Facilities Council # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -145,3 +145,29 @@ def test_aic_defcontainerrootname(): routine_node = psyir.children[0] name = invoke._def_container_root_name(routine_node) assert name == "alg1_psy" + + +@pytest.mark.parametrize( + "orig_string,new_string,expected_name", + [("", "", "invoke_0_kern"), + ("kern(field1)", "setval_c(field1, 0.0)", "invoke_0")]) +def test_aic_defroutinerootname_single( + orig_string, new_string, expected_name): + '''Check that _def_routine_root_name returns the expected + values. Test for when there is a single user kernel or builtin in + an invoke, as the output will differ. + + ''' + code = ( + "subroutine alg1()\n" + " use kern_mod, only : kern\n" + " use field_mod, only : field_type\n" + " type(field_type) :: field1\n" + " call invoke(kern(field1))\n" + "end subroutine alg1\n") + code = code.replace(orig_string, new_string) + psyir = create_alg_psyir(code) + invoke = psyir.children[0][0] + assert isinstance(invoke, LFRicAlgorithmInvokeCall) + name = invoke._def_routine_root_name() + assert name == expected_name diff --git a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py index cff217d8270..828e6cbc0e9 100644 --- a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py @@ -1499,14 +1499,14 @@ def test_field_utility(): metadata.validate() result = metadata.field_meta_args_on_fs(FieldArgMetadata, "w0") assert len(result) == 1 - assert type(result[0]) == FieldArgMetadata + assert type(result[0]) is FieldArgMetadata result = metadata.field_meta_args_on_fs(FieldArgMetadata, "w1") assert len(result) == 0 result = metadata.field_meta_args_on_fs( [FieldArgMetadata, FieldVectorArgMetadata], "w0") assert len(result) == 2 - assert type(result[0]) == FieldArgMetadata - assert type(result[1]) == FieldVectorArgMetadata + assert type(result[0]) is FieldArgMetadata + assert type(result[1]) is FieldVectorArgMetadata def test_operator_utility(): @@ -1523,14 +1523,14 @@ def test_operator_utility(): result = metadata.operator_meta_args_on_fs( ColumnwiseOperatorArgMetadata, "w0") assert len(result) == 1 - assert type(result[0]) == ColumnwiseOperatorArgMetadata + assert type(result[0]) is ColumnwiseOperatorArgMetadata result = metadata.operator_meta_args_on_fs(OperatorArgMetadata, "w1") assert len(result) == 1 - assert type(result[0]) == OperatorArgMetadata + assert type(result[0]) is OperatorArgMetadata result = metadata.operator_meta_args_on_fs(FieldArgMetadata, "w2") assert len(result) == 0 result = metadata.operator_meta_args_on_fs( [OperatorArgMetadata, ColumnwiseOperatorArgMetadata], "w0") assert len(result) == 2 - assert type(result[0]) == ColumnwiseOperatorArgMetadata - assert type(result[1]) == OperatorArgMetadata + assert type(result[0]) is ColumnwiseOperatorArgMetadata + assert type(result[1]) is OperatorArgMetadata diff --git a/src/psyclone/tests/domain/lfric/lfric_builtins_test.py b/src/psyclone/tests/domain/lfric/lfric_builtins_test.py index 81899bde032..d246c063809 100644 --- a/src/psyclone/tests/domain/lfric/lfric_builtins_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_builtins_test.py @@ -701,15 +701,15 @@ def test_X_plus_Y( assert metadata.name == "X_plus_Y" assert metadata.procedure_name == "X_plus_Y_code" assert len(metadata.meta_args) == 3 - assert type(metadata.meta_args[0]) == FieldArgMetadata + assert type(metadata.meta_args[0]) is FieldArgMetadata assert metadata.meta_args[0].datatype == "gh_real" assert metadata.meta_args[0].access == "gh_write" assert metadata.meta_args[0].function_space == "any_space_1" - assert type(metadata.meta_args[1]) == FieldArgMetadata + assert type(metadata.meta_args[1]) is FieldArgMetadata assert metadata.meta_args[1].datatype == "gh_real" assert metadata.meta_args[1].access == "gh_read" assert metadata.meta_args[1].function_space == "any_space_1" - assert type(metadata.meta_args[2]) == FieldArgMetadata + assert type(metadata.meta_args[2]) is FieldArgMetadata assert metadata.meta_args[2].datatype == "gh_real" assert metadata.meta_args[2].access == "gh_read" assert metadata.meta_args[2].function_space == "any_space_1" diff --git a/src/psyclone/tests/domain/lfric/lfric_loop_bounds_test.py b/src/psyclone/tests/domain/lfric/lfric_loop_bounds_test.py index 50d70fc9b73..4d3deaed7ab 100644 --- a/src/psyclone/tests/domain/lfric/lfric_loop_bounds_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_loop_bounds_test.py @@ -32,15 +32,14 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab -# Modified: L. Turner, Met Office +# Modified: L. Turner and O. Brunt, Met Office ''' This module contains pytest tests for the LFRicLoopBounds collection class. ''' -from __future__ import absolute_import import os -from psyclone.dynamo0p3 import LFRicLoopBounds +from psyclone.domain.lfric import LFRicLoopBounds from psyclone.f2pygen import SubroutineGen, ModuleGen from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory diff --git a/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py b/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py index 3ebd04d9e49..2525f7f1a4e 100644 --- a/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors: R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab; -# I. Kavcic and A. Coughtrie, Met Office; +# I. Kavcic, A. Coughtrie and L. Turner, Met Office; # C. M. Maynard, Met Office/University of Reading; # J. Henrichs, Bureau of Meteorology. @@ -131,7 +131,7 @@ def test_stub_generate_with_scalar_sums_err(): a reduction (since these are not permitted for user-supplied kernels). ''' with pytest.raises(ParseError) as err: _ = generate( - os.path.join(BASE_PATH, "simple_with_reduction.f90"), + os.path.join(BASE_PATH, "testkern_simple_with_reduction_mod.f90"), api=TEST_API) assert ( "A user-supplied LFRic kernel must not write/update a scalar " diff --git a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py index 8e8927ef0db..25b0f150668 100644 --- a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py +++ b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py @@ -7474,7 +7474,8 @@ def test_kern_const_invalid_make_constant2(): # Expecting scalar integer. Set to constant. symbol._datatype = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - symbol._constant_value = 10 + symbol._initial_value = Literal("10", INTEGER_TYPE) + symbol._is_constant = True with pytest.raises(TransformationError) as excinfo: kctrans.apply(kernel, {"element_order": 0}) assert ("Expected entry to be a scalar integer argument but found " diff --git a/src/psyclone/tests/domain/nemo/transformations/acc_update_test.py b/src/psyclone/tests/domain/nemo/transformations/acc_update_test.py index 030f05e5423..37dd05be65e 100644 --- a/src/psyclone/tests/domain/nemo/transformations/acc_update_test.py +++ b/src/psyclone/tests/domain/nemo/transformations/acc_update_test.py @@ -137,7 +137,7 @@ def test_simple_missed_region(parser, fortran_writer): assert (" call dia_ptr_hst(jn, 'ldf', zftv(:,:,:))\n" " !$acc update if_present host(zftv)\n" " zftv(:,:,:) = 1.0d0\n" - " !$acc update if_present device(zftv)\n" in code) + " !$acc update if_present device(jn,zftv)\n" in code) assert (" !$acc update if_present host(jn,zftv)\n" " call" in code) @@ -188,7 +188,7 @@ def test_nested_acc_in_if(parser, fortran_writer): assert (" !$acc update if_present host(zftv)\n" " zftv(:,:,:) = 1.0d0\n" ) in code - assert (" !$acc update if_present device(zftv)\n" + assert (" !$acc update if_present device(jn,zftv)\n" " else\n" ) in code assert (" !$acc update if_present host(jn,zftv)\n" @@ -196,7 +196,7 @@ def test_nested_acc_in_if(parser, fortran_writer): ) in code assert (" !$acc update if_present host(jpi,tmask)\n" " tmask(:,:) = jpi\n" - " !$acc update if_present device(tmask)\n" + " !$acc update if_present device(jn,tmask,zftv)\n" ) in code @@ -226,7 +226,7 @@ def test_call_accesses(fortran_writer): " call dia_ptr_hst(jn, 'ldf', zftv(:,:,:))\n" " !$acc update if_present host(checksum,zftv)\n" " checksum = SUM(zftv)\n" - " !$acc update if_present device(checksum)\n" in code) + " !$acc update if_present device(checksum,jn,zftv)\n" in code) def test_call_within_if(parser, fortran_writer): diff --git a/src/psyclone/tests/dynamo0p3_basis_test.py b/src/psyclone/tests/dynamo0p3_basis_test.py index dd40e0f9e29..98f6589d0d4 100644 --- a/src/psyclone/tests/dynamo0p3_basis_test.py +++ b/src/psyclone/tests/dynamo0p3_basis_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors: R. W. Ford, A. R. Porter and N. Nobre, STFC Daresbury Lab -# Modified: I. Kavcic, Met Office +# Modified: I. Kavcic and L. Turner, Met Office # Modified: J. Henrichs, Bureau of Meteorology ''' Module containing py.test tests for functionality related to @@ -389,7 +389,7 @@ def test_two_qr_same_shape(tmpdir): expected_declns = ( " SUBROUTINE invoke_0(f1, f2, m1, a, m2, istp, g1, g2, n1, b, " "n2, qr, qr2)\n" - " USE testkern_qr, ONLY: testkern_qr_code\n" + " USE testkern_qr_mod, ONLY: testkern_qr_code\n" " USE quadrature_xyoz_mod, ONLY: quadrature_xyoz_type, " "quadrature_xyoz_proxy_type\n" " USE function_space_mod, ONLY: BASIS, DIFF_BASIS\n" @@ -690,7 +690,7 @@ def test_qr_plus_eval(tmpdir): output_decls = ( " SUBROUTINE invoke_0(f0, f1, f2, m1, a, m2, istp, qr)\n" - " USE testkern_qr, ONLY: testkern_qr_code\n" + " USE testkern_qr_mod, ONLY: testkern_qr_code\n" " USE testkern_eval_mod, ONLY: testkern_eval_code\n" " USE quadrature_xyoz_mod, ONLY: quadrature_xyoz_type, " "quadrature_xyoz_proxy_type\n" diff --git a/src/psyclone/tests/dynamo0p3_quadrature_test.py b/src/psyclone/tests/dynamo0p3_quadrature_test.py index 1a648538c01..12308e6ad0e 100644 --- a/src/psyclone/tests/dynamo0p3_quadrature_test.py +++ b/src/psyclone/tests/dynamo0p3_quadrature_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author R. W. Ford and A. R. Porter, STFC Daresbury Lab -# Modified I. Kavcic, Met Office +# Modified I. Kavcic and L. Turner, Met Office # Modified by J. Henrichs, Bureau of Meteorology ''' Module containing py.test tests for functionality related to @@ -90,7 +90,7 @@ def test_field_xyoz(tmpdir): output_decls = ( " SUBROUTINE invoke_0_testkern_qr_type(f1, f2, m1, a, m2, istp," " qr)\n" - " USE testkern_qr, ONLY: testkern_qr_code\n" + " USE testkern_qr_mod, ONLY: testkern_qr_code\n" " USE quadrature_xyoz_mod, ONLY: quadrature_xyoz_type, " "quadrature_xyoz_proxy_type\n" " USE function_space_mod, ONLY: BASIS, DIFF_BASIS\n" @@ -710,7 +710,7 @@ def test_dynkern_setup(monkeypatch): monkeypatch.setattr(kern, "_eval_shapes", value=["gh_wrong_shape"]) # Rather than try and mock-up a DynKernMetadata object, it's easier # to make one properly by parsing the kernel code. - ast = fpapi.parse(os.path.join(BASE_PATH, "testkern_qr.F90"), + ast = fpapi.parse(os.path.join(BASE_PATH, "testkern_qr_mod.F90"), ignore_comments=False) name = "testkern_qr_type" dkm = DynKernMetadata(ast, name=name) diff --git a/src/psyclone/tests/dynamo0p3_stubgen_test.py b/src/psyclone/tests/dynamo0p3_stubgen_test.py index 28451ebfe43..18c8b80743d 100644 --- a/src/psyclone/tests/dynamo0p3_stubgen_test.py +++ b/src/psyclone/tests/dynamo0p3_stubgen_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2022, Science and Technology Facilities Council. +# Copyright (c) 2017-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors R. W. Ford and A. R. Porter, STFC Daresbury Lab -# Modified I. Kavcic, Met Office +# Modified I. Kavcic and L. Turner, Met Office # Modified J. Henrichs, Bureau of Meteorology ''' This module tests the LFRic (Dynamo 0.3) kernel-stub generator using @@ -121,7 +121,7 @@ def test_stub_generate_with_anyw2(): def test_stub_generate_working(): ''' Check that the stub generate produces the expected output ''' - result = generate(os.path.join(BASE_PATH, "simple.f90"), + result = generate(os.path.join(BASE_PATH, "testkern_simple_mod.f90"), api=TEST_API) assert SIMPLE in str(result) @@ -129,7 +129,7 @@ def test_stub_generate_working(): def test_stub_generate_working_noapi(): ''' check that the stub generate produces the expected output when we use the default api (which should be dynamo0.3)''' - result = generate(os.path.join(BASE_PATH, "simple.f90")) + result = generate(os.path.join(BASE_PATH, "testkern_simple_mod.f90")) assert SIMPLE in str(result) diff --git a/src/psyclone/tests/dynamo0p3_test.py b/src/psyclone/tests/dynamo0p3_test.py index e66bfddf156..e7eacab3438 100644 --- a/src/psyclone/tests/dynamo0p3_test.py +++ b/src/psyclone/tests/dynamo0p3_test.py @@ -1162,7 +1162,7 @@ def test_stub_file_content_not_fortran(): def test_stub_file_fortran_invalid(): ''' fail if the fortran in the kernel is not valid ''' with pytest.raises(ParseError) as excinfo: - generate(os.path.join(BASE_PATH, "testkern_invalid_fortran.F90"), + generate(os.path.join(BASE_PATH, "testkern_invalid_fortran_mod.f90"), api=TEST_API) assert 'contain <== no parse pattern found' in str(excinfo.value) @@ -1179,7 +1179,7 @@ def test_file_fortran_not_kernel(): def test_module_name_too_short(): ''' fail if length of kernel module name is too short ''' with pytest.raises(ParseError) as excinfo: - generate(os.path.join(BASE_PATH, "testkern_short_name.F90"), + generate(os.path.join(BASE_PATH, "testkern_short_name_mod.f90"), api=TEST_API) assert "too short to have '_mod' as an extension" in str(excinfo.value) @@ -1195,7 +1195,7 @@ def test_module_name_convention(): def test_kernel_datatype_not_found(): ''' fail if kernel datatype is not found ''' with pytest.raises(ParseError) as excinfo: - generate(os.path.join(BASE_PATH, "testkern_no_datatype.F90"), + generate(os.path.join(BASE_PATH, "testkern_no_datatype_mod.f90"), api=TEST_API) assert 'Kernel type testkern_type does not exist' in str(excinfo.value) diff --git a/src/psyclone/tests/f2pygen_test.py b/src/psyclone/tests/f2pygen_test.py index aed281e6d89..95c32282033 100644 --- a/src/psyclone/tests/f2pygen_test.py +++ b/src/psyclone/tests/f2pygen_test.py @@ -38,7 +38,7 @@ from __future__ import absolute_import, print_function import pytest from psyclone.f2pygen import ModuleGen, CommentGen, SubroutineGen, DoGen, \ - CallGen, AllocateGen, DeallocateGen, IfThenGen, DeclGen, TypeDeclGen,\ + CallGen, AllocateGen, DeallocateGen, IfThenGen, DeclGen, TypeDeclGen, \ CharDeclGen, ImplicitNoneGen, UseGen, DirectiveGen, AssignGen, PSyIRGen from psyclone.errors import InternalError from psyclone.psyir.nodes import Node, Return diff --git a/src/psyclone/tests/generator_test.py b/src/psyclone/tests/generator_test.py index 52933a486d2..f1a2bbbfbf5 100644 --- a/src/psyclone/tests/generator_test.py +++ b/src/psyclone/tests/generator_test.py @@ -45,8 +45,10 @@ import os import re +import shutil import stat from sys import modules + import pytest from fparser.common.readfortran import FortranStringReader @@ -365,8 +367,9 @@ def test_script_file_too_short(): _, _ = generate(os.path.join(BASE_PATH, "dynamo0p3", "1_single_invoke.f90"), api="dynamo0.3", - script_name=os.path.join(BASE_PATH, - "dynamo0p3", "xyz")) + script_name=os.path.join( + BASE_PATH, + "dynamo0p3", "testkern_xyz_mod.f90")) def test_no_script_gocean(): @@ -1274,3 +1277,92 @@ def test_no_invokes_lfric_new(monkeypatch): api="dynamo0.3") assert ("Algorithm file contains no invoke() calls: refusing to generate " "empty PSy code" in str(info.value)) + + +def test_generate_unknown_container_lfric(tmpdir, monkeypatch): + '''Test that a GenerationError exception in the generate function is + raised for the LFRic DSL if one of the functors is not explicitly + declared. This can happen in LFRic algorithm code as it is never + compiled. The exception is only raised with the new PSyIR approach + to modify the algorithm layer which is currently in development so + is protected by a switch. This switch is turned on in this test by + monkeypatching. + + At the moment this exception is only raised if the functor is + declared in a different subroutine or function, as the original + parsing approach picks up all other cases. However, the original + parsing approach will eventually be removed. + + ''' + monkeypatch.setattr(generator, "LFRIC_TESTING", True) + code = ( + "module some_kernel_mod\n" + "use module_mod, only : module_type\n" + "contains\n" + "subroutine dummy_kernel()\n" + " use testkern_mod, only: testkern_type\n" + "end subroutine dummy_kernel\n" + "subroutine some_kernel()\n" + " use constants_mod, only: r_def\n" + " use field_mod, only : field_type\n" + " type(field_type) :: field1, field2, field3, field4\n" + " real(kind=r_def) :: scalar\n" + " call invoke(testkern_type(scalar, field1, field2, field3, " + "field4))\n" + "end subroutine some_kernel\n" + "end module some_kernel_mod\n") + alg_filename = str(tmpdir.join("alg.f90")) + with open(alg_filename, "w", encoding='utf-8') as my_file: + my_file.write(code) + kern_filename = os.path.join(DYN03_BASE_PATH, "testkern_mod.F90") + shutil.copyfile(kern_filename, str(tmpdir.join("testkern_mod.F90"))) + with pytest.raises(GenerationError) as info: + _, _ = generate(alg_filename) + assert ("Kernel functor 'testkern_type' in routine 'some_kernel' from " + "algorithm file '" in str(info.value)) + assert ("alg.f90' must be named in a use statement (found [" + "'constants_mod', 'field_mod', '_psyclone_builtins', " + "'module_mod']) or be a recognised built-in (one of " + "['x_plus_y', 'inc_x_plus_y'," in str(info.value)) + + +def test_generate_unknown_container_gocean(tmpdir): + '''Test that a GenerationError exception in the generate function is + raised for the GOcean DSL if one of the functors is not explicitly + declared. This can happen in GOcean algorithm code as it is never + compiled. + + At the moment this exception is only raised if the functor is + declared in a different subroutine or function, as the original + parsing approach picks up all other cases. However, the original + parsing approach will eventually be removed. + + ''' + code = ( + "module some_kernel_mod\n" + "use module_mod, only : module_type\n" + "contains\n" + "subroutine dummy_kernel()\n" + " use compute_cu_mod, only: compute_cu\n" + "end subroutine dummy_kernel\n" + "subroutine some_kernel()\n" + " use kind_params_mod\n" + " use grid_mod, only: grid_type\n" + " use field_mod, only: r2d_field\n" + " type(grid_type), target :: model_grid\n" + " type(r2d_field) :: p_fld, u_fld, cu_fld\n" + " call invoke( compute_cu(cu_fld, p_fld, u_fld) )\n" + "end subroutine some_kernel\n" + "end module some_kernel_mod\n") + alg_filename = str(tmpdir.join("alg.f90")) + with open(alg_filename, "w", encoding='utf-8') as my_file: + my_file.write(code) + kern_filename = os.path.join(GOCEAN_BASE_PATH, "compute_cu_mod.f90") + shutil.copyfile(kern_filename, str(tmpdir.join("compute_cu_mod.f90"))) + with pytest.raises(GenerationError) as info: + _, _ = generate(alg_filename, api="gocean1.0") + assert ("Kernel functor 'compute_cu' in routine 'some_kernel' from " + "algorithm file '" in str(info.value)) + assert ("alg.f90' must be named in a use statement (found " + "['kind_params_mod', 'grid_mod', 'field_mod', 'module_mod'])." + in str(info.value)) diff --git a/src/psyclone/tests/line_length_test.py b/src/psyclone/tests/line_length_test.py index 896c8cbaa80..70371b95ea5 100644 --- a/src/psyclone/tests/line_length_test.py +++ b/src/psyclone/tests/line_length_test.py @@ -1,19 +1,48 @@ # ----------------------------------------------------------------------------- -# (c) The copyright relating to this work is owned jointly by the Crown, -# Met Office and NERC 2015. -# However, it has been created with the help of the GungHo Consortium, -# whose members are identified at https://puma.nerc.ac.uk/trac/GungHo/wiki +# BSD 3-Clause License +# +# Copyright (c) 2017-2023, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author R. Ford STFC Daresbury Lab +# Modified by A. B. G. Chalk, STFC Daresbury Lab +# ----------------------------------------------------------------------------- ''' This module tests the line_limit module using pytest. ''' # imports -from __future__ import absolute_import, print_function import os import pytest -from psyclone.line_length import FortLineLength +from psyclone.line_length import FortLineLength, find_break_point from psyclone.generator import generate +from psyclone.errors import InternalError # functions @@ -330,6 +359,56 @@ def test_edge_conditions_comments(): assert output_string == expected_output +def test_long_lines_allocate(fortran_reader, fortran_writer): + '''Test the we get the correct behaviour and never break the + line before the first statement.''' + code = '''subroutine test() + use external_module, only: some_type + integer, dimension(:,:,:,:), allocatable :: pressure_prsc + IF (.NOT. ALLOCATED(pressure_prsc)) & + ALLOCATE(pressure_prsc ( some_type%longstringname1, & + some_type%longstringname2, & + some_type%longstringname3, & + some_type%longstringname4 )) + end subroutine test + ''' + + psyir = fortran_reader.psyir_from_source(code) + output = fortran_writer(psyir) + line_length = FortLineLength() + correct = ''' if (.NOT.ALLOCATED(pressure_prsc)) then + ALLOCATE(pressure_prsc(1:some_type%longstringname1,1:some_type%\ +longstringname2,1:some_type%longstringname3,& +&1:some_type%longstringname4))''' + out = line_length.process(output) + assert correct in out + + +def test_long_lines_indentation(): + '''Test that if we have too much initial indentation that we still + can output a result.''' + long_string = ''' \ + \ + allocate(pressure_prsc(some_type%longstringname1, \ +some_type%longstringname2))''' + line_length = FortLineLength() + out = line_length.process(long_string) + correct = '''allocate(pressure_prsc(some_type%longstringname1, \ +some_type%longstringname2))''' + assert correct == out + + long_string = ''' \ + \ + allocate(pressure_prsc(some_type%longstringname1, \ +some_type%longstringname2, some_type%longstringname3, \ +some_type%longerstringname4))''' + out = line_length.process(long_string) + correct = '''allocate(pressure_prsc(some_type%longstringname1, \ +some_type%longstringname2, some_type%longstringname3, & +&some_type%longerstringname4))''' + assert correct == out + + def test_long_lines_true(): ''' Tests that the long_lines method returns true with fortran input which has at least one line longer than the specified @@ -339,7 +418,7 @@ def test_long_lines_true(): "! " + "line2"*6 + "\n" "! line3\n") fll = FortLineLength(line_length=30) - assert fll.long_lines(input_string),\ + assert fll.long_lines(input_string), \ "long_lines_true test should return True" @@ -351,7 +430,7 @@ def test_long_lines_false(): "! " + "line2"*5 + "\n" + "! line3\n") fll = FortLineLength(line_length=30) - assert not fll.long_lines(input_string),\ + assert not fll.long_lines(input_string), \ "long_lines_false test should return False" @@ -360,7 +439,7 @@ def test_length(): input_length = 20 fll = FortLineLength(line_length=input_length) output_length = fll.length - assert output_length == input_length,\ + assert output_length == input_length, \ "test_length expecting length method to be the same as the length" +\ "provided on input" @@ -379,3 +458,30 @@ def test_long_line_continuator(): input_string = str(alg) fll = FortLineLength() _ = fll.process(input_string) + + +@pytest.mark.parametrize("line,max_index,key_list,index", [ + ("allocate(x, y, z)", 17, [",", ")"], 14), + ("allocate(x, y, z)", 17, ["y,", ")"], 14), + ("allocate(x, y, z)", 17, ["allocate", ","], 14), + ("x = z + c * y - x + 3", 22, ["x"], 17), + ("x = max(35, 16) - 19 + 3", 21, ["+", "-"], 17) + ]) +def test_find_break_point(line, max_index, key_list, index): + '''Tests the find_break_point routine correctly gives correct + line break point.''' + assert find_break_point(line, max_index, key_list) == index + + +def test_find_break_point_exception(): + '''Test the fail case of find_break_point raises the exception + and expected error message.''' + line = "allocate(x, y, z)" + key_list = ["+"] + max_index = 17 + + with pytest.raises(InternalError) as excinfo: + find_break_point(line, max_index, key_list) + + assert ("Error in find_break_point. No suitable break point found for line" + " 'allocate(x, y, z)' and keys '['+']'" in str(excinfo.value)) diff --git a/src/psyclone/tests/nemo/transformations/openacc/loop_directive_test.py b/src/psyclone/tests/nemo/transformations/openacc/loop_directive_test.py index 30ea0cbcfd5..49d236ce4f5 100644 --- a/src/psyclone/tests/nemo/transformations/openacc/loop_directive_test.py +++ b/src/psyclone/tests/nemo/transformations/openacc/loop_directive_test.py @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors: R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified: J. G. Wallwork, Met Office '''Module containing py.test tests for the transformation of the PSy representation of NEMO code using the OpenACC loop directive. @@ -165,6 +166,28 @@ def test_seq_loop(parser): " do ji = 1, jpj, 1\n" in code) +@pytest.mark.parametrize("clause", ["gang", "vector"]) +def test_loop_clauses(parser, clause): + ''' Check that we can apply the transformation with different + clauses for independent loops. ''' + reader = FortranStringReader(SINGLE_LOOP) + code = parser(reader) + psy = PSyFactory(API, distributed_memory=False).create(code) + schedule = psy.invokes.invoke_list[0].schedule + acc_trans = TransInfo().get_trans_name('ACCLoopTrans') + # An ACC Loop must be within a KERNELS or PARALLEL region + kernels_trans = TransInfo().get_trans_name('ACCKernelsTrans') + kernels_trans.apply(schedule.children) + loops = schedule[0].walk(Loop) + acc_trans.apply(loops[0], {clause: True}) + code = str(psy.gen).lower() + assert (" real(kind=wp), dimension(jpj) :: sto_tmp\n" + "\n" + " !$acc kernels\n" + f" !$acc loop {clause} independent\n" + " do ji = 1, jpj, 1\n" in code) + + def test_collapse(parser): ''' Check that we can apply the loop transformation with the 'collapse' clause. ''' diff --git a/src/psyclone/tests/parse/kernel_test.py b/src/psyclone/tests/parse/kernel_test.py index 73df9b7723f..f379eb389da 100644 --- a/src/psyclone/tests/parse/kernel_test.py +++ b/src/psyclone/tests/parse/kernel_test.py @@ -48,7 +48,7 @@ from psyclone.domain.lfric.lfric_builtins import BUILTIN_MAP as builtins from psyclone.domain.lfric.lfric_builtins import \ BUILTIN_DEFINITIONS_FILE as fname -from psyclone.parse.kernel import KernelType, get_kernel_metadata,\ +from psyclone.parse.kernel import KernelType, get_kernel_metadata, \ get_kernel_interface, KernelProcedure, Descriptor, \ BuiltInKernelTypeFactory, get_kernel_filepath, get_kernel_ast from psyclone.parse.utils import ParseError diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index dfb1a7819af..6dc4876ff9e 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -32,7 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Authors: R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab -# Modified I. Kavcic, Met Office +# Modified I. Kavcic and L. Turner, Met Office # ----------------------------------------------------------------------------- ''' Performs py.test tests on the psyGen module ''' @@ -547,7 +547,7 @@ def test_codedkern_module_inline_gen_code(tmpdir): def test_codedkern_module_inline_kernel_in_multiple_invokes(tmpdir): ''' Check that module-inline works as expected when the same kernel is provided in different invokes''' - # Use LFRic example with the kernel 'testkern_qr' repeated once in + # Use LFRic example with the kernel 'testkern_qr_mod' repeated once in # the first invoke and 3 times in the second invoke. _, invoke_info = parse( os.path.join(BASE_PATH, "3.1_multi_functions_multi_invokes.f90"), @@ -556,7 +556,7 @@ def test_codedkern_module_inline_kernel_in_multiple_invokes(tmpdir): # By default the kernel is imported once per invoke gen = str(psy.gen) - assert gen.count("USE testkern_qr, ONLY: testkern_qr_code") == 2 + assert gen.count("USE testkern_qr_mod, ONLY: testkern_qr_code") == 2 # Module inline kernel in invoke 1 schedule = psy.invokes.invoke_list[0].schedule @@ -570,7 +570,7 @@ def test_codedkern_module_inline_kernel_in_multiple_invokes(tmpdir): # After this, one invoke uses the inlined top-level subroutine # and the other imports it (shadowing the top-level symbol) - assert gen.count("USE testkern_qr, ONLY: testkern_qr_code") == 1 + assert gen.count("USE testkern_qr_mod, ONLY: testkern_qr_code") == 1 assert LFRicBuild(tmpdir).code_compiles(psy) # Module inline kernel in invoke 2 @@ -581,7 +581,7 @@ def test_codedkern_module_inline_kernel_in_multiple_invokes(tmpdir): gen = str(psy.gen) # After this, no imports are remaining and both use the same # top-level implementation - assert gen.count("USE testkern_qr, ONLY: testkern_qr_code") == 0 + assert gen.count("USE testkern_qr_mod, ONLY: testkern_qr_code") == 0 assert LFRicBuild(tmpdir).code_compiles(psy) diff --git a/src/psyclone/tests/psyad/transformations/test_assignment_trans.py b/src/psyclone/tests/psyad/transformations/test_assignment_trans.py index ea169ac99f1..a9dd5546a26 100644 --- a/src/psyclone/tests/psyad/transformations/test_assignment_trans.py +++ b/src/psyclone/tests/psyad/transformations/test_assignment_trans.py @@ -952,7 +952,8 @@ def test_validate_rhs_zero(): assignment = Assignment.create(Reference(lhs_symbol), rhs_literal) trans.validate(assignment) # 0 with a kind value - real_kind = DataSymbol("r_def", INTEGER_TYPE, constant_value=8) + real_kind = DataSymbol("r_def", INTEGER_TYPE, is_constant=True, + initial_value=8) scalar_type = ScalarType(ScalarType.Intrinsic.REAL, real_kind) rhs_literal = Literal("0.0", scalar_type) assignment = Assignment.create(Reference(lhs_symbol), rhs_literal) diff --git a/src/psyclone/tests/psyad/transformations/test_preprocess.py b/src/psyclone/tests/psyad/transformations/test_preprocess.py index bb2bb9aeda8..8ed748e0dfa 100644 --- a/src/psyclone/tests/psyad/transformations/test_preprocess.py +++ b/src/psyclone/tests/psyad/transformations/test_preprocess.py @@ -40,8 +40,7 @@ ''' import pytest -from psyclone.psyad.transformations.preprocess import ( - associativity, preprocess_trans) +from psyclone.psyad.transformations.preprocess import preprocess_trans from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.frontend.fortran import FortranReader from psyclone.tests.utilities import Compile @@ -224,60 +223,33 @@ def test_preprocess_arrayrange2loop(tmpdir, fortran_reader, fortran_writer): assert Compile(tmpdir).string_compiles(result) -def test_preprocess_associativity(fortran_reader, fortran_writer): - '''Test that the associativity function is called from the preprocess - script. - - ''' - code = ( - "program test\n" - " integer :: a, b, c, d\n" - " a = b * (c + d)\n" - "end program test\n") - expected = ( - "program test\n" - " integer :: a\n" - " integer :: b\n" - " integer :: c\n" - " integer :: d\n\n" - " a = b * c + b * d\n\n" - "end program test\n") - psyir = fortran_reader.psyir_from_source(code) - preprocess_trans(psyir, ["a", "c", "d"]) - result = fortran_writer(psyir) - assert result == expected - - @pytest.mark.parametrize("operation", ["+", "-"]) -def test_associativity1(operation, fortran_reader, fortran_writer): - '''Test that the associativity function works as expected for x*(a+-b) - where a and b are active and x is inactive. +def test_preprocess_associativity(operation, fortran_reader, fortran_writer): + '''Test that associativity is handled correctly. ''' code = ( f"program test\n" - f" integer :: a, b, c, d, e, f\n" - f" a = (b*e/f) * (c {operation} d)\n" + f" integer :: a, b, c, d\n" + f" a = b * (c {operation} d)\n" f"end program test\n") expected = ( f"program test\n" f" integer :: a\n" f" integer :: b\n" f" integer :: c\n" - f" integer :: d\n" - f" integer :: e\n" - f" integer :: f\n\n" - f" a = b * e / f * c {operation} b * e / f * d\n\n" + f" integer :: d\n\n" + f" a = b * c {operation} b * d\n\n" f"end program test\n") psyir = fortran_reader.psyir_from_source(code) - associativity(psyir.children[0][0], ["a", "c", "d"]) + preprocess_trans(psyir, ["a", "c", "d"]) result = fortran_writer(psyir) assert result == expected @pytest.mark.parametrize("operation", ["*", "/"]) def test_associativity2(operation, fortran_reader, fortran_writer): - '''Test that the associativity function works as expected for (a+b)*/x + '''Test that associativity works as expected for (a+b)*/x where a and b are active and x is inactive. ''' @@ -286,6 +258,10 @@ def test_associativity2(operation, fortran_reader, fortran_writer): f" integer :: a, b, c, d, e, f\n" f" a = (c + d) {operation} (b*e/f)\n" f"end program test\n") + if operation == "*": + expr = "b * c * e / f + b * d * e / f" + else: + expr = "c * f / (b * e) + d * f / (b * e)" expected = ( f"program test\n" f" integer :: a\n" @@ -294,17 +270,17 @@ def test_associativity2(operation, fortran_reader, fortran_writer): f" integer :: d\n" f" integer :: e\n" f" integer :: f\n\n" - f" a = c {operation} (b * e / f) + d {operation} (b * e / f)\n\n" + f" a = {expr}\n\n" f"end program test\n") psyir = fortran_reader.psyir_from_source(code) - associativity(psyir.children[0][0], ["a", "c", "d"]) + preprocess_trans(psyir, ["a", "c", "d"]) result = fortran_writer(psyir) assert result == expected def test_associativity3(fortran_reader, fortran_writer): - '''Test that the associativity function works as expected when the - function needs to be applied multiple times in an assignment. + '''Test that associativity works as expected when it + needs to be applied multiple times in an assignment. ''' code = ( @@ -321,16 +297,16 @@ def test_associativity3(fortran_reader, fortran_writer): " integer :: e\n" " integer :: f\n" " integer :: g\n\n" - " a = b * c / e + (b * (f * d) / e + b * (f * g) / e)\n\n" + " a = b * c / e + b * d * f / e + b * f * g / e\n\n" "end program test\n") psyir = fortran_reader.psyir_from_source(code) - associativity(psyir.children[0][0], ["d", "c", "g"]) + preprocess_trans(psyir.children[0][0], ["d", "c", "g"]) result = fortran_writer(psyir) assert result == expected def test_preprocess_associativity4(fortran_reader, fortran_writer): - '''Test that the associativity function works as expected when we have + '''Test that associativity works as expected when we have array ranges. ''' @@ -354,7 +330,7 @@ def test_preprocess_associativity4(fortran_reader, fortran_writer): def test_associativity5(tmpdir, fortran_reader, fortran_writer): - '''Test that the associativity function works as expected when we have + '''Test that associativity works as expected when we have a literal as part of the expression that we would like to expand. @@ -379,9 +355,8 @@ def test_associativity5(tmpdir, fortran_reader, fortran_writer): def test_associativity6(tmpdir, fortran_reader, fortran_writer): - '''Test that the associativity function works as expected when we have - a negative literal as part of the expression that we would like to - expand. + '''Test that associativity works as expected when we have a negative + literal as part of the expression that we would like to expand. ''' code = ( @@ -404,7 +379,7 @@ def test_associativity6(tmpdir, fortran_reader, fortran_writer): def test_associativity7(fortran_reader, fortran_writer): - '''Test that the associativity function works as expected when we have + '''Test that associativity works as expected when we have user-defined types. ''' code = ( diff --git a/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py b/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py index 6cabf29a463..875f554be04 100644 --- a/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py @@ -52,12 +52,12 @@ def test_gen_param_decls_dependencies(fortran_writer): ''' Test that dependencies between parameter declarations are handled. ''' symbol_table = SymbolTable() - rlg_sym = DataSymbol("rlg", INTEGER_TYPE, - constant_value=Literal("8", INTEGER_TYPE)) - wp_sym = DataSymbol("wp", INTEGER_TYPE, - constant_value=Reference(rlg_sym)) - var_sym = DataSymbol("var", INTEGER_TYPE, - constant_value=BinaryOperation.create( + rlg_sym = DataSymbol("rlg", INTEGER_TYPE, is_constant=True, + initial_value=Literal("8", INTEGER_TYPE)) + wp_sym = DataSymbol("wp", INTEGER_TYPE, is_constant=True, + initial_value=Reference(rlg_sym)) + var_sym = DataSymbol("var", INTEGER_TYPE, is_constant=True, + initial_value=BinaryOperation.create( BinaryOperation.Operator.ADD, Reference(rlg_sym), Reference(wp_sym))) symbol_table.add(var_sym) @@ -70,8 +70,8 @@ def test_gen_param_decls_dependencies(fortran_writer): # Check that an (invalid, obviously) circular dependency is handled. # Replace "rlg" with a new one that depends on "wp". del symbol_table._symbols[rlg_sym.name] - rlg_sym = DataSymbol("rlg", INTEGER_TYPE, - constant_value=Reference(wp_sym)) + rlg_sym = DataSymbol("rlg", INTEGER_TYPE, is_constant=True, + initial_value=Reference(wp_sym)) symbol_table.add(rlg_sym) with pytest.raises(VisitorError) as err: fortran_writer._gen_parameter_decls(symbol_table) @@ -99,15 +99,15 @@ def test_gen_param_decls_kind_dep(fortran_writer): ''' Check that symbols defining precision are accounted for when allowing for dependencies between parameter declarations. ''' table = SymbolTable() - rdef_sym = DataSymbol("r_def", INTEGER_TYPE, - constant_value=Literal("4", INTEGER_TYPE)) - wp_sym = DataSymbol("wp", INTEGER_TYPE, - constant_value=Reference(rdef_sym)) + rdef_sym = DataSymbol("r_def", INTEGER_TYPE, is_constant=True, + initial_value=Literal("4", INTEGER_TYPE)) + wp_sym = DataSymbol("wp", INTEGER_TYPE, is_constant=True, + initial_value=Reference(rdef_sym)) rdef_type = ScalarType(ScalarType.Intrinsic.REAL, wp_sym) - var_sym = DataSymbol("var", rdef_type, - constant_value=Literal("1.0", rdef_type)) - var2_sym = DataSymbol("var2", REAL_TYPE, - constant_value=Literal("1.0", rdef_type)) + var_sym = DataSymbol("var", rdef_type, is_constant=True, + initial_value=Literal("1.0", rdef_type)) + var2_sym = DataSymbol("var2", REAL_TYPE, is_constant=True, + initial_value=Literal("1.0", rdef_type)) table.add(var2_sym) table.add(var_sym) table.add(wp_sym) @@ -144,8 +144,8 @@ def test_gen_decls(fortran_writer): symbol_table.add(grid_type) grid_variable = DataSymbol("grid", grid_type) symbol_table.add(grid_variable) - symbol_table.add(DataSymbol("rlg", INTEGER_TYPE, - constant_value=Literal("8", INTEGER_TYPE))) + symbol_table.add(DataSymbol("rlg", INTEGER_TYPE, is_constant=True, + initial_value=Literal("8", INTEGER_TYPE))) result = fortran_writer.gen_decls(symbol_table) # If derived type declaration is not inside a module then its components # cannot have accessibility attributes. @@ -277,5 +277,37 @@ def test_gen_decls_static_variables(fortran_writer): symbol_table.add(sym) assert "integer, save :: v1" in fortran_writer.gen_decls(symbol_table) assert "integer, save :: v1" in fortran_writer.gen_vardecl(sym) - sym.constant_value = 1 + sym.initial_value = 1 + sym.is_constant = True assert "parameter :: v1 = 1" in fortran_writer.gen_vardecl(sym) + + +@pytest.mark.parametrize("visibility", ["public", "private"]) +def test_visibility_interface(fortran_reader, fortran_writer, visibility): + '''Test that PSyclone's Fortran backend successfully writes out + public/private clauses and symbols when the symbol's declaration + is hidden in an abstract interface. + + ''' + code = ( + f"module test\n" + f" abstract interface\n" + f" subroutine update_interface()\n" + f" end subroutine update_interface\n" + f" end interface\n" + f" {visibility} :: update_interface\n" + f"contains\n" + f" subroutine alg()\n" + f" end subroutine alg\n" + f"end module test\n") + psyir = fortran_reader.psyir_from_source(code) + result = fortran_writer(psyir) + # The default visibility is PUBLIC so it is always output by + # the backend. + assert "public\n" in result + if visibility == "public": + # The generic PUBLIC visibility covers all symbols so we do + # not need to output "public :: update_interface". + assert "public :: update_interface" not in result + if visibility == "private": + assert "private :: update_interface" in result diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 1e0e8939fcb..abb283f70a5 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -51,11 +51,12 @@ Schedule, Routine, Return, FileContainer, IfBlock, OMPTaskloopDirective, OMPMasterDirective, OMPParallelDirective, Loop, OMPNumTasksClause, OMPDependClause, IntrinsicCall) -from psyclone.psyir.symbols import DataSymbol, SymbolTable, ContainerSymbol, \ - ImportInterface, ArgumentInterface, UnresolvedInterface, ScalarType, \ - ArrayType, INTEGER_TYPE, REAL_TYPE, CHARACTER_TYPE, BOOLEAN_TYPE, \ - REAL_DOUBLE_TYPE, DeferredType, RoutineSymbol, Symbol, UnknownType, \ - UnknownFortranType, DataTypeSymbol, StructureType +from psyclone.psyir.symbols import ( + DataSymbol, SymbolTable, ContainerSymbol, RoutineSymbol, Symbol, + ImportInterface, ArgumentInterface, UnresolvedInterface, StaticInterface, + ScalarType, ArrayType, INTEGER_TYPE, REAL_TYPE, CHARACTER_TYPE, + BOOLEAN_TYPE, REAL_DOUBLE_TYPE, DeferredType, + UnknownType, UnknownFortranType, DataTypeSymbol, StructureType) from psyclone.errors import InternalError from psyclone.tests.utilities import Compile from psyclone.psyGen import PSyFactory @@ -678,14 +679,28 @@ def test_fw_gen_vardecl(fortran_writer): interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) result = fortran_writer.gen_vardecl(symbol) - assert result == \ - "real, allocatable, dimension(:,:), intent(inout) :: dummy2\n" + assert (result == + "real, allocatable, dimension(:,:), intent(inout) :: dummy2\n") - # Constant - symbol = DataSymbol("dummy3", INTEGER_TYPE, constant_value=10) + # Constant. + symbol = DataSymbol("dummy3", INTEGER_TYPE, is_constant=True, + initial_value=10) result = fortran_writer.gen_vardecl(symbol) assert result == "integer, parameter :: dummy3 = 10\n" + # Symbol has initial value but is not constant (static). This is a property + # of the Fortran language and therefore is only checked for when we attempt + # to generate Fortran. + symbol = DataSymbol("dummy3a", INTEGER_TYPE, initial_value=10) + with pytest.raises(VisitorError) as err: + _ = fortran_writer.gen_vardecl(symbol) + assert ("'dummy3a' has an initial value (10) and therefore (in Fortran) " + "must have a StaticInterface. However it has an interface of " + "'Automatic'" in str(err.value)) + symbol.interface = StaticInterface() + result = fortran_writer.gen_vardecl(symbol) + assert result == "integer, save :: dummy3a = 10\n" + # Use statement symbol = DataSymbol("dummy1", DeferredType(), interface=ImportInterface( @@ -708,7 +723,8 @@ def test_fw_gen_vardecl_visibility(fortran_writer): ''' Test the include_visibility argument to gen_vardecl(). ''' # Simple constant symbol = DataSymbol("dummy3", INTEGER_TYPE, - visibility=Symbol.Visibility.PUBLIC, constant_value=10) + visibility=Symbol.Visibility.PUBLIC, is_constant=True, + initial_value=10) # Expect include_visibility to default to False result = fortran_writer.gen_vardecl(symbol) assert result == "integer, parameter :: dummy3 = 10\n" diff --git a/src/psyclone/tests/psyir/backend/psyir_openacc_test.py b/src/psyclone/tests/psyir/backend/psyir_openacc_test.py index c01bc9faed7..104395e91a2 100644 --- a/src/psyclone/tests/psyir/backend/psyir_openacc_test.py +++ b/src/psyclone/tests/psyir/backend/psyir_openacc_test.py @@ -33,6 +33,7 @@ # ----------------------------------------------------------------------------- # Author: A. R. Porter, STFC Daresbury Lab # Modified by: S. Siso and N. Nobre, STFC Daresbury Lab +# Modified by: J. G. Wallwork, Met Office # ----------------------------------------------------------------------------- '''Performs pytest tests on the support for OpenACC directives in the @@ -257,6 +258,16 @@ def test_acc_loop(parser, fortran_writer): assert (" !$acc kernels\n" " !$acc loop\n" " do jj = 1, jpj, 1\n" in result) + loop_dir._gang = True + result = fortran_writer(schedule) + assert (" !$acc kernels\n" + " !$acc loop gang\n" + " do jj = 1, jpj, 1\n" in result) + loop_dir._vector = True + result = fortran_writer(schedule) + assert (" !$acc kernels\n" + " !$acc loop gang vector\n" + " do jj = 1, jpj, 1\n" in result) # ---------------------------------------------------------------------------- diff --git a/src/psyclone/tests/psyir/backend/sympy_writer_test.py b/src/psyclone/tests/psyir/backend/sympy_writer_test.py index 537b52dcfb6..7c52737db53 100644 --- a/src/psyclone/tests/psyir/backend/sympy_writer_test.py +++ b/src/psyclone/tests/psyir/backend/sympy_writer_test.py @@ -43,6 +43,7 @@ from sympy import Function, Symbol from sympy.parsing.sympy_parser import parse_expr +from psyclone.psyir.frontend.sympy_reader import SymPyReader from psyclone.psyir.backend.sympy_writer import SymPyWriter from psyclone.psyir.backend.visitor import VisitorError from psyclone.psyir.nodes import Literal @@ -215,20 +216,20 @@ def test_sympy_writer_create_type_map(expr, sym_map, fortran_reader): assert sympy_writer._sympy_type_map.keys() == sym_map.keys() -@pytest.mark.parametrize("expressions", [("a%x", "a%a_x"), - ("b(i)%x", "b(i,i,1)%b_x"), - ("a%x(i)", "a%a_x(i,i,1)"), - ("b(j)%x(i)", "b(j,j,1)%b_x(i,i,1)"), +@pytest.mark.parametrize("expressions", [("a%x", "a_x"), + ("b(i)%x", "b_x(i,i,1)"), + ("a%x(i)", "a_x(i,i,1)"), + ("b(j)%x(i)", "b_x(j,j,1,i,i,1)"), ("b(i)%c(b_c)", - "b(i,i,1)%b_c_1(b_c,b_c,1)"), + "b_c_1(i,i,1,b_c,b_c,1)"), ("a_c + a%c(i)", - "a_c + a%a_c_1(i,i,1)"), + "a_c + a_c_1(i,i,1)"), ("b(b_c)%c(i)", - "b(b_c,b_c,1)%b_c_1(i,i,1)"), + "b_c_1(b_c,b_c,1,i,i,1)"), ("b(b_c)%c(i)", - "b(b_c,b_c,1)%b_c_1(i,i,1)"), + "b_c_1(b_c,b_c,1,i,i,1)"), ("a_b_c + a_b_c_1 + a%b%c", - "a_b_c + a_b_c_1 + a%a_b%a_b_c_2"), + "a_b_c + a_b_c_1 + a_b_c_2"), ]) def test_sym_writer_rename_members(fortran_reader, expressions): '''Test that members are converted and get a unique name that @@ -336,11 +337,12 @@ def test_sym_writer_convert_to_sympy_expressions(fortran_reader): psyir = fortran_reader.psyir_from_source(source) exp1 = psyir.children[0].children[0].rhs exp2 = psyir.children[0].children[1].rhs - sympy_list = SymPyWriter(exp1, exp2) + sympy_writer = SymPyWriter() + sympy_list = sympy_writer([exp1, exp2]) - expr = parse_expr("a%a_b_1 + a%a_c(1,1,1) + i") + expr = parse_expr("a_b_1 + a_c(1,1,1) + i", sympy_writer.type_map) assert sympy_list[0] == expr - assert sympy_list[1] == parse_expr("a_b + j") + assert sympy_list[1] == parse_expr("a_b + j", sympy_writer.type_map) def test_sym_writer_parse_errors(fortran_reader): @@ -391,18 +393,18 @@ def test_sym_writer_parse_errors(fortran_reader): "sympy_lower,sympy_upper,1)"), ("c", "c(sympy_lower,sympy_upper,1," "sympy_lower,sympy_upper,1)"), - ("b(i)%x", "b(i,i,1)%b_x"), - ("b(i)%x(j)", "b(i,i,1)%b_x(j,j,1)"), - ("c(i,j)%x", "c(i,i,1,j,j,1)%c_x"), + ("b(i)%x", "b_x(i,i,1)"), + ("b(i)%x(j)", "b_x(i,i,1,j,j,1)"), + ("c(i,j)%x", "c_x(i,i,1,j,j,1)"), ("c(i,j)%x(j)", - "c(i,i,1,j,j,1)%c_x(j,j,1)"), + "c_x(i,i,1,j,j,1,j,j,1)"), ("c(i,j)%d%e", - "c(i,i,1,j,j,1)%c_d%c_d_e"), + "c_d_e(i,i,1,j,j,1)"), ("c(i,j)%d%f(i)", - "c(i,i,1,j,j,1)%c_d%c_d_f(i,i,1)"), + "c_d_f(i,i,1,j,j,1,i,i,1)"), ("c(i::k,j)%d%f(i:j:k)", - "c(i,sympy_upper,k,j,j,1)%c_d%" - "c_d_f(i,j,k)"), + "c_d_f(i,sympy_upper,k,j,j,1," + "i,j,k)"), # Check name clashes, if a user # variable is the same as the names # for upper/lower bound @@ -461,3 +463,94 @@ def test_gen_indices(): with pytest.raises(NotImplementedError) as err: _ = sympy_writer.gen_indices([None]) assert "unsupported gen_indices index 'None'" in str(err.value) + + +@pytest.mark.parametrize("fortran_expr,sympy_str", + [("a%b", "a_b"), + # Handle name clash: + ("a%c + a_c", "a_c_1 + a_c"), + ("a%b(i)", "a_b(i,i,1)"), + ("b(i)%b", "b_b(i,i,1)"), + ("b(:)%b(i) + b(1)%c", + "b_b(sympy_lower,sympy_upper,1,i,i,1) + " + "b_c(1,1,1)"), + ("b(i)%b(j)", "b_b(i,i,1,j,j,1)"), + ("a%b(i)%c", "a_b_c(i,i,1)"), + ("a%b%c(i)", "a_b_c(i,i,1)"), + ("a%b%c(2 * i - 1)", "a_b_c(2 * i - 1,2 * i - 1,1)") + ]) +def test_sympy_writer_user_types(fortran_reader, fortran_writer, + fortran_expr, sympy_str): + '''Test handling of user-defined types, e.g. conversion of + ``a(i)%b(j)`` to ``a_b(i,i,1,j,j,1)``. Each Fortran expression + ``fortran_expr`` is first converted to a string ``sympy_str`` to be + parsed by SymPy. The sympy expression is then converted back to PSyIR. + This string must be the same as the original ``fortran_expr``. + + ''' + source = f'''program test_prog + use my_mod + type(my_mod_type) :: a, b(1) + x = {fortran_expr} + end program test_prog''' + + psyir = fortran_reader.psyir_from_source(source) + # Get the actual fortran expression requested: + psyir_expr = psyir.children[0].children[0].rhs + + # Convert the PSyIR to a SymPy string: + sympy_writer = SymPyWriter() + out = sympy_writer._to_str([psyir_expr]) + # Make sure we get the expected string as output: + assert out[0] == sympy_str + + # Second part of the test: convert the PSyIR to a SymPy expression + # (not only a string): + sympy_exp = sympy_writer(psyir_expr) + + symbol_table = psyir.children[0].symbol_table + sympy_reader = SymPyReader(sympy_writer) + new_psyir = sympy_reader.psyir_from_expression(sympy_exp, symbol_table) + assert fortran_writer(new_psyir) == fortran_expr + + +@pytest.mark.parametrize("expression", ["def", "if", "raise", "del", + "import", "return", "elif", "in", + "try", "and", "else", "is", "while", + "as", "except", "lambda", "with", + "assert", "finally", "nonlocal", + "yield", "break", "for", "not", + "class", "from", "or", "continue", + "global", "pass"]) +def test_sym_writer_reserved_names(fortran_reader, expression): + '''Test that reserved names are properly renamed. In this example, + all reserved named will get a ``_1`` appended. + ''' + # A dummy program to easily create the PSyIR for the + # expressions we need. We just take the RHS of the assignments + source = f'''program test_prog + use some_mod + integer :: x + x = {expression} + end program test_prog ''' + psyir = fortran_reader.psyir_from_source(source) + # psyir is a FileContainer, its first child the program, and its + # first child the assignment, of which we take the right hand side + psyir_expr = psyir.children[0].children[0].rhs + + # Make sure that the symbols are renamed in the string representation + # of the PSyIR - the symbol table will make the symbols unique by + # appending ``_1`` + sympy_writer = SymPyWriter() + assert sympy_writer._to_str(psyir_expr) == f"{expression}_1" + + # The SymPy representation will contain e.g. the symbol 'lambda_1' after + # renaming, but when the expression is converted to a string, it should + # use 'lambda' as string representation for the symbol 'lambda_1'. + # Explicit: + # >>> lambda_1 = sp.Symbol("lambda") + # >>> print(lambda_1) + # lambda + + sympy_exp = sympy_writer(psyir_expr) + assert str(sympy_exp) == expression diff --git a/src/psyclone/tests/psyir/conftest.py b/src/psyclone/tests/psyir/conftest.py index 945b7f2960c..af450db4ac2 100644 --- a/src/psyclone/tests/psyir/conftest.py +++ b/src/psyclone/tests/psyir/conftest.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2020, Science and Technology Facilities Council. +# Copyright (c) 2019-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,14 +32,15 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author A. R. Porter, STFC Daresbury Lab +# Modified R. W. Ford, STFC Daresbury Lab ''' Module which performs pytest set-up specific to the PSyIR tests. ''' -from __future__ import absolute_import import pytest -from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE + import psyclone.psyir.frontend.fparser2 as fp2 +from psyclone.psyir.symbols import DataSymbol, INTEGER_TYPE @pytest.fixture(scope="function") @@ -52,6 +53,6 @@ def disable_declaration_check(monkeypatch): ''' monkeypatch.setattr( - fp2, "_find_or_create_imported_symbol", + fp2, "_find_or_create_unresolved_symbol", lambda _1, name, _2=None: DataSymbol(name, INTEGER_TYPE)) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py b/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py index c8d5bbbc118..9491bbac0d0 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_common_block_test.py @@ -216,3 +216,23 @@ def test_commonblock_with_explicit_array_shape_symbol(): assert ("The symbol interface of a common block variable could not be " "updated because of \"Could not find 'a(10, 4)' in the Symbol " "Table.\"." in str(err.value)) + + +@pytest.mark.usefixtures("f2008_parser") +def test_commonblock_with_explicit_init_symbol(): + ''' Test that commonblocks containing a symbol declared with explicit + initialisation produce NotImplementedError.''' + + # Create a dummy test routine + routine = Routine("test_routine") + processor = Fparser2Reader() + + # This is also invalid Fortran, but fparser2 doesn't notice. + reader = FortranStringReader(''' + integer :: a = 10 + common /name1/ a''') + fparser2spec = Specification_Part(reader) + with pytest.raises(NotImplementedError) as err: + processor.process_declarations(routine, fparser2spec.content, []) + assert ("Symbol 'a' has an initial value (10) but appears in a common " + "block." in str(err.value)) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_find_or_create_symbol_test.py b/src/psyclone/tests/psyir/frontend/fparser2_find_or_create_symbol_test.py index 3f8abbb857b..8029ca07f61 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_find_or_create_symbol_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_find_or_create_symbol_test.py @@ -34,7 +34,7 @@ # Author: A. R. Porter, STFC Daresbury Lab # ----------------------------------------------------------------------------- -''' Performs py.test tests for the _find_or_create_imported_symbol routine +''' Performs py.test tests for the _find_or_create_unresolved_symbol routine of the fparser2 frontend. ''' @@ -42,7 +42,7 @@ import pytest from fparser.common.readfortran import FortranFileReader from psyclone.psyGen import PSyFactory, Kern -from psyclone.psyir.frontend.fparser2 import _find_or_create_imported_symbol +from psyclone.psyir.frontend.fparser2 import _find_or_create_unresolved_symbol from psyclone.psyir.nodes import Reference, Container, Assignment, Literal, \ KernelSchedule, BinaryOperation from psyclone.psyir.symbols import Symbol, DataSymbol, SymbolError, \ @@ -51,7 +51,7 @@ from psyclone.tests.utilities import get_invoke -def test_find_or_create_imported_symbol(): +def test_find_or_create_unresolved_symbol(): '''Test that the find_or_create_imported_symbol method in a Node instance returns the associated symbol if there is one and raises an exception if not. Also test for an incorrect scope argument.''' @@ -70,7 +70,7 @@ def test_find_or_create_imported_symbol(): assert field_old.symbol in kernel_schedule.symbol_table.symbols # Symbol in KernelSchedule SymbolTable with KernelSchedule scope - assert isinstance(_find_or_create_imported_symbol( + assert isinstance(_find_or_create_unresolved_symbol( field_old, field_old.name, scope_limit=kernel_schedule), DataSymbol) assert field_old.symbol.name == field_old.name @@ -78,66 +78,66 @@ def test_find_or_create_imported_symbol(): # the symbol should not be found as we limit the scope to the # immediate parent of the reference with pytest.raises(SymbolError) as excinfo: - _ = _find_or_create_imported_symbol(field_old, field_old.name, - scope_limit=field_old.parent) + _ = _find_or_create_unresolved_symbol(field_old, field_old.name, + scope_limit=field_old.parent) assert "No Symbol found for name 'field_old'." in str(excinfo.value) # Symbol in Container SymbolTable alpha = references[6] assert alpha.name == "alpha" - assert isinstance(_find_or_create_imported_symbol(alpha, alpha.name), + assert isinstance(_find_or_create_unresolved_symbol(alpha, alpha.name), DataSymbol) container = kernel_schedule.ancestor(Container) assert isinstance(container, Container) - assert (_find_or_create_imported_symbol(alpha, alpha.name) in + assert (_find_or_create_unresolved_symbol(alpha, alpha.name) in container.symbol_table.symbols) # Symbol in Container SymbolTable with KernelSchedule scope, so # the symbol should not be found as we limit the scope to the # kernel so do not search the container symbol table. with pytest.raises(SymbolError) as excinfo: - _ = _find_or_create_imported_symbol(alpha, alpha.name, - scope_limit=kernel_schedule) + _ = _find_or_create_unresolved_symbol(alpha, alpha.name, + scope_limit=kernel_schedule) assert "No Symbol found for name 'alpha'." in str(excinfo.value) # Symbol in Container SymbolTable with Container scope - assert (_find_or_create_imported_symbol( + assert (_find_or_create_unresolved_symbol( alpha, alpha.name, scope_limit=container).name == alpha.name) - # Test _find_or_create_imported_symbol with invalid location + # Test _find_or_create_unresolved_symbol with invalid location with pytest.raises(TypeError) as excinfo: - _ = _find_or_create_imported_symbol("hello", alpha.name) + _ = _find_or_create_unresolved_symbol("hello", alpha.name) assert ("The location argument 'hello' provided to " - "_find_or_create_imported_symbol() is not of type `Node`." + "_find_or_create_unresolved_symbol() is not of type `Node`." in str(excinfo.value)) - # Test _find_or_create_imported_symbol with invalid scope type + # Test _find_or_create_unresolved_symbol with invalid scope type with pytest.raises(TypeError) as excinfo: - _ = _find_or_create_imported_symbol(alpha, alpha.name, - scope_limit="hello") + _ = _find_or_create_unresolved_symbol(alpha, alpha.name, + scope_limit="hello") assert ("The scope_limit argument 'hello' provided to " - "_find_or_create_imported_symbol() is not of type `Node`." + "_find_or_create_unresolved_symbol() is not of type `Node`." in str(excinfo.value)) # find_or_create_symbol method with invalid scope location with pytest.raises(ValueError) as excinfo: - _ = _find_or_create_imported_symbol(alpha, alpha.name, - scope_limit=alpha) + _ = _find_or_create_unresolved_symbol(alpha, alpha.name, + scope_limit=alpha) assert ("The scope_limit node 'Reference[name:'alpha']' provided to " - "_find_or_create_imported_symbol() is not an ancestor of this " + "_find_or_create_unresolved_symbol() is not an ancestor of this " "node 'Reference[name:'alpha']'." in str(excinfo.value)) # With a visibility parameter - sym = _find_or_create_imported_symbol(alpha, "very_private", - visibility=Symbol.Visibility.PRIVATE) + sym = _find_or_create_unresolved_symbol( + alpha, "very_private", visibility=Symbol.Visibility.PRIVATE) assert sym.name == "very_private" assert sym.visibility == Symbol.Visibility.PRIVATE assert sym is container.symbol_table.lookup("very_private", scope_limit=container) -def test_find_or_create_imported_symbol_2(): - ''' Check that the _find_or_create_imported_symbol() method creates new +def test_find_or_create_unresolved_symbol_2(): + ''' Check that the _find_or_create_unresolved_symbol() method creates new symbols when appropriate. ''' # Create some suitable PSyIR from scratch symbol_table = SymbolTable() @@ -151,10 +151,10 @@ def test_find_or_create_imported_symbol_2(): kernel1.addchild(assign) # We have no wildcard imports so there can be no symbol named 'undefined' with pytest.raises(SymbolError) as err: - _ = _find_or_create_imported_symbol(assign, "undefined") + _ = _find_or_create_unresolved_symbol(assign, "undefined") assert "No Symbol found for name 'undefined'" in str(err.value) # We should be able to find the 'tmp' symbol in the parent Container - sym = _find_or_create_imported_symbol(assign, "tmp") + sym = _find_or_create_unresolved_symbol(assign, "tmp") assert sym.datatype.intrinsic == ScalarType.Intrinsic.REAL # Add a wildcard import to the SymbolTable of the KernelSchedule new_container = ContainerSymbol("some_mod") @@ -162,11 +162,11 @@ def test_find_or_create_imported_symbol_2(): kernel1.symbol_table.add(new_container) # Symbol not in any container but we do have wildcard imports so we # get a new symbol back - new_symbol = _find_or_create_imported_symbol(assign, "undefined") + new_symbol = _find_or_create_unresolved_symbol(assign, "undefined") assert new_symbol.name == "undefined" assert isinstance(new_symbol.interface, UnresolvedInterface) # pylint: disable=unidiomatic-typecheck - assert type(new_symbol) == Symbol + assert type(new_symbol) is Symbol assert "undefined" not in container.symbol_table assert kernel1.symbol_table.lookup("undefined") is new_symbol @@ -184,12 +184,12 @@ def test_nemo_find_container_symbol(parser): # Get a node from the schedule bops = psy._invokes.invoke_list[0].schedule.walk(BinaryOperation) # Use it as the starting point for the search - symbol = _find_or_create_imported_symbol(bops[0], "alpha") + symbol = _find_or_create_unresolved_symbol(bops[0], "alpha") assert symbol.datatype.intrinsic == ScalarType.Intrinsic.REAL def test_find_or_create_change_symbol_type(): - ''' Check that the _find_or_create_imported_symbol routine correctly + ''' Check that the _find_or_create_unresolved_symbol routine correctly updates the class of the located symbol if it is not an instance of the requested symbol type. ''' @@ -203,19 +203,45 @@ def test_find_or_create_change_symbol_type(): assign = Assignment.create(Reference(tmp_sym), Literal("1.0", REAL_TYPE)) kernel1.addchild(assign) # Search for the 'tmp' symbol - sym = _find_or_create_imported_symbol(assign, "tmp") + sym = _find_or_create_unresolved_symbol(assign, "tmp") assert sym is tmp_sym - assert type(sym) == Symbol + assert type(sym) is Symbol # Repeat but this time specify that we're expecting a DataSymbol - sym = _find_or_create_imported_symbol(assign, "tmp", - symbol_type=DataSymbol, - datatype=REAL_TYPE) + sym = _find_or_create_unresolved_symbol(assign, "tmp", + symbol_type=DataSymbol, + datatype=REAL_TYPE) assert sym is tmp_sym - assert type(sym) == DataSymbol + assert type(sym) is DataSymbol assert sym.datatype == REAL_TYPE # Search for 'my_sub' and specify that it should be a RoutineSymbol - sym2 = _find_or_create_imported_symbol(assign, "my_sub", - symbol_type=RoutineSymbol) + sym2 = _find_or_create_unresolved_symbol(assign, "my_sub", + symbol_type=RoutineSymbol) assert sym2 is sub_sym - assert type(sym2) == RoutineSymbol + assert type(sym2) is RoutineSymbol assert isinstance(sym2.datatype, NoType) + + +@pytest.mark.parametrize("visibility", [ + Symbol.Visibility.PUBLIC, Symbol.Visibility.PRIVATE]) +def test_visibility_interface(fortran_reader, visibility): + '''Test that PSyclone successfully parses public/private symbols where + their declaration is hidden in an abstract interface. This support + is implemented in _find_or_create_unresolved_symbol. + + ''' + code = ( + f"module test\n" + f" abstract interface\n" + f" subroutine update_interface()\n" + f" end subroutine update_interface\n" + f" end interface\n" + f" {visibility.name} :: update_interface\n" + f"contains\n" + f" subroutine alg()\n" + f" end subroutine alg\n" + f"end module test\n") + psyir = fortran_reader.psyir_from_source(code) + symbol_table = psyir.children[0].symbol_table + assert symbol_table.lookup("_psyclone_internal_interface") + symbol = symbol_table.lookup("update_interface") + assert symbol.visibility is visibility diff --git a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py index ba96c00a190..a1cd5f7a2f7 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py @@ -32,6 +32,7 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: S. Siso, STFC Daresbury Lab +# Modified: A. R. Porter, STFC Daresbury Lab # ----------------------------------------------------------------------------- ''' Tests Fortran parameter statements in the fparser2 PSyIR front-end ''' @@ -42,7 +43,7 @@ from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.nodes import Routine, Literal, BinaryOperation, \ Container, CodeBlock, Reference -from psyclone.psyir.symbols import Symbol +from psyclone.psyir.symbols import Symbol, StaticInterface @pytest.mark.usefixtures("f2008_parser") @@ -63,8 +64,10 @@ def test_parameter_statements_work(): processor.process_declarations(routine, fparser2spec.content, []) newsymbol = symtab.lookup("var1") assert newsymbol.is_constant - assert isinstance(newsymbol.constant_value, Literal) - assert newsymbol.constant_value.value == "3" + assert isinstance(newsymbol.initial_value, Literal) + assert newsymbol.initial_value.value == "3" + assert newsymbol.is_constant is True + assert isinstance(newsymbol.interface, StaticInterface) # Test with a single parameter with an expression reader = FortranStringReader(''' @@ -74,8 +77,9 @@ def test_parameter_statements_work(): processor.process_declarations(routine, fparser2spec.content, []) newsymbol1 = symtab.lookup("var_expr") assert newsymbol1.is_constant - assert isinstance(newsymbol1.constant_value, BinaryOperation) - assert newsymbol1.constant_value.children[0].value == "10" + assert isinstance(newsymbol1.initial_value, BinaryOperation) + assert newsymbol1.initial_value.children[0].value == "10" + assert newsymbol1.is_constant is True # Test with multiple parameters of different types reader = FortranStringReader(''' @@ -94,14 +98,14 @@ def test_parameter_statements_work(): assert newsymbol3.is_constant assert newsymbol4.is_constant assert newsymbol5.is_constant - assert isinstance(newsymbol2.constant_value, Literal) - assert newsymbol2.constant_value.value == "1" - assert isinstance(newsymbol3.constant_value, Literal) - assert newsymbol3.constant_value.value == "3.14" - assert isinstance(newsymbol4.constant_value, Literal) - assert newsymbol4.constant_value.value == "true" - assert isinstance(newsymbol5.constant_value, Literal) - assert newsymbol5.constant_value.value == "a" + assert isinstance(newsymbol2.initial_value, Literal) + assert newsymbol2.initial_value.value == "1" + assert isinstance(newsymbol3.initial_value, Literal) + assert newsymbol3.initial_value.value == "3.14" + assert isinstance(newsymbol4.initial_value, Literal) + assert newsymbol4.initial_value.value == "true" + assert isinstance(newsymbol5.initial_value, Literal) + assert newsymbol5.initial_value.value == "a" @pytest.mark.usefixtures("f2008_parser") @@ -129,12 +133,12 @@ def test_parameter_statements_complex_case_work(): assert newsymbol1.is_constant assert newsymbol2.is_constant assert newsymbol3.is_constant - assert isinstance(newsymbol1.constant_value, Literal) - assert isinstance(newsymbol2.constant_value, Reference) - assert newsymbol2.constant_value.name == "var1" - assert isinstance(newsymbol3.constant_value, BinaryOperation) - assert newsymbol3.constant_value.children[0].name == "var1" - assert newsymbol3.constant_value.children[1].name == "var2" + assert isinstance(newsymbol1.initial_value, Literal) + assert isinstance(newsymbol2.initial_value, Reference) + assert newsymbol2.initial_value.name == "var1" + assert isinstance(newsymbol3.initial_value, BinaryOperation) + assert newsymbol3.initial_value.children[0].name == "var1" + assert newsymbol3.initial_value.children[1].name == "var2" @pytest.mark.usefixtures("f2008_parser") diff --git a/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py b/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py index 9b66cebaf68..1427e47fe65 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2019-2020, Science and Technology Facilities Council. +# Copyright (c) 2019-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -31,12 +31,11 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- -# Author A. Porter and A. B. G. Chalk, STFC Daresbury Laboratory +# Author A. Porter, A. B. G. Chalk and S. Siso, STFC Daresbury Laboratory ''' Module containing pytest tests for the handling of some select case construction for the Fparser->PSyIR frontend.''' -from __future__ import absolute_import import pytest from fparser.common.readfortran import FortranStringReader @@ -44,9 +43,11 @@ Assignment_Stmt, Execution_Part, Name) from psyclone.errors import InternalError -from psyclone.psyir.frontend.fparser2 import Fparser2Reader +from psyclone.psyir.frontend.fparser2 import ( + Fparser2Reader, _find_or_create_psyclone_internal_cmp) from psyclone.psyir.nodes import ( - Schedule, CodeBlock, Assignment, BinaryOperation, IfBlock) + Schedule, CodeBlock, Assignment, BinaryOperation, IfBlock, Routine, Return, + Container) from psyclone.psyir.symbols import ( DataSymbol, INTEGER_TYPE, Symbol) @@ -452,37 +453,245 @@ def test_nonlogical_reference_case(fortran_reader, fortran_writer): assert "a == c" in pass_through -def test_expression_case(fortran_reader): +def has_cmp_interface(code): + ''' Utility function that asserts that the psyclone_internal_cmp + generic interface and its 3 implementations are part of a given code. + ''' + + # Check that the generic interface in in the code + assert '''interface psyclone_internal_cmp + procedure psyclone_cmp_int + procedure psyclone_cmp_logical + procedure psyclone_cmp_char +end interface psyclone_internal_cmp''' in code + + # Check that the integer implementation is in the code + assert '''function psyclone_cmp_int(op1, op2) + integer, intent(in) :: op1 + integer, intent(in) :: op2 + logical :: psyclone_cmp_int + + psyclone_cmp_int = op1 == op2 + + end function psyclone_cmp_int''' in code + + # Check that the char implementation is in the code + assert '''function psyclone_cmp_char(op1, op2) + CHARACTER(LEN = *), INTENT(IN) :: op1 + CHARACTER(LEN = *), INTENT(IN) :: op2 + logical :: psyclone_cmp_char + + psyclone_cmp_char = op1 == op2 + + end function psyclone_cmp_char''' in code + + # Check that the logical implementation is in the code + assert '''function psyclone_cmp_logical(op1, op2) + logical, intent(in) :: op1 + logical, intent(in) :: op2 + logical :: psyclone_cmp_logical + + psyclone_cmp_logical = op1 .EQV. op2 + + end function psyclone_cmp_logical''' in code + + +def test_find_or_create_psyclone_internal_cmp(fortran_writer): + '''Test that the find_or_create_psyclone_internal_cmp returns the expected + symbol and creates the interface if it does not exist. ''' + subroutine = Routine("mysub", children=[Return()]) + node_in_subroutine = subroutine.children[0] + + # If it is not inside a Container it producess a NotImplementedError + with pytest.raises(NotImplementedError) as error: + _ = _find_or_create_psyclone_internal_cmp(node_in_subroutine) + assert ("Could not find the generic comparison interface and the scope " + "does not have an ancestor container in which to add it." + in str(error.value)) + + container = Container("test", children=[subroutine]) + symbol = _find_or_create_psyclone_internal_cmp(node_in_subroutine) + + # Check that the interface and 3 additional functions have been added to + # the container + assert "psyclone_internal_cmp" in container.symbol_table + assert symbol is container.symbol_table.lookup_with_tag( + "psyclone_internal_cmp") + assert symbol.visibility == Symbol.Visibility.PRIVATE + assert len(container.children) == 4 + assert (container.symbol_table.lookup("psyclone_cmp_int").visibility + == Symbol.Visibility.PRIVATE) + assert (container.symbol_table.lookup("psyclone_cmp_logical").visibility + == Symbol.Visibility.PRIVATE) + assert (container.symbol_table.lookup("psyclone_cmp_char").visibility + == Symbol.Visibility.PRIVATE) + + # Check the generated code matches the expected code + has_cmp_interface(fortran_writer(container)) + + # If called again, the same symbol is retrived and no extra code is added + another_symbol = _find_or_create_psyclone_internal_cmp(node_in_subroutine) + assert symbol is another_symbol + assert len(container.children) == 4 + + # Test what happens if there are name clashes for the interface and + # supporting functions, an easy way to do it is remove the existing tag + # so the existing symbols remain but are not considered the ones we are + # looking for + del container.symbol_table.tags_dict['psyclone_internal_cmp'] + another_symbol = _find_or_create_psyclone_internal_cmp(node_in_subroutine) + assert another_symbol is not symbol + assert another_symbol.name == "psyclone_internal_cmp_1" + assert len(container.children) == 7 # 3 more functions added + assert "psyclone_internal_cmp_1" in container.symbol_table + + # Check that the interface new names are internally consistent + assert '''interface psyclone_internal_cmp_1 + procedure psyclone_cmp_int_1 + procedure psyclone_cmp_logical_1 + procedure psyclone_cmp_char_1 +end interface psyclone_internal_cmp_1''' in fortran_writer(container) + + # And that from now on the tag refers to the new symbol + assert container.symbol_table.lookup_with_tag( + "psyclone_internal_cmp").name == "psyclone_internal_cmp_1" + + +def test_expression_case(fortran_reader, fortran_writer): '''Test that a select case statement comparing two expressions - results in a code block.''' - code = '''subroutine test_subroutine(a, b, c) - integer :: a, b, c + is using the generic comparison interface. - SELECT CASE(a*a) - CASE(b-c) - print *, "Not hello" - CASE(c-b) - print *, "hello" - END SELECT - end subroutine''' + # TODO #1799: The interface may not be needed for this case if + # we can successuly obtain the expression.datatype + + ''' + code = ''' + module test + contains + subroutine test_subroutine(a, b, c) + integer :: a, b, c + + SELECT CASE(a*a) + CASE(b-c) + print *, "Not hello" + CASE(c-b) + print *, "hello" + END SELECT + end subroutine + end module test + ''' psyir = fortran_reader.psyir_from_source(code) - assert isinstance(psyir.children[0].children[0], CodeBlock) + output = fortran_writer(psyir) + # Check that the interface implementation has been inserted + has_cmp_interface(output) -def test_unknown_types_case(fortran_reader): - '''Test that a select case statement comparing two unknown types - results in a code block.''' - code = '''subroutine test_subroutine() - use my_mod, only : a, b, c + # Check that the cannonicalised comparisons use the interface method + assert "if (psyclone_internal_cmp(a * a, b - c)) then" in output + assert "if (psyclone_internal_cmp(a * a, c - b)) then" in output - SELECT CASE(a) - CASE(b) - print *, "Not hello" - CASE(c) - print *, "hello" - END SELECT - end subroutine''' + +def test_unknown_types_case(fortran_reader, fortran_writer): + '''Test that a select case statement comparing two unknown types + is using the generic comparison interface''' + code = ''' + module test + contains + subroutine test_subroutine() + use my_mod, only : a, b, c + + SELECT CASE(a) + CASE(b) + print *, "Not hello" + CASE(c) + print *, "hello" + END SELECT + end subroutine test_subroutine + end module test + ''' + psyir = fortran_reader.psyir_from_source(code) + output = fortran_writer(psyir) + + # Check that the interface implementation has been inserted + has_cmp_interface(output) + + # Check that the canonicalised comparisons use the interface method + assert "if (psyclone_internal_cmp(a, b)) then" in output + assert "if (psyclone_internal_cmp(a, c)) then" in output + + +def test_unknown_types_case_without_module(fortran_reader): + '''Test that a select case statement comparing two unknown types in a + situation wihtout an ancestor module, it will generate a CodeBlock''' + code = ''' + subroutine test_subroutine() + use my_mod, only : a, b, c + + SELECT CASE(a) + CASE(b) + print *, "Not hello" + CASE(c) + print *, "hello" + END SELECT + end subroutine test_subroutine + ''' psyir = fortran_reader.psyir_from_source(code) assert isinstance(psyir.children[0].children[0], CodeBlock) + + +def test_derived_types_case(fortran_reader, fortran_writer): + '''Test that a select case statement comparing two derived types accessors + generate the appropriate code''' + + # When the datatype information is known + code = ''' + module test + type :: my_type + integer :: field + end type + contains + subroutine test_subroutine() + type(my_type) :: a + SELECT CASE(a%field) + CASE(1) + print *, "Not hello" + CASE(2) + print *, "hello" + END SELECT + end subroutine test_subroutine + end module test + ''' + + psyir = fortran_reader.psyir_from_source(code) + output = fortran_writer(psyir) + assert "if (a%field == 1) then" in output + assert "if (a%field == 2) then" in output + + # And then the datatype information is unknown + code = ''' + module test + contains + subroutine test_subroutine() + use my_mod, only : a, b, c, i + + SELECT CASE(a%b(i)%c) + CASE(b%d) + print *, "Not hello" + CASE(c%a) + print *, "hello" + END SELECT + end subroutine test_subroutine + end module test + ''' + + psyir = fortran_reader.psyir_from_source(code) + output = fortran_writer(psyir) + + # Check that the interface implementation has been inserted + has_cmp_interface(output) + + # Check that the canonicalised comparisons use the interface method + assert "if (psyclone_internal_cmp(a%b(i)%c, b%d)) then" in output + assert "if (psyclone_internal_cmp(a%b(i)%c, c%a)) then" in output diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 1562485987f..25674aa232b 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -699,11 +699,12 @@ def test_get_partial_datatype(): processor = Fparser2Reader() # Entry in symbol table with unmodified properties. - reader = FortranStringReader("integer :: l1") + reader = FortranStringReader("integer :: l1=2") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) + assert isinstance(init, Literal) assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -713,8 +714,9 @@ def test_get_partial_datatype(): reader = FortranStringReader("integer, pointer :: l1 => null()") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) + assert isinstance(init, CodeBlock) assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -724,8 +726,9 @@ def test_get_partial_datatype(): reader = FortranStringReader("real*4, target, dimension(10,20) :: l1") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ArrayType) + assert init is None assert datatype.intrinsic is ScalarType.Intrinsic.REAL assert datatype.precision == 4 assert datatype.shape[0].upper.value == '10' @@ -739,7 +742,9 @@ def test_get_partial_datatype(): reader = FortranStringReader(" complex :: c\n") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - assert not processor._get_partial_datatype(node, fake_parent, {}) + dtype, init = processor._get_partial_datatype(node, fake_parent, {}) + assert dtype is None + assert init is None # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -749,8 +754,9 @@ def test_get_partial_datatype(): "integer, pointer :: l1 => null(), l2 => null()") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] - datatype = processor._get_partial_datatype(node, fake_parent, {}) + datatype, init = processor._get_partial_datatype(node, fake_parent, {}) assert isinstance(datatype, ScalarType) + assert isinstance(init, CodeBlock) assert datatype.intrinsic is ScalarType.Intrinsic.INTEGER # Check fparser2 tree is unmodified assert ids == [id(entry) for entry in walk(node)] @@ -817,43 +823,56 @@ def test_process_declarations(): processor.process_declarations(fake_parent, [fparser2spec], []) newsymbol = symtab.lookup("i1") assert newsymbol.is_constant - assert isinstance(newsymbol.constant_value, Literal) - assert newsymbol.constant_value.value == "1" + assert isinstance(newsymbol.initial_value, Literal) + assert newsymbol.initial_value.value == "1" reader = FortranStringReader("real, parameter :: i2 = 2.2, i3 = 3.3") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - assert symtab.lookup("i2").constant_value.value == "2.2" - assert symtab.lookup("i3").constant_value.value == "3.3" + assert symtab.lookup("i2").initial_value.value == "2.2" + assert symtab.lookup("i3").initial_value.value == "3.3" # Initialisation with constant expressions reader = FortranStringReader("real, parameter :: i4 = 1.1, i5 = i4 * 2") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - assert symtab.lookup("i4").constant_value.value == "1.1" - assert isinstance(symtab.lookup("i5").constant_value, BinaryOperation) + assert symtab.lookup("i4").initial_value.value == "1.1" + assert isinstance(symtab.lookup("i5").initial_value, BinaryOperation) # Initialisation with a constant expression (1) and with a symbol (val1) reader = FortranStringReader("integer, parameter :: val1 = 1, val2 = val1") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - assert fake_parent.symbol_table.lookup("val1").constant_value.value == "1" + assert fake_parent.symbol_table.lookup("val1").initial_value.value == "1" assert isinstance( - fake_parent.symbol_table.lookup("val2").constant_value, Reference) - assert fake_parent.symbol_table.lookup("val2").constant_value.symbol == \ + fake_parent.symbol_table.lookup("val2").initial_value, Reference) + assert fake_parent.symbol_table.lookup("val2").initial_value.symbol == \ fake_parent.symbol_table.lookup("val1") # Initialisation with a complex constant expression - symtab.add(DataSymbol("precisionkind", INTEGER_TYPE, constant_value=4)) + symtab.add(DataSymbol("precisionkind", INTEGER_TYPE, is_constant=True, + initial_value=4)) reader = FortranStringReader( "integer, parameter :: val3 = 2 * (val1 + val2) + 2_precisionkind") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) - # Val3 has been given a constant expression - assert fake_parent.symbol_table.lookup("val3").constant_value + # Val3 has been given an initial_value expression + val3 = fake_parent.symbol_table.lookup("val3") + assert isinstance(val3.initial_value, BinaryOperation) + assert val3.is_constant # The new symbol (precisionkind) has been added to the parent Symbol Table assert fake_parent.symbol_table.lookup("precisionkind") + # Initialisation of a variable + reader = FortranStringReader( + "integer :: val4 = 2 * (val1 + val2) + 2_precisionkind") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + val4 = fake_parent.symbol_table.lookup("val4") + assert val4.initial_value + assert isinstance(val4.initial_value, BinaryOperation) + assert val4.is_constant is False + # Check we catch duplicated symbols reader = FortranStringReader("integer :: i2") fparser2spec = Specification_Part(reader).content[0] @@ -862,6 +881,16 @@ def test_process_declarations(): assert ("Symbol 'i2' already present in SymbolTable with a defined " "interface" in str(error.value)) + # Initialisation of a pointer. + reader = FortranStringReader( + "real, dimension(:), pointer :: dptr => null()") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + ptr_sym = fake_parent.symbol_table.lookup("dptr") + assert isinstance(ptr_sym, DataSymbol) + assert isinstance(ptr_sym.datatype, UnknownFortranType) + assert isinstance(ptr_sym.initial_value, CodeBlock) + @pytest.mark.usefixtures("f2008_parser") def test_process_declarations_unknownfortrantype(): @@ -939,15 +968,13 @@ def test_process_declarations_errors(): @pytest.mark.usefixtures("f2008_parser") def test_declarations_with_initialisations(fortran_reader): '''Test that Fparser2Reader keeps all the variable initialisation - expressions, even though some may end up in UnknownTypes for now - because we don't support variable initialisations that are not - parameters. + expressions. ''' psyir = fortran_reader.psyir_from_source( """ module test - integer :: a = 1 + integer :: a = 1, aa = 4 integer, save :: b = 1 integer, parameter :: c = 1 contains @@ -960,32 +987,33 @@ def test_declarations_with_initialisations(fortran_reader): """) inner_st = psyir.walk(Routine)[0].symbol_table + asym = inner_st.lookup('a') + aasym = inner_st.lookup('aa') + bsym = inner_st.lookup('b') + csym = inner_st.lookup('c') + dsym = inner_st.lookup('d') + esym = inner_st.lookup('e') + fsym = inner_st.lookup('f') + all_syms = [asym, aasym, bsym, csym, dsym, esym, fsym] + # All initialisation variables are DataSymbols - assert isinstance(inner_st.lookup('a'), DataSymbol) - assert isinstance(inner_st.lookup('b'), DataSymbol) - assert isinstance(inner_st.lookup('c'), DataSymbol) - assert isinstance(inner_st.lookup('d'), DataSymbol) - assert isinstance(inner_st.lookup('e'), DataSymbol) - assert isinstance(inner_st.lookup('f'), DataSymbol) - - # When it is not a parameter they are unknown interface and datatype - assert isinstance(inner_st.lookup('a').interface, UnknownInterface) - assert isinstance(inner_st.lookup('b').interface, UnknownInterface) - assert isinstance(inner_st.lookup('d').interface, UnknownInterface) - assert isinstance(inner_st.lookup('e').interface, UnknownInterface) - assert isinstance(inner_st.lookup('a').datatype, UnknownFortranType) - assert isinstance(inner_st.lookup('b').datatype, UnknownFortranType) - assert isinstance(inner_st.lookup('d').datatype, UnknownFortranType) - assert isinstance(inner_st.lookup('e').datatype, UnknownFortranType) - - # When it is a parameter the interface, type and constant_value is defined - assert isinstance(inner_st.lookup('c').interface, DefaultModuleInterface) - assert isinstance(inner_st.lookup('c').datatype, ScalarType) - assert isinstance(inner_st.lookup('c').constant_value, Literal) - assert isinstance(inner_st.lookup('f').interface, AutomaticInterface) - assert isinstance(inner_st.lookup('f').datatype, ScalarType) - assert isinstance(inner_st.lookup('f').constant_value, Literal) - assert isinstance(inner_st.lookup('f').interface, AutomaticInterface) + assert all(isinstance(sym, DataSymbol) for sym in all_syms) + + # All of the data symbols should have a StaticInterface (because they + # either have an explicit 'save' or 'parameter' or are given an + # initial_value with then implies 'save'). + assert all(isinstance(sym.interface, StaticInterface) for sym in all_syms) + + # All symbols should have a known data type. + assert all(isinstance(sym.datatype, ScalarType) for sym in all_syms) + + # When it is a parameter the initial_value is defined and is_constant + # is True. + assert isinstance(csym.initial_value, Literal) + assert csym.is_constant is True + + assert isinstance(fsym.initial_value, Literal) + assert fsym.is_constant is True @pytest.mark.usefixtures("f2008_parser") @@ -1043,49 +1071,30 @@ def test_process_unsupported_declarations(fortran_reader): fake_parent = KernelSchedule("dummy_schedule") processor = Fparser2Reader() - # Initial values for variables are not supported so we should get a symbol - # with unknown type. - reader = FortranStringReader("real:: a = 1.1") - fparser2spec = Specification_Part(reader).content[0] - processor.process_declarations(fake_parent, [fparser2spec], []) - asym = fake_parent.symbol_table.lookup("a") - assert isinstance(asym.datatype, UnknownFortranType) - assert asym.datatype.declaration == "REAL :: a = 1.1" - - reader = FortranStringReader("real:: b = 1.1, c = 2.2") - fparser2spec = Specification_Part(reader).content[0] - processor.process_declarations(fake_parent, [fparser2spec], []) - bsym = fake_parent.symbol_table.lookup("b") - assert isinstance(bsym.datatype, UnknownFortranType) - assert bsym.datatype.declaration == "REAL :: b = 1.1" - csym = fake_parent.symbol_table.lookup("c") - assert isinstance(csym.datatype, UnknownFortranType) - assert csym.datatype.declaration == "REAL :: c = 2.2" - # Multiple symbols with a single attribute - reader = FortranStringReader("integer, private :: d = 1, e = 2") + reader = FortranStringReader("integer, private, pointer :: d, e") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) dsym = fake_parent.symbol_table.lookup("d") assert isinstance(dsym.datatype, UnknownFortranType) - assert dsym.datatype.declaration == "INTEGER, PRIVATE :: d = 1" + assert dsym.datatype.declaration == "INTEGER, PRIVATE, POINTER :: d" esym = fake_parent.symbol_table.lookup("e") assert isinstance(esym.datatype, UnknownFortranType) - assert esym.datatype.declaration == "INTEGER, PRIVATE :: e = 2" + assert esym.datatype.declaration == "INTEGER, PRIVATE, POINTER :: e" # Multiple attributes reader = FortranStringReader( - "INTEGER, PRIVATE, DIMENSION(3) :: f = 2, g = 3") + "INTEGER, PRIVATE, DIMENSION(3), POINTER :: f, g") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) fsym = fake_parent.symbol_table.lookup("f") assert isinstance(fsym.datatype, UnknownFortranType) assert (fsym.datatype.declaration == - "INTEGER, PRIVATE, DIMENSION(3) :: f = 2") + "INTEGER, PRIVATE, DIMENSION(3), POINTER :: f") gsym = fake_parent.symbol_table.lookup("g") assert isinstance(gsym.datatype, UnknownFortranType) assert (gsym.datatype.declaration == - "INTEGER, PRIVATE, DIMENSION(3) :: g = 3") + "INTEGER, PRIVATE, DIMENSION(3), POINTER :: g") # Test with unsupported intrinsic type. Note the space before complex # below which stops the line being treated as a comment. @@ -1118,16 +1127,16 @@ def test_process_unsupported_declarations(fortran_reader): processor.process_declarations(fake_parent, [fparser2spec], []) fbsym = fake_parent.symbol_table.lookup("fbsp") assert fbsym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert isinstance(fbsym.constant_value, CodeBlock) + assert isinstance(fbsym.initial_value, CodeBlock) # The first parameter should have been handled correctly hsym = fake_parent.symbol_table.lookup("happy") assert hsym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert hsym.constant_value.value == "1" + assert hsym.initial_value.value == "1" # As should the third ssym = fake_parent.symbol_table.lookup("sad") assert ssym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert isinstance(ssym.constant_value, Reference) - assert ssym.constant_value.symbol.name == "fbsp" + assert isinstance(ssym.initial_value, Reference) + assert ssym.initial_value.symbol.name == "fbsp" @pytest.mark.usefixtures("f2008_parser") @@ -1144,16 +1153,16 @@ def test_unsupported_decln_initial_value(monkeypatch): # for anything other than a Literal. class BrokenDataSymbol(DataSymbol): - ''' Sub-class of DataSymbol with `constant_value` setter patched + ''' Sub-class of DataSymbol with `initial_value` setter patched so that it raises a ValueError for anything other than a Literal. ''' @property - def constant_value(self): - return self._constant_value + def initial_value(self): + return self._initial_value - @constant_value.setter - def constant_value(self, value): + @initial_value.setter + def initial_value(self, value): if isinstance(value, Literal): - self._constant_value = value + self._initial_value = value else: raise ValueError("") @@ -1168,7 +1177,7 @@ def constant_value(self, value): processor.process_declarations(fake_parent, [fparser2spec], []) hsym = fake_parent.symbol_table.lookup("happy") assert hsym.datatype.intrinsic == ScalarType.Intrinsic.INTEGER - assert hsym.constant_value.value == "1" + assert hsym.initial_value.value == "1" fbsym = fake_parent.symbol_table.lookup("fbsp") assert isinstance(fbsym.datatype, UnknownFortranType) assert (fbsym.datatype.declaration == "INTEGER, PRIVATE, PARAMETER :: " @@ -1775,7 +1784,7 @@ class UnrecognizedType(): csym = sym_table.new_symbol("some_mod", symbol_type=ContainerSymbol) vsym = sym_table.new_symbol("var3", interface=ImportInterface(csym)) # pylint: disable=unidiomatic-typecheck - assert type(vsym) == Symbol + assert type(vsym) is Symbol shape = Fparser2Reader._parse_dimensions(fparser2spec, sym_table) assert len(shape) == 1 assert shape[0][0].value == "1" @@ -2765,7 +2774,7 @@ def test_named_and_wildcard_use_var(f2008_parser): avar1 = container.symbol_table.lookup("a_var") # It must be a generic Symbol since we don't know anything about it # pylint: disable=unidiomatic-typecheck - assert type(avar1) == Symbol + assert type(avar1) is Symbol # There should be no entry for "a_var" in the symbol table for the # "test_sub1" routine as it is not declared there. schedule = psy.invokes.invoke_list[0].schedule @@ -2774,7 +2783,7 @@ def test_named_and_wildcard_use_var(f2008_parser): # for "test_sub2" as it has a use statement that imports it. schedule = psy.invokes.invoke_list[1].schedule avar2 = schedule.symbol_table.lookup("a_var") - assert type(avar2) == Symbol + assert type(avar2) is Symbol assert avar2 is not avar1 diff --git a/src/psyclone/tests/psyir/frontend/sympy_reader_test.py b/src/psyclone/tests/psyir/frontend/sympy_reader_test.py index 2b2fed44e10..2426249a1a4 100644 --- a/src/psyclone/tests/psyir/frontend/sympy_reader_test.py +++ b/src/psyclone/tests/psyir/frontend/sympy_reader_test.py @@ -55,6 +55,11 @@ def test_sympy_reader_constructor(): @pytest.mark.parametrize("expressions", [("a", "a"), ("b(i)", "b(i)"), + ("d%e(i)", "d%e(i)"), + ("e(i)%e", "e(i)%e"), + ("e(i)%e(j)", "e(i)%e(j)"), + ("e(1:9:3)%e(i:j:k)", + "e(1:9:3)%e(i:j:k)"), ("c(i,j)", "c(i,j)"), ("b(2:3:4)", "b(2:3:4)"), ("b(2:3:1)", "b(2:3)"), diff --git a/src/psyclone/tests/psyir/nodes/acc_directives_test.py b/src/psyclone/tests/psyir/nodes/acc_directives_test.py index 100f33d996d..e7752153644 100644 --- a/src/psyclone/tests/psyir/nodes/acc_directives_test.py +++ b/src/psyclone/tests/psyir/nodes/acc_directives_test.py @@ -34,6 +34,7 @@ # Authors R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab # Modified I. Kavcic, Met Office # Modified A. B. G. Chalk, STFC Daresbury Lab +# Modified J. G. Wallwork, Met Office # ----------------------------------------------------------------------------- ''' Performs py.test tests on the OpenACC PSyIR Directive nodes. ''' @@ -202,8 +203,8 @@ def test_accloopdirective_node_str(monkeypatch): lambda x: "ACCLoopDirective") # Default value output - expected = ("ACCLoopDirective[sequential=False,collapse=None," - "independent=True]") + expected = ("ACCLoopDirective[sequential=False,gang=False,vector=False," + "collapse=None,independent=True]") assert directive.node_str() == expected assert str(directive) == expected @@ -211,8 +212,10 @@ def test_accloopdirective_node_str(monkeypatch): directive._sequential = True directive._collapse = 2 directive._independent = False - expected = ("ACCLoopDirective[sequential=True,collapse=2," - "independent=False]") + directive._gang = True + directive._vector = True + expected = ("ACCLoopDirective[sequential=True,gang=True,vector=True," + "collapse=2,independent=False]") assert directive.node_str() == expected assert str(directive) == expected @@ -262,6 +265,16 @@ def test_accloopdirective_equality(): directive2._sequential = not directive1._sequential assert directive1 != directive2 + # Check equality fails when gang is different + directive2._sequential = directive1.sequential + directive2._gang = not directive1._gang + assert directive1 != directive2 + + # Check equality fails when vector is different + directive2._gang = directive1.gang + directive2._vector = not directive1._vector + assert directive1 != directive2 + # Class ACCLoopDirective end diff --git a/src/psyclone/tests/psyir/nodes/array_mixin_test.py b/src/psyclone/tests/psyir/nodes/array_mixin_test.py index 045506ca2ed..b7dc4d6ef4a 100644 --- a/src/psyclone/tests/psyir/nodes/array_mixin_test.py +++ b/src/psyclone/tests/psyir/nodes/array_mixin_test.py @@ -290,8 +290,8 @@ def test_get_lbound_expression(): ''' # Symbol is of ArrayType. - lbound = DataSymbol("jmin", INTEGER_TYPE, - constant_value=Literal("3", INTEGER_TYPE)) + lbound = DataSymbol("jmin", INTEGER_TYPE, is_constant=True, + initial_value=Literal("3", INTEGER_TYPE)) lbnd_ref = Reference(lbound) symbol = DataSymbol("my_symbol", ArrayType(INTEGER_TYPE, [10, (2, 10), (lbnd_ref, 10)])) diff --git a/src/psyclone/tests/psyir/nodes/call_test.py b/src/psyclone/tests/psyir/nodes/call_test.py index b6581f072e1..18bf29554ae 100644 --- a/src/psyclone/tests/psyir/nodes/call_test.py +++ b/src/psyclone/tests/psyir/nodes/call_test.py @@ -37,8 +37,9 @@ ''' Performs py.test tests on the Call PSyIR node. ''' import pytest +from psyclone.core import Signature, VariablesAccessInfo from psyclone.psyir.nodes import ( - Call, Reference, ArrayReference, Schedule, Literal) + BinaryOperation, Call, Reference, ArrayReference, Schedule, Literal) from psyclone.psyir.nodes.node import colored from psyclone.psyir.symbols import ArrayType, INTEGER_TYPE, DataSymbol, \ RoutineSymbol, NoType, REAL_TYPE @@ -390,6 +391,52 @@ def test_validate_name_valid(name): call._validate_name(name) +def test_call_reference_accesses(): + '''Test the reference_accesses() method.''' + rsym = RoutineSymbol("trillian") + # A call with an argument passed by value. + call1 = Call.create(rsym, [Literal("1", INTEGER_TYPE)]) + var_info = VariablesAccessInfo() + call1.reference_accesses(var_info) + assert not var_info.all_signatures + dsym = DataSymbol("beta", INTEGER_TYPE) + # Simple argument passed by reference. + call2 = Call.create(rsym, [Reference(dsym)]) + call2.reference_accesses(var_info) + assert var_info.has_read_write(Signature("beta")) + # Array access argument. The array should be READWRITE, any variable in + # the index expression should be READ. + idx_sym = DataSymbol("ji", INTEGER_TYPE) + asym = DataSymbol("gamma", ArrayType(INTEGER_TYPE, shape=[10])) + aref = ArrayReference.create(asym, [Reference(idx_sym)]) + call3 = Call.create(rsym, [aref]) + call3.reference_accesses(var_info) + assert var_info.has_read_write(Signature("gamma")) + assert var_info.is_read(Signature("ji")) + # Argument is a temporary so any inputs to it are READ only. + expr = BinaryOperation.create(BinaryOperation.Operator.MUL, + Literal("2", INTEGER_TYPE), Reference(dsym)) + call4 = Call.create(rsym, [expr]) + var_info = VariablesAccessInfo() + call4.reference_accesses(var_info) + assert var_info.is_read(Signature("beta")) + # Argument is itself a function call: call trillian(some_func(gamma(ji))) + fsym = RoutineSymbol("some_func") + fcall = Call.create(fsym, + [ArrayReference.create(asym, [Reference(idx_sym)])]) + call5 = Call.create(rsym, [fcall]) + call5.reference_accesses(var_info) + assert var_info.has_read_write(Signature("gamma")) + assert var_info.is_read(Signature("ji")) + # Call to a PURE routine - arguments should be READ only. + puresym = RoutineSymbol("dirk", is_pure=True) + call6 = Call.create(puresym, [Reference(dsym)]) + var_info = VariablesAccessInfo() + call6.reference_accesses(var_info) + assert var_info.is_read(Signature("beta")) + assert not var_info.is_written(Signature("beta")) + + def test_call_argumentnames_after_removearg(): '''Test the argument_names property makes things consistent if a child argument is removed. This is used transparently by the class to diff --git a/src/psyclone/tests/psyir/nodes/node_test.py b/src/psyclone/tests/psyir/nodes/node_test.py index f5c3b76664b..9fd9522cdb2 100644 --- a/src/psyclone/tests/psyir/nodes/node_test.py +++ b/src/psyclone/tests/psyir/nodes/node_test.py @@ -1455,3 +1455,35 @@ def test_debug_string(monkeypatch): monkeypatch.setattr(DebugWriter, "__call__", lambda x, y: "CORRECT STRING") tnode = Node() assert tnode.debug_string() == "CORRECT STRING" + + +def test_path_from(fortran_reader): + ''' Test the path_from method of the Node class.''' + + code = '''subroutine test_sub() + integer :: i, j, k, l + k = 12 + do i = 1, 128 + do j = 2, 256 + k = k + 32 + end do + end do + l = k + end subroutine''' + + psyir = fortran_reader.psyir_from_source(code) + assigns = psyir.walk(Assignment) + routine = psyir.walk(Routine)[0] + result = assigns[1].path_from(routine) + start = routine + for index in result: + start = start.children[index] + assert start is assigns[1] + + assert len(assigns[1].path_from(assigns[1])) == 0 + + loops = psyir.walk(Loop) + with pytest.raises(ValueError) as excinfo: + assigns[0].path_from(loops[0]) + assert ("Attempted to find path_from a non-ancestor 'Loop' " + "node." in str(excinfo.value)) diff --git a/src/psyclone/tests/psyir/nodes/omp_clause_test.py b/src/psyclone/tests/psyir/nodes/omp_clause_test.py index b06d4738e63..be6c64406d6 100644 --- a/src/psyclone/tests/psyir/nodes/omp_clause_test.py +++ b/src/psyclone/tests/psyir/nodes/omp_clause_test.py @@ -39,9 +39,9 @@ import pytest from psyclone.psyir.nodes.node import colored -from psyclone.psyir.nodes.omp_clauses import OMPGrainsizeClause,\ - OMPNowaitClause, OMPNogroupClause, OMPNumTasksClause, OMPSharedClause,\ - OMPDependClause, OMPPrivateClause, OMPFirstprivateClause,\ +from psyclone.psyir.nodes.omp_clauses import OMPGrainsizeClause, \ + OMPNowaitClause, OMPNogroupClause, OMPNumTasksClause, OMPSharedClause, \ + OMPDependClause, OMPPrivateClause, OMPFirstprivateClause, \ OMPDefaultClause, OMPScheduleClause from psyclone.psyir.nodes.literal import Literal from psyclone.psyir.nodes.reference import Reference diff --git a/src/psyclone/tests/psyir/symbols/datasymbol_test.py b/src/psyclone/tests/psyir/symbols/datasymbol_test.py index c6612fd9ce7..e2819e54292 100644 --- a/src/psyclone/tests/psyir/symbols/datasymbol_test.py +++ b/src/psyclone/tests/psyir/symbols/datasymbol_test.py @@ -43,12 +43,12 @@ from fparser.common.readfortran import FortranStringReader from fparser.two import Fortran2003 -from psyclone.psyir.symbols import DataSymbol, ContainerSymbol, \ - AutomaticInterface, ImportInterface, ArgumentInterface, \ - ScalarType, ArrayType, REAL_SINGLE_TYPE, REAL_DOUBLE_TYPE, REAL4_TYPE, \ - REAL8_TYPE, INTEGER_SINGLE_TYPE, INTEGER_DOUBLE_TYPE, INTEGER4_TYPE, \ - BOOLEAN_TYPE, CHARACTER_TYPE, DeferredType, Symbol, DataTypeSymbol, \ - UnresolvedInterface +from psyclone.psyir.symbols import ( + DataSymbol, ContainerSymbol, Symbol, DataTypeSymbol, AutomaticInterface, + ImportInterface, ArgumentInterface, UnresolvedInterface, + ScalarType, ArrayType, REAL_SINGLE_TYPE, REAL_DOUBLE_TYPE, REAL4_TYPE, + REAL8_TYPE, INTEGER_SINGLE_TYPE, INTEGER_DOUBLE_TYPE, INTEGER4_TYPE, + BOOLEAN_TYPE, CHARACTER_TYPE, DeferredType) from psyclone.psyir.nodes import (Literal, Reference, BinaryOperation, Return, CodeBlock) @@ -65,19 +65,31 @@ def test_datasymbol_initialisation(): real_kind_type = ScalarType(ScalarType.Intrinsic.REAL, kind) assert isinstance(DataSymbol('a', real_kind_type), DataSymbol) - # real constants are not currently supported assert isinstance(DataSymbol('a', INTEGER_SINGLE_TYPE), DataSymbol) - assert isinstance(DataSymbol('a', INTEGER_DOUBLE_TYPE, constant_value=0), - DataSymbol) + # Run-time constant. + sym = DataSymbol('a', REAL_DOUBLE_TYPE, is_constant=True, + initial_value=0.0) + assert isinstance(sym, DataSymbol) + assert sym.is_constant + # Defaults to StaticInterface for a run-time constant. + assert sym.is_static + # Run-time constant can only have StaticInterface or ImportInterface. + with pytest.raises(ValueError) as err: + _ = DataSymbol('a', INTEGER_DOUBLE_TYPE, is_constant=True, + initial_value=1, interface=AutomaticInterface()) + assert ("A DataSymbol representing a constant must have either a " + "StaticInterface or an ImportInterface but 'a' has interface " + "'Automatic'" in str(err.value)) + assert isinstance(DataSymbol('a', INTEGER4_TYPE), DataSymbol) assert isinstance(DataSymbol('a', CHARACTER_TYPE), DataSymbol) - assert isinstance(DataSymbol('a', CHARACTER_TYPE, - constant_value="hello"), DataSymbol) + assert isinstance(DataSymbol('a', CHARACTER_TYPE, is_constant=True, + initial_value="hello"), DataSymbol) assert isinstance(DataSymbol('a', BOOLEAN_TYPE), DataSymbol) - assert isinstance(DataSymbol('a', BOOLEAN_TYPE, - constant_value=False), + assert isinstance(DataSymbol('a', BOOLEAN_TYPE, is_constant=True, + initial_value=False), DataSymbol) array_type = ArrayType(REAL_SINGLE_TYPE, [ArrayType.Extent.ATTRIBUTE]) assert isinstance(DataSymbol('a', array_type), DataSymbol) @@ -126,25 +138,44 @@ def test_datasymbol_specialise_and_process_arguments(): sym2 = Symbol("symbol2") sym2.specialise(DataSymbol, datatype=REAL_SINGLE_TYPE) assert sym2.datatype is REAL_SINGLE_TYPE - assert sym2.constant_value is None + assert sym2.is_constant is False + assert sym2.initial_value is None - # Include a constant_value + # Include an initial_value sym3 = Symbol("symbol3") sym3.specialise(DataSymbol, datatype=REAL_SINGLE_TYPE, - constant_value=3.14) + initial_value=3.14) assert sym3.datatype is REAL_SINGLE_TYPE - assert isinstance(sym3.constant_value, Literal) - assert sym3.constant_value.value == '3.14' + assert isinstance(sym3.initial_value, Literal) + assert sym3.initial_value.value == '3.14' - # Include a constant_value of the wrong type + # Include an initial_value of the wrong type sym4 = Symbol("symbol4") with pytest.raises(ValueError) as error: sym4.specialise(DataSymbol, datatype=INTEGER_SINGLE_TYPE, - constant_value=3.14) + initial_value=3.14) assert ("This DataSymbol instance datatype is 'Scalar' " - "meaning the constant value should be" + "meaning the initial value should be" in str(error.value)) + # Attempt to specify that the symbol is constant but without providing an + # initial value. + sym5 = Symbol("symbol5") + with pytest.raises(ValueError) as error: + sym5.specialise(DataSymbol, datatype=INTEGER_SINGLE_TYPE, + is_constant=True) + assert ("DataSymbol 'symbol5' does not have an initial value set and is " + "not imported and therefore cannot be a constant." + in str(error.value)) + # The absence of an initial value is permitted if the symbol has an + # ImportInterface. + csym = ContainerSymbol("some_mod") + sym6 = Symbol("symbol6") + sym6.specialise(DataSymbol, datatype=INTEGER_SINGLE_TYPE, + is_constant=True, interface=ImportInterface(csym)) + assert sym6.is_constant + assert sym6.is_import + def test_datasymbol_can_be_printed(): '''Test that a DataSymbol instance can always be printed. (i.e. is @@ -170,115 +201,135 @@ def test_datasymbol_can_be_printed(): assert ("s3: DataSymbol, Import(container='my_mod')>" in str(sym3)) - sym3 = DataSymbol("s3", INTEGER_SINGLE_TYPE, constant_value=12) + sym3 = DataSymbol("s3", INTEGER_SINGLE_TYPE, initial_value=12) assert ("s3: DataSymbol, Automatic, " - "constant_value=Literal" + "initial_value=Literal" "[value:'12', Scalar]>" in str(sym3)) - + sym3.is_constant = True + assert ("s3: DataSymbol, Automatic, " + "initial_value=Literal" + "[value:'12', Scalar], constant=True>" + in str(sym3)) sym4 = DataSymbol("s4", INTEGER_SINGLE_TYPE, interface=UnresolvedInterface()) assert "s4: DataSymbol, Unresolved>" in str(sym4) -def test_datasymbol_constant_value_setter(): - '''Test that a DataSymbol constant value can be set if given a new valid - constant value.''' +def test_datasymbol_initial_value_setter(): + '''Test that a DataSymbol initial value can be set if given a new valid + value.''' # Test with valid constant values - sym = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=7) - assert sym.constant_value.value == "7" - sym.constant_value = 9 - assert sym.constant_value.value == "9" + sym = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=7) + assert sym.initial_value.value == "7" + sym.initial_value = 9 + assert sym.initial_value.value == "9" - sym = DataSymbol('a', REAL_SINGLE_TYPE, constant_value=3.1415) - assert sym.constant_value.value == "3.1415" - sym.constant_value = 1.0 - assert sym.constant_value.value == "1.0" + sym = DataSymbol('a', REAL_SINGLE_TYPE, initial_value=3.1415) + assert sym.initial_value.value == "3.1415" + sym.initial_value = 1.0 + assert sym.initial_value.value == "1.0" - sym = DataSymbol('a', BOOLEAN_TYPE, constant_value=True) - assert sym.constant_value.value == "true" - sym.constant_value = False - assert sym.constant_value.value == "false" + sym = DataSymbol('a', BOOLEAN_TYPE, initial_value=True) + assert sym.initial_value.value == "true" + sym.initial_value = False + assert sym.initial_value.value == "false" # Test with valid constant expressions lhs = Literal('2', INTEGER_SINGLE_TYPE) rhs = Reference(DataSymbol('constval', INTEGER_SINGLE_TYPE)) ct_expr = BinaryOperation.create(BinaryOperation.Operator.ADD, lhs, rhs) - sym = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=ct_expr) - assert isinstance(sym.constant_value, BinaryOperation) - assert sym.constant_value is ct_expr + sym = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=ct_expr) + assert isinstance(sym.initial_value, BinaryOperation) + assert sym.initial_value is ct_expr - # Test setting it back to non-constant - sym.constant_value = None - assert sym.constant_value is None + # Test setting it back to nothing + sym.initial_value = None + assert sym.initial_value is None -def test_datasymbol_constant_value_setter_invalid(): - '''Test that a DataSymbol constant value setter raises the appropriate +def test_datasymbol_initial_value_setter_invalid(): + '''Test that the DataSymbol initial_value setter raises the appropriate error if an invalid value and/or datatype are given.''' # Test with invalid constant values sym = DataSymbol('a', DeferredType()) with pytest.raises(ValueError) as error: - sym.constant_value = 1.0 - assert ("Error setting constant value for symbol 'a'. A DataSymbol with " - "a constant value must be a scalar or an array but found " - "'DeferredType'." in str(error.value)) + sym.initial_value = 1.0 + assert ("Error setting initial value for symbol 'a'. A DataSymbol with " + "an initial value must be a scalar or an array or of UnknownType " + "but found 'DeferredType'." in str(error.value)) - # Test with invalid constant expressions + # Test with invalid initial expressions ct_expr = Return() with pytest.raises(ValueError) as error: - _ = DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=ct_expr) - assert ("Error setting constant value for symbol 'a'. PSyIR static " + _ = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=ct_expr) + assert ("Error setting initial value for symbol 'a'. PSyIR static " "expressions can only contain PSyIR Literal, Operation, Reference " "or CodeBlock nodes but found:" in str(error.value)) with pytest.raises(ValueError) as error: DataSymbol('a', INTEGER_SINGLE_TYPE, interface=ArgumentInterface(), - constant_value=9) - assert ("Error setting constant value for symbol 'a'. A DataSymbol with " - "an ArgumentInterface can not have a constant value." + initial_value=9) + assert ("Error setting initial value for symbol 'a'. A DataSymbol with " + "an ArgumentInterface can not have an initial value." in str(error.value)) with pytest.raises(ValueError) as error: - DataSymbol('a', INTEGER_SINGLE_TYPE, constant_value=9.81) - assert ("Error setting constant value for symbol 'a'. This DataSymbol " + DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=9.81) + assert ("Error setting initial value for symbol 'a'. This DataSymbol " "instance datatype is 'Scalar' meaning " - "the constant value should be") in str(error.value) + "the initial value should be") in str(error.value) assert "'int'>' but found " in str(error.value) assert "'float'>'." in str(error.value) with pytest.raises(ValueError) as error: - DataSymbol('a', CHARACTER_TYPE, constant_value=42) - assert ("Error setting constant value for symbol 'a'. This DataSymbol " + DataSymbol('a', CHARACTER_TYPE, initial_value=42) + assert ("Error setting initial value for symbol 'a'. This DataSymbol " "instance datatype is 'Scalar' meaning " - "the constant value should be") in str(error.value) + "the initial value should be") in str(error.value) assert "'str'>' but found " in str(error.value) assert "'int'>'." in str(error.value) with pytest.raises(ValueError) as error: - DataSymbol('a', BOOLEAN_TYPE, constant_value="hello") - assert ("Error setting constant value for symbol 'a'. This DataSymbol " + DataSymbol('a', BOOLEAN_TYPE, initial_value="hello") + assert ("Error setting initial value for symbol 'a'. This DataSymbol " "instance datatype is 'Scalar' meaning " - "the constant value should be") in str(error.value) + "the initial value should be") in str(error.value) assert "'bool'>' but found " in str(error.value) assert "'str'>'." in str(error.value) + # is_constant specified but without an initial_value + with pytest.raises(ValueError) as error: + DataSymbol('a', BOOLEAN_TYPE, is_constant=True) + assert ("DataSymbol 'a' does not have an initial value set and is not " + "imported and therefore cannot be a constant" in str(error.value)) + def test_datasymbol_is_constant(): '''Test that the DataSymbol is_constant property returns True if a constant value is set and False if it is not. ''' - sym = DataSymbol('a', INTEGER_SINGLE_TYPE) + sym = DataSymbol('a', INTEGER_SINGLE_TYPE, initial_value=9) assert not sym.is_constant - sym.constant_value = 9 + sym.is_constant = True assert sym.is_constant + with pytest.raises(ValueError) as err: + sym.initial_value = None + assert ("DataSymbol 'a' is a constant and not imported and therefore " + "must have an initial value but got None" in str(err.value)) + sym.is_constant = False + sym.initial_value = None + with pytest.raises(ValueError) as err: + sym.is_constant = True + assert ("DataSymbol 'a' does not have an initial value set and is not " + "imported and therefore cannot be a constant." in str(err.value)) @pytest.mark.usefixtures("parser") -def test_datasymbol_is_constant_codeblock(): - ''' Test that a DataSymbol can have a CodeBlock as its constant value. ''' +def test_datasymbol_initial_value_codeblock(): + ''' Test that a DataSymbol can have a CodeBlock as its initial value. ''' sym = DataSymbol('a', INTEGER_SINGLE_TYPE) reader = FortranStringReader( "INTEGER, PARAMETER :: a=SELECTED_REAL_KIND(6,37)") @@ -287,9 +338,9 @@ def test_datasymbol_is_constant_codeblock(): # the basis for our CodeBlock inits = Fortran2003.walk(fparser2spec, Fortran2003.Initialization) cblock = CodeBlock([inits[0].children[1]], CodeBlock.Structure.EXPRESSION) - assert not sym.is_constant - sym.constant_value = cblock - assert sym.is_constant + assert sym.initial_value is None + sym.initial_value = cblock + assert isinstance(sym.initial_value, CodeBlock) def test_datasymbol_scalar_array(): @@ -314,7 +365,7 @@ def test_datasymbol_copy(): ''' array_type = ArrayType(REAL_SINGLE_TYPE, [1, 2]) - symbol = DataSymbol("myname", array_type, constant_value=None, + symbol = DataSymbol("myname", array_type, initial_value=None, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) new_symbol = symbol.copy() @@ -323,11 +374,12 @@ def test_datasymbol_copy(): assert symbol.name == new_symbol.name assert symbol.datatype == new_symbol.datatype assert symbol.shape == new_symbol.shape - assert symbol.constant_value == new_symbol.constant_value + assert symbol.initial_value == new_symbol.initial_value + assert symbol.is_constant == new_symbol.is_constant assert symbol.interface == new_symbol.interface # Change the properties of the new symbol and check the original - # is not affected. Can't check constant_value yet as we have a + # is not affected. Can't check initial_value yet as we have a # shape value new_symbol._name = "new" new_symbol.datatype = ArrayType(ScalarType(ScalarType.Intrinsic.INTEGER, @@ -357,10 +409,10 @@ def test_datasymbol_copy(): ScalarType.Intrinsic.INTEGER) assert (symbol.shape[1].upper.datatype.precision == ScalarType.Precision.UNDEFINED) - assert not symbol.constant_value + assert symbol.initial_value is None - # Now check constant_value - new_symbol.constant_value = 3 + # Now check initial_value + new_symbol.initial_value = 3 assert isinstance(symbol.shape[0].upper, Literal) assert symbol.shape[0].upper.value == "1" @@ -374,13 +426,13 @@ def test_datasymbol_copy(): ScalarType.Intrinsic.INTEGER) assert (symbol.shape[1].upper.datatype.precision == ScalarType.Precision.UNDEFINED) - assert not symbol.constant_value + assert symbol.initial_value is None def test_datasymbol_copy_properties(): '''Test that the DataSymbol copy_properties method works as expected.''' array_type = ArrayType(REAL_SINGLE_TYPE, [1, 2]) - symbol = DataSymbol("myname", array_type, constant_value=None, + symbol = DataSymbol("myname", array_type, initial_value=None, interface=ArgumentInterface( ArgumentInterface.Access.READWRITE)) @@ -391,7 +443,7 @@ def test_datasymbol_copy_properties(): "") in str(excinfo.value) new_symbol = DataSymbol("other_name", INTEGER_SINGLE_TYPE, - constant_value=7) + initial_value=7) symbol.copy_properties(new_symbol) @@ -399,11 +451,11 @@ def test_datasymbol_copy_properties(): assert symbol.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert symbol.datatype.precision == ScalarType.Precision.SINGLE assert symbol.is_automatic - assert isinstance(symbol.constant_value, Literal) - assert symbol.constant_value.value == "7" - assert (symbol.constant_value.datatype.intrinsic == + assert isinstance(symbol.initial_value, Literal) + assert symbol.initial_value.value == "7" + assert (symbol.initial_value.datatype.intrinsic == symbol.datatype.intrinsic) - assert (symbol.constant_value.datatype.precision == + assert (symbol.initial_value.datatype.precision == symbol.datatype.precision) @@ -437,7 +489,7 @@ def test_datasymbol_shape(): def test_datasymbol_str(): '''Test that the DataSymbol __str__ method returns the expected string''' - data_symbol = DataSymbol("a", INTEGER4_TYPE, constant_value=3) + data_symbol = DataSymbol("a", INTEGER4_TYPE, initial_value=3) assert (data_symbol.__str__() == - "a: DataSymbol, Automatic, constant_value=" + "a: DataSymbol, Automatic, initial_value=" "Literal[value:'3', Scalar]>") diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index bb6cdf15b01..993f3215a83 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -157,7 +157,8 @@ def test_scalartype_datasymbol_precision(intrinsic): # Create an r_def precision symbol with a constant value of 8 data_type = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - precision_symbol = DataSymbol("r_def", data_type, constant_value=8) + precision_symbol = DataSymbol("r_def", data_type, is_constant=True, + initial_value=8) # Set the precision of our ScalarType to be the precision symbol scalar_type = ScalarType(intrinsic, precision_symbol) assert isinstance(scalar_type, ScalarType) @@ -177,7 +178,8 @@ def test_scalartype_not_equal(): intrinsic = ScalarType.Intrinsic.INTEGER data_type = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - precision_symbol = DataSymbol("r_def", data_type, constant_value=8) + precision_symbol = DataSymbol("r_def", data_type, is_constant=True, + initial_value=8) # Set the precision of our ScalarType to be the precision symbol scalar_type = ScalarType(intrinsic, precision_symbol) # Same precision symbol but different intrinsic type @@ -268,7 +270,8 @@ def test_arraytype(): '''Test that the ArrayType class __init__ works as expected. Test the different dimension datatypes that are supported.''' scalar_type = ScalarType(ScalarType.Intrinsic.INTEGER, 4) - data_symbol = DataSymbol("var", scalar_type, constant_value=30) + data_symbol = DataSymbol("var", scalar_type, is_constant=True, + initial_value=30) one = Literal("1", scalar_type) var_plus_1 = BinaryOperation.create( BinaryOperation.Operator.ADD, Reference(data_symbol), one) @@ -382,7 +385,8 @@ def test_arraytype_invalid_shape_dimension_1(): ''' scalar_type = ScalarType(ScalarType.Intrinsic.REAL, 4) - symbol = DataSymbol("fred", scalar_type, constant_value=3.0) + symbol = DataSymbol("fred", scalar_type, is_constant=True, + initial_value=3.0) with pytest.raises(TypeError) as excinfo: _ = ArrayType(scalar_type, [Reference(symbol)]) assert ( @@ -457,7 +461,7 @@ def test_arraytype_invalid_shape_bounds(): assert ("If present, the lower bound in an ArrayType 'shape' must " "represent a value but found ArrayType.Extent" in str(err.value)) scalar_type = ScalarType(ScalarType.Intrinsic.REAL, 4) - symbol = DataSymbol("fred", scalar_type, constant_value=3.0) + symbol = DataSymbol("fred", scalar_type, initial_value=3.0) with pytest.raises(TypeError) as excinfo: _ = ArrayType(scalar_type, [(1, Reference(symbol))]) assert ( @@ -503,7 +507,8 @@ def test_arraytype_str(): '''Test that the ArrayType class str method works as expected.''' scalar_type = ScalarType(ScalarType.Intrinsic.INTEGER, ScalarType.Precision.UNDEFINED) - data_symbol = DataSymbol("var", scalar_type, constant_value=20) + data_symbol = DataSymbol("var", scalar_type, is_constant=True, + initial_value=20) data_type = ArrayType(scalar_type, [10, Reference(data_symbol), (2, Reference(data_symbol)), (Reference(data_symbol), 10)]) diff --git a/src/psyclone/tests/psyir/symbols/symbol_table_test.py b/src/psyclone/tests/psyir/symbols/symbol_table_test.py index 60a9cfc4f59..d288adb9719 100644 --- a/src/psyclone/tests/psyir/symbols/symbol_table_test.py +++ b/src/psyclone/tests/psyir/symbols/symbol_table_test.py @@ -544,7 +544,7 @@ def test_swap_symbol(): assert ("Symbol to remove must be of type Symbol but got 'str'" in str(err.value)) # Test that we reject attempts to swap symbols with different names. - symbol2 = DataSymbol("var2", INTEGER_TYPE, constant_value=6) + symbol2 = DataSymbol("var2", INTEGER_TYPE, initial_value=6) with pytest.raises(SymbolError) as err: sym_table.swap(symbol1, symbol2) assert ("Cannot swap symbols that have different names, got: 'var1' and " @@ -651,7 +651,7 @@ def test_table_merge(): # 'Own' routine symbol excluded. table2.add(RoutineSymbol("dent"), tag="own_routine_symbol") # Precision symbol should be included. - wp_sym = DataSymbol("wp", INTEGER_TYPE, constant_value=8) + wp_sym = DataSymbol("wp", INTEGER_TYPE, is_constant=True, initial_value=8) table2.add(wp_sym) table2.add(DataSymbol("marvin", ScalarType(ScalarType.Intrinsic.REAL, wp_sym))) @@ -707,13 +707,17 @@ def test_merge_container_syms(): tab3.add(csym2) wpsym2 = DataSymbol("wp", INTEGER_TYPE, interface=ImportInterface(csym2)) tab3.add(wpsym2) - dpsym = DataSymbol("dp", INTEGER_TYPE, interface=ImportInterface(csym2)) + dpsym = DataSymbol("dp", INTEGER_TYPE, + interface=ImportInterface(csym2, + orig_name="different_name")) tab3.add(dpsym) tab1.merge(tab3) wp3 = tab1.lookup("wp") assert wp3.interface.container_symbol.name == "slartibartfast" dp3 = tab1.lookup("dp") assert dp3.interface.container_symbol.name == "slartibartfast" + # Check that the dp import renaming is conserved + assert dp3.interface.orig_name == "different_name" # A third table which imports wp from a *different* container. tab4 = SymbolTable() csym3 = ContainerSymbol("magrathea") @@ -800,7 +804,8 @@ def test_swap_symbol_properties(): ''' Test the symboltable swap_properties method ''' # pylint: disable=too-many-statements - symbol1 = DataSymbol("var1", INTEGER_TYPE, constant_value=7) + symbol1 = DataSymbol("var1", INTEGER_TYPE, is_constant=True, + initial_value=7) symbol2 = DataSymbol("dim1", INTEGER_TYPE, interface=ArgumentInterface( ArgumentInterface.Access.READ)) @@ -859,18 +864,18 @@ def test_swap_symbol_properties(): assert symbol1.datatype.shape[0].upper.symbol == symbol2 assert symbol1.datatype.shape[1].upper.symbol == symbol3 assert symbol1.is_argument - assert symbol1.constant_value is None + assert symbol1.initial_value is None assert symbol1.interface.access == ArgumentInterface.Access.READWRITE assert symbol4.name == "var2" assert symbol4.datatype.intrinsic == ScalarType.Intrinsic.INTEGER assert symbol4.datatype.precision == ScalarType.Precision.UNDEFINED assert not symbol4.shape - assert symbol4.is_automatic - assert symbol4.constant_value.value == "7" - assert (symbol4.constant_value.datatype.intrinsic == + assert symbol4.is_static + assert symbol4.initial_value.value == "7" + assert (symbol4.initial_value.datatype.intrinsic == symbol4.datatype.intrinsic) - assert (symbol4.constant_value.datatype.precision == + assert (symbol4.initial_value.datatype.precision == symbol4.datatype.precision) # Check symbol references are unaffected @@ -1701,7 +1706,7 @@ def test_new_symbol(): sym = symtab.new_symbol("generic") assert sym.name == "generic" assert symtab.lookup("generic") is sym - assert type(sym) == Symbol + assert type(sym) is Symbol assert not symtab.tags_dict # Doing it again it will find a new name @@ -1728,8 +1733,8 @@ def test_new_symbol(): datatype=INTEGER_TYPE) assert sym1.name == "routine" assert sym2.name == "data" - assert type(sym1) == RoutineSymbol - assert type(sym2) == DataSymbol + assert type(sym1) is RoutineSymbol + assert type(sym2) is DataSymbol assert symtab.lookup("routine") is sym1 assert symtab.lookup("data") is sym2 # which will be initialised with default values @@ -1739,7 +1744,7 @@ def test_new_symbol(): assert isinstance(sym2.interface, AutomaticInterface) assert isinstance(sym1.datatype, NoType) assert sym2.datatype is INTEGER_TYPE - assert sym2.constant_value is None + assert sym2.initial_value is None # The initialization parameters of new symbols can be given as # keyword parameters @@ -1750,18 +1755,20 @@ def test_new_symbol(): sym2 = symtab.new_symbol("data", symbol_type=DataSymbol, datatype=INTEGER_TYPE, visibility=Symbol.Visibility.PRIVATE, - constant_value=3) + is_constant=True, + initial_value=3) assert sym1.name == "routine_1" assert sym2.name == "data_1" - assert type(sym1) == RoutineSymbol - assert type(sym2) == DataSymbol + assert type(sym1) is RoutineSymbol + assert type(sym2) is DataSymbol assert symtab.lookup("routine_1") is sym1 assert symtab.lookup("data_1") is sym2 assert sym1.visibility is Symbol.Visibility.PRIVATE assert sym2.visibility is Symbol.Visibility.PRIVATE assert isinstance(sym1.datatype, DeferredType) assert sym2.datatype is INTEGER_TYPE - assert sym2.constant_value is not None + assert sym2.initial_value is not None + assert sym2.is_constant is True # Check that symbol_type only accepts symbols with pytest.raises(TypeError) as err: @@ -1811,12 +1818,14 @@ def test_find_or_create(): symbol_type=DataSymbol, datatype=INTEGER_TYPE, visibility=Symbol.Visibility.PRIVATE, - constant_value=3) + is_constant=True, + initial_value=3) assert new2.name == "new2" assert isinstance(new2, DataSymbol) assert new2.datatype is INTEGER_TYPE assert new2.visibility is Symbol.Visibility.PRIVATE - assert new2.constant_value.value == "3" + assert new2.initial_value.value == "3" + assert new2.is_constant is True assert symtab.lookup_with_tag("mytag") is new2 # Check that it fails if the named Symbol exists but is not of the @@ -1845,7 +1854,7 @@ def test_find_or_create_tag(): assert isinstance(tag2, Symbol) assert symtab.lookup_with_tag("tag2") is tag2 # By default it is a generic symbol with the same name as the tag - assert type(tag2) == Symbol + assert type(tag2) is Symbol assert tag2.name == "tag2" # If the operation is repeated it returns the already created symbol @@ -1857,12 +1866,14 @@ def test_find_or_create_tag(): symbol_type=DataSymbol, datatype=INTEGER_TYPE, visibility=Symbol.Visibility.PRIVATE, - constant_value=3) + is_constant=True, + initial_value=3) assert symtab.lookup_with_tag("tag3") is tag3 - assert type(tag3) == DataSymbol + assert type(tag3) is DataSymbol assert tag3.visibility is Symbol.Visibility.PRIVATE assert tag3.datatype is INTEGER_TYPE - assert tag3.constant_value is not None + assert tag3.is_constant is True + assert tag3.initial_value is not None # It can be given a different root_name tag4 = symtab.find_or_create_tag("tag4", root_name="var") @@ -2175,7 +2186,7 @@ def test_resolve_imports(fortran_reader, tmpdir, monkeypatch): # b_1 have all relevant info now assert isinstance(b_1, DataSymbol) assert b_1.datatype.intrinsic.name == 'INTEGER' - assert b_1.constant_value.value == "10" + assert b_1.initial_value.value == "10" # The interface is also updated updated now because we know where it comes # from assert isinstance(b_1.interface, ImportInterface) @@ -2312,7 +2323,7 @@ def test_resolve_imports_private_symbols(fortran_reader, tmpdir, monkeypatch): # is mentioned by the accessibility statement public1 = symtab.lookup("name_public1") # pylint: disable=unidiomatic-typecheck - assert type(public1) == Symbol + assert type(public1) is Symbol # This should succeed because all name clashes are protected by proper # private accessibility diff --git a/src/psyclone/tests/psyir/tools/dependency_tools_test.py b/src/psyclone/tests/psyir/tools/dependency_tools_test.py index 943684d664e..4860522ddb4 100644 --- a/src/psyclone/tests/psyir/tools/dependency_tools_test.py +++ b/src/psyclone/tests/psyir/tools/dependency_tools_test.py @@ -220,7 +220,7 @@ def test_arrays_parallelise(fortran_reader): loops = psyir.children[0].children[0:4] dep_tools = DependencyTools() - # Write to array that does not depend on parallel loop variable + # Write to array that does not depend on the parallel loop variable # Test that right default variable name (outer loop jj) is used. parallel = dep_tools.can_loop_be_parallelised(loops[0]) assert parallel is False @@ -230,12 +230,12 @@ def test_arrays_parallelise(fortran_reader): assert msg.code == DTCode.ERROR_WRITE_WRITE_RACE assert msg.var_names == ["mask(jk,jk)"] - # Write to array that does not depend on parallel loop variable + # Write to array that does not depend on the parallel loop variable parallel = dep_tools.can_loop_be_parallelised(loops[1]) assert parallel is True assert dep_tools.get_all_messages() == [] - # Use parallel loop variable in more than one dimension + # Use the parallel loop variable in more than one dimension parallel = dep_tools.can_loop_be_parallelised(loops[2]) assert parallel is True @@ -779,3 +779,72 @@ def test_da_array_expression(fortran_reader): dep_tools = DependencyTools() result = dep_tools.can_loop_be_parallelised(loops[0]) assert result is False + + +# ----------------------------------------------------------------------------- +def test_reserved_words(fortran_reader): + '''Tests that using a reserved Python word ('lambda' here') as a loop + variable, which will be renamed when converting to SymPy, works as + expected. Also make sure that name clashes are handled as expected by + declaring local symbols lambda_1 (which will clash with the renamed + lambda) and lambda_1_1 (which will clash with the renamed lambda_1). + Otherwise this test is identical to test_arrays_parallelise. + ''' + source = '''program test + integer ji, lambda, jk + integer, parameter :: jpi=5, jpj=10 + real, dimension(jpi,jpi) :: mask, umask, lambda_1, lambda_1_1 + do lambda = 1, jpj ! loop 0 + do ji = 1, jpi + mask(jk, jk) = -1.0d0 + end do + end do + do lambda = 1, jpj ! loop 1 + do ji = 1, jpi + mask(ji, lambda+lambda_1+lambda_1_1) = umask(jk, jk) + end do + end do + do lambda = 1, jpj ! loop 2 + do ji = 1, jpi + mask(lambda, lambda) = umask(ji, lambda) + end do + end do + do lambda = 1, jpj ! loop 3 + do ji = 1, jpi + mask(ji, lambda) = mask(ji, lambda+1) + end do + end do + end program test''' + + psyir = fortran_reader.psyir_from_source(source) + loops = psyir.children[0].children[0:4] + dep_tools = DependencyTools() + + # Write to array that does not depend on the parallel loop variable + # Test that right default variable name (outer loop lambda) is used. + parallel = dep_tools.can_loop_be_parallelised(loops[0]) + assert parallel is False + msg = dep_tools.get_all_messages()[0] + assert ("The write access to 'mask(jk,jk)' causes a write-write race " + "condition" in str(msg)) + assert msg.code == DTCode.ERROR_WRITE_WRITE_RACE + assert msg.var_names == ["mask(jk,jk)"] + + # Write to array that does not depend on the parallel loop variable + parallel = dep_tools.can_loop_be_parallelised(loops[1]) + assert parallel is True + assert dep_tools.get_all_messages() == [] + + # Use the parallel loop variable in more than one dimension + parallel = dep_tools.can_loop_be_parallelised(loops[2]) + assert parallel is True + + # Use a stencil access (with write), which prevents parallelisation + parallel = dep_tools.can_loop_be_parallelised(loops[3]) + assert parallel is False + msg = dep_tools.get_all_messages()[0] + assert ("The write access to 'mask(ji,lambda)' and to " + "'mask(ji,lambda + 1)' are dependent and cannot be parallelised." + in str(msg)) + assert msg.code == DTCode.ERROR_DEPENDENCY + assert msg.var_names == ["mask(ji,lambda)", "mask(ji,lambda + 1)"] diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py index bdc93521938..96ccca5b89e 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py @@ -56,7 +56,7 @@ def create_matmul(): symbol_table = SymbolTable() one = Literal("1", INTEGER_TYPE) two = Literal("2", INTEGER_TYPE) - index = DataSymbol("idx", INTEGER_TYPE, constant_value=3) + index = DataSymbol("idx", INTEGER_TYPE, is_constant=True, initial_value=3) symbol_table.add(index) array_type = ArrayType(REAL_TYPE, [5, 10, 15]) mat_symbol = DataSymbol("x", array_type) @@ -166,7 +166,8 @@ def test_get_array_bound(): new nodes are created each time the utility is called. ''' - scalar_symbol = DataSymbol("n", INTEGER_TYPE, constant_value=20) + scalar_symbol = DataSymbol("n", INTEGER_TYPE, is_constant=True, + initial_value=20) array_type = ArrayType(REAL_TYPE, [10, Reference(scalar_symbol)]) array_symbol = DataSymbol("x", array_type) reference = Reference(array_symbol) @@ -368,9 +369,9 @@ def test_validate5(): _ = Assignment.create(array, matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) - assert ("Expected children of a MATMUL BinaryOperation to be references, " - "but found 'BinaryOperation', 'BinaryOperation'." - in str(excinfo.value)) + assert ("Expected result and operands of MATMUL BinaryOperation to be " + "references, but found: 'x(10) = MATMUL(x(10) * x(10), x(10) * " + "x(10))\n'." in str(excinfo.value)) def test_validate6(): @@ -387,9 +388,8 @@ def test_validate6(): _ = Assignment.create(scalar.copy(), matmul) with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) - assert ("Transformation Error: Expected children of a MATMUL " - "BinaryOperation to be references to arrays but found " - "'DataSymbol', 'DataSymbol' for 'x', 'x'." in str(excinfo.value)) + assert ("Expected result and operands of MATMUL BinaryOperation to be " + "references to arrays but found" in str(excinfo.value)) def test_validate_structure_accesses(fortran_reader): @@ -409,9 +409,8 @@ def test_validate_structure_accesses(fortran_reader): assign = psyir.walk(Assignment)[0] with pytest.raises(TransformationError) as err: trans.apply(assign.rhs) - assert ("Expected children of a MATMUL BinaryOperation to be references " - "to arrays but found 'DataSymbol', 'DataSymbol' for 'grid', " - "'grid_inv'" in str(err.value)) + assert ("Expected result and operands of MATMUL BinaryOperation to be " + "references to arrays but found" in str(err.value)) def test_validate7(): @@ -484,7 +483,7 @@ def test_validate10(): matmul = create_matmul() matrix = matmul.children[0] matrix.children[0] = Literal("1", INTEGER_TYPE) - with pytest.raises(NotImplementedError) as excinfo: + with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("To use matmul2code_trans on matmul, the first two indices of the " "1st argument 'x' must be full ranges." in str(excinfo.value)) @@ -502,7 +501,7 @@ def test_validate11(): matrix = matmul.children[0] my_range = matrix.children[0].copy() matrix.children[2] = my_range - with pytest.raises(NotImplementedError) as excinfo: + with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("To use matmul2code_trans on matmul, only the first two indices " "of the 1st argument are permitted to be Ranges but " @@ -520,7 +519,7 @@ def test_validate12(): matmul = create_matmul() vector = matmul.children[1] vector.children[0] = Literal("1", INTEGER_TYPE) - with pytest.raises(NotImplementedError) as excinfo: + with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("To use matmul2code_trans on matmul, the first index of the 2nd " "argument 'y' must be a full range." in str(excinfo.value)) @@ -535,11 +534,11 @@ def test_validate_2nd_dim_2nd_arg(): matrix2 = matmul.children[1] matrix2.children[1] = Range.create(Literal("1", INTEGER_TYPE), Literal("2", INTEGER_TYPE)) - with pytest.raises(NotImplementedError) as excinfo: + with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("To use matmul2code_trans on matmul for a matrix-matrix " "multiplication, the second index of the 2nd argument 'y' must " - "be a full range." in str(excinfo)) + "be a full range." in str(excinfo.value)) def test_validate13(): @@ -554,7 +553,7 @@ def test_validate13(): vector = matmul.children[1] my_range = vector.children[0].copy() vector.children[2] = my_range - with pytest.raises(NotImplementedError) as excinfo: + with pytest.raises(TransformationError) as excinfo: trans.validate(matmul) assert ("To use matmul2code_trans on matmul, only the first two " "indices of the 2nd argument are permitted to be a Range but " @@ -572,6 +571,65 @@ def test_validate14(): trans.validate(matmul) +def test_validate_matmat_with_slices_on_rhs(fortran_reader): + ''' + Check that the validate method refuses matrix-matrix operations with + array slides in its lhs. + + ''' + psyir = fortran_reader.psyir_from_source( + "subroutine my_sub()\n" + " real, dimension(2,6) :: jac\n" + " real, dimension(6,3) :: jac_inv\n" + " real, dimension(10,10) :: result\n" + " result(2:4,2:5) = matmul(jac(:,:), jac_inv(:,:))\n" + "end subroutine my_sub\n") + trans = Matmul2CodeTrans() + assign = psyir.walk(Assignment)[0] + with pytest.raises(TransformationError) as excinfo: + trans.validate(assign.rhs) + assert ("To use matmul2code_trans on matmul, each range on the result " + "variable 'result' must be a full range but found " + "result(2:4,2:5)" in str(excinfo.value)) + + +def test_validate_matmat_with_same_mem(fortran_reader): + ''' + Check that the validate method refuses cases where one of the operands + is also the lhs of the matrix multiplication. + + ''' + psyir = fortran_reader.psyir_from_source( + "subroutine my_sub()\n" + " real, dimension(2,2) :: jac\n" + " real, dimension(2,2) :: jac_inv\n" + " jac = matmul(jac(:,:), jac_inv(:,:))\n" + "end subroutine my_sub\n") + trans = Matmul2CodeTrans() + assign = psyir.walk(Assignment)[0] + with pytest.raises(TransformationError) as excinfo: + trans.validate(assign.rhs) + assert ("Transformation Error: 'jac' is the result location and one of the" + " MATMUL operators. This is not supported." in str(excinfo.value)) + + # In the version below we can not guarantee whether the memory is the same + psyir = fortran_reader.psyir_from_source( + "subroutine my_sub()\n" + " real, dimension(2,2) :: jac\n" + " real, dimension(2,2), pointer :: jac_inv\n" + " real, dimension(2,2), pointer :: result\n" + " result = matmul(jac(:,:), jac_inv(:,:))\n" + "end subroutine my_sub\n") + trans = Matmul2CodeTrans() + assign = psyir.walk(Assignment)[0] + with pytest.raises(TransformationError) as excinfo: + trans.validate(assign.rhs) + assert ("Transformation Error: Expected result and operands of MATMUL " + "BinaryOperation to be references to arrays but found 'result: " + "DataSymbol @brief A module providing integer field related classes. !> !> @details This is a version of a field object that can hold integer data !> values. It contains both a representation of an integer field which provides !> no access to the underlying data (to be used in the algorithm layer) and an -!> accessor class (to be used in the Psy layer) are provided. +!> accessor class (to be used in the Psy layer). module integer_field_mod @@ -52,7 +52,6 @@ module integer_field_mod str_def, integer_type use function_space_mod, only: function_space_type use mesh_mod, only: mesh_type - use field_parent_mod, only: field_parent_type, & field_parent_proxy_type, & write_interface, read_interface, & @@ -80,6 +79,9 @@ module integer_field_mod !> The integer values of the field integer(kind=i_def), allocatable :: data( : ) + !> Enable field to point to bespoke data provided by application + !> instead of having allocated data + integer(kind=i_def), pointer :: override_data( : ) ! IO interface procedure pointers @@ -93,7 +95,7 @@ module integer_field_mod procedure, public :: initialise => field_initialiser ! Routine to return a deep copy of a field including all its data - procedure, public :: copy_field + procedure, public :: copy_field_serial ! Routine to return a deep, but empty copy of a field procedure, public :: copy_field_properties @@ -153,6 +155,9 @@ module integer_field_mod !> Routine to destroy field_type procedure :: field_final + !> Checks if field has been initialised + procedure, public :: is_initialised + !> Finalizers for scalar and arrays of field_type objects final :: field_destructor_scalar, & field_destructor_array1d, & @@ -163,22 +168,23 @@ module integer_field_mod end type integer_field_type -!______integer_field_pointer_type_______________________________________________ +!______integer_field_pointer_type_______________________________________________________ !> a class to hold a pointer to an integer field in an object that is a child !> of the pure abstract field class type, extends(pure_abstract_field_type), public :: integer_field_pointer_type + private !> A pointer to an integer field type(integer_field_type), pointer, public :: field_ptr contains !> Initialiser for a field. May only be called once. - procedure, public :: integer_field_pointer_initialiser + procedure, public :: initialise => integer_field_pointer_initialiser !> Finaliser for a field pointer object final :: integer_field_pointer_destructor end type integer_field_pointer_type -!______integer_field_proxy_type_______________________________________________ +!______integer_field_proxy_type_______________________________________________________ !> Psy layer representation of a field. !> @@ -253,7 +259,12 @@ type(integer_field_proxy_type) function get_proxy(self) ! Call the routine that initialises the proxy for data held in the parent call self%field_parent_proxy_initialiser(get_proxy) - get_proxy%data => self%data + if (allocated(self%data))then + get_proxy%data => self%data + else + ! Fields can alternatively point to bespoke data + get_proxy%data => self%override_data + end if end function get_proxy @@ -263,37 +274,49 @@ end function get_proxy !> @param [in] name The name of the field. 'none' is a reserved name !> @param [in] ndata_first Whether mutlidata fields have data ordered by !> the multidata dimension first - !> @param [in] advection_flag Whether the field is to be advected + !> @param [in] override_data Optional alternative data that can be attached to field !> subroutine field_initialiser(self, & vector_space, & name, & ndata_first, & - advection_flag) + override_data) - use log_mod, only : log_event, & - LOG_LEVEL_ERROR implicit none class(integer_field_type), intent(inout) :: self type(function_space_type), pointer, intent(in) :: vector_space character(*), optional, intent(in) :: name logical, optional, intent(in) :: ndata_first - logical, optional, intent(in) :: advection_flag + integer(i_def), target, optional, intent(in) :: override_data( : ) + + character(str_def) :: local_name - ! If there's already data in the field, destruct it - ! ready for re-initialisation - if(allocated(self%data))call field_destructor_scalar(self) + if ( present(name) ) then + local_name = name + else + local_name = 'none' + end if + + ! In case the field is already initialised, destruct it ready for + ! re-initialisation + call field_destructor_scalar(self) call self%field_parent_initialiser(vector_space, & - name=name, & + name=local_name, & fortran_type=integer_type, & fortran_kind=i_def, & - ndata_first= ndata_first, & - advection_flag=advection_flag) + ndata_first=ndata_first) - ! Create space for holding field data - allocate( self%data(self%vspace%get_last_dof_halo()) ) + ! Associate data with the field + if (present(override_data))then + ! Override normal field data if an alternative was provided + self%override_data => override_data + else + ! Create space for holding field data + allocate( self%data(vector_space%get_last_dof_halo()) ) + self%override_data => null() + end if end subroutine field_initialiser @@ -312,7 +335,7 @@ end subroutine integer_field_pointer_initialiser ! ! The following finaliser doesn't do anything. Without it, the Gnu compiler ! tries to create its own, but only ends up producing an Internal Compiler - ! Error, so its included here to prevent that. + ! Error, so it is included here to prevent that. subroutine integer_field_pointer_destructor(self) implicit none type(integer_field_pointer_type), intent(inout) :: self @@ -323,13 +346,20 @@ end subroutine integer_field_pointer_destructor !> !> @param[out] dest field object into which the copy will be made !> @param[in] name An optional argument that provides an identifying name - subroutine copy_field(self, dest, name) + subroutine copy_field_serial(self, dest, name) + use log_mod, only : log_event, & + LOG_LEVEL_ERROR implicit none class(integer_field_type), target, intent(in) :: self class(integer_field_type), target, intent(out) :: dest character(*), optional, intent(in) :: name + if ( .not. allocated(self%data) ) then + call log_event( 'Error: copy_field_serial: Copied field must have field data', & + LOG_LEVEL_ERROR ) + end if + if (present(name)) then call self%copy_field_properties(dest, name) else @@ -339,24 +369,30 @@ subroutine copy_field(self, dest, name) dest%data(:) = self%data(:) call self%copy_field_parent(dest) - end subroutine copy_field + end subroutine copy_field_serial !> Create a new empty field that inherits the properties of the source field. !> !> @param[out] dest field object into which the copy will be made !> @param[in] name An optional argument that provides an identifying name subroutine copy_field_properties(self, dest, name) - use log_mod, only : log_event, & - LOG_LEVEL_ERROR + implicit none class(integer_field_type), target, intent(in) :: self class(integer_field_type), target, intent(out) :: dest character(*), optional, intent(in) :: name + type(function_space_type), pointer :: function_space => null() + + ! Get function space from parent + function_space => self%get_function_space() + if (present(name)) then - call dest%initialise(self%vspace, name, self%is_advected()) + call dest%initialise(vector_space = function_space, & + name = name) else - call dest%initialise( self%vspace, self%get_name(), self%is_advected() ) + call dest%initialise(vector_space = function_space, & + name = self%get_name()) end if dest%write_method => self%write_method @@ -384,8 +420,8 @@ subroutine field_type_assign(dest, source) write(log_scratch_space,'(A,A)')& '"field2=field1" syntax no longer supported. '// & - 'Use "call field1%copy_field(field2)". Field: ', & - source%name + 'Use "setval_X(field2, field1)". Field: ', & + source%get_name() call log_event(log_scratch_space,LOG_LEVEL_INFO ) allocate(dest%data(1)) ! allocate the same memory twice, to force allocate(dest%data(2)) ! an error and generate a stack trace @@ -407,14 +443,23 @@ subroutine field_final(self) deallocate(self%data) end if - nullify( self%vspace, & - self%write_method, & + nullify( self%write_method, & self%read_method, & self%checkpoint_write_method, & self%checkpoint_read_method ) end subroutine field_final + !> Return a logical indicating whether the field has been initialised + function is_initialised(self) result(initialised) + implicit none + class(integer_field_type), intent(in) :: self + logical(l_def) :: initialised + + initialised = allocated(self%data) + + end function is_initialised + !> Finalizer for a scalar field_type instance. subroutine field_destructor_scalar(self) @@ -484,7 +529,6 @@ subroutine get_write_behaviour(self, write_behaviour) end subroutine get_write_behaviour !> Setter for read behaviour - !> @param[in,out] self field_type !> @param [in] read_behaviour - pointer to procedure implementing read method subroutine set_read_behaviour(self, read_behaviour) implicit none @@ -604,18 +648,22 @@ subroutine log_field( self, dump_level, label ) integer(i_def), intent(in) :: dump_level character( * ), intent(in) :: label - integer(i_def) :: cell - integer(i_def) :: layer - integer(i_def) :: df - integer(i_def), pointer :: map(:) => null() + type(function_space_type), pointer :: function_space => null() + integer(i_def) :: cell + integer(i_def) :: layer + integer(i_def) :: df + integer(i_def), pointer :: map(:) => null() + + ! Get function space from parent + function_space => self%get_function_space() write( log_scratch_space, '( A, A)' ) trim( label ), " =[" call log_event( log_scratch_space, dump_level ) - do cell=1,self%vspace%get_ncell() - map => self%vspace%get_cell_dofmap( cell ) - do df=1,self%vspace%get_ndf() - do layer=0,self%vspace%get_nlayers()-1 + do cell=1,function_space%get_ncell() + map => function_space%get_cell_dofmap( cell ) + do df=1,function_space%get_ndf() + do layer=0,function_space%get_nlayers()-1 write( log_scratch_space, '( I6, I6, I6, I16 )' ) & cell, df, layer+1, self%data( map( df ) + layer ) call log_event( log_scratch_space, dump_level ) @@ -642,11 +690,15 @@ subroutine log_dofs( self, log_level, label ) integer(i_def), intent(in) :: log_level character( * ), intent(in) :: label + type(function_space_type), pointer :: function_space => null() integer(i_def) :: df + ! Get function space from parent + function_space => self%get_function_space() + call log_event( label, log_level ) - do df=1,self%vspace%get_undf() + do df=1,function_space%get_undf() write( log_scratch_space, '( I6, I16 )' ) df,self%data( df ) call log_event( log_scratch_space, log_level ) end do @@ -668,25 +720,30 @@ subroutine log_minmax( self, log_level, label ) class( integer_field_type ), target, intent(in) :: self integer(i_def), intent(in) :: log_level character( * ), intent(in) :: label + type(function_space_type), pointer :: function_space => null() integer(i_def) :: i integer(i_def) :: l_min, l_max integer(i_def) :: answer_min, answer_max + ! Get function space from parent + function_space => self%get_function_space() + ! If we aren't going to log the min and max then we don't need to ! do any further work here. if ( log_level < application_log_level() ) return l_max = self%data(1) l_min = self%data(1) - do i = 2, self%vspace%get_last_dof_owned() + do i = 2, function_space%get_last_dof_owned() if( self%data(i) > l_max ) l_max = self%data(i) if( self%data(i) < l_min ) l_min = self%data(i) end do - ! Functions global_max() and global_min() rely on MPI comms - ! so these calls are disabled here. Max and min are simply - ! l_max and l_min. - !call global_max( l_max, answer_max ) - !call global_min( l_min, answer_min ) + ! Functions 'global_max()' and 'global_min()' rely on MPI + ! comms which are not supported by the PSyclone test LFRic + ! infrastructure. Hence, these calls are disabled here and + ! 'max' and 'min' are simply 'l_max' and 'l_min'. + !call global_mpi%global_max( l_max, answer_max ) + !call global_mpi%global_min( l_min, answer_min ) answer_max = l_max answer_min = l_min @@ -712,20 +769,25 @@ subroutine log_absmax( self, log_level, label ) class( integer_field_type ), target, intent(in) :: self integer(i_def), intent(in) :: log_level character( * ), intent(in) :: label + type(function_space_type), pointer :: function_space => null() integer(i_def) :: i integer(i_def) :: l_max, answer + ! Get function space from parent + function_space => self%get_function_space() + ! If we aren't going to log the abs max then we don't need to ! do any further work here. if ( log_level < application_log_level() ) return l_max = abs(self%data(1)) - do i = 2, self%vspace%get_last_dof_owned() + do i = 2, function_space%get_last_dof_owned() if( abs(self%data(i)) > l_max ) l_max = abs(self%data(i)) end do - ! Function global_max() relies on MPI comms so this call - ! is disabled here. Max is simply l_max. - !call global_max( l_max, answer ) + ! Function 'global_max()' relies on MPI comms which are not + ! supported by the PSyclone test LFRic infrastructure. Hence, + ! this call is disabled here and 'max' is simply 'l_max'. + !call global_mpi%global_max( l_max, answer ) answer = l_max write( log_scratch_space, '( A, A, E16.8 )' ) & @@ -764,8 +826,7 @@ end subroutine write_field !> @param [in] field_name - field name / id to read subroutine read_field( self, field_name) use log_mod, only : log_event, & - LOG_LEVEL_ERROR, & - LOG_LEVEL_INFO + LOG_LEVEL_ERROR implicit none @@ -904,6 +965,7 @@ subroutine halo_exchange_start( self, depth ) call log_event( 'Error in field: '// & 'attempt to exchange halos with depth out of range.', & LOG_LEVEL_ERROR ) + else call log_event( 'Error in field: '// & 'attempt to exchange halos (a write operation) on a read-only field.', & @@ -912,7 +974,7 @@ subroutine halo_exchange_start( self, depth ) end subroutine halo_exchange_start - !! Wait for a halo exchange to complete + !! Wait for an asynchronous halo exchange to complete !! subroutine halo_exchange_finish( self, depth ) diff --git a/src/psyclone/tests/test_files/dynamo0p3/infrastructure/field/integer_field_mod.f90 b/src/psyclone/tests/test_files/dynamo0p3/infrastructure/field/integer_field_mod.f90 index f1a5e0c0634..45ccf5a4078 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/infrastructure/field/integer_field_mod.f90 +++ b/src/psyclone/tests/test_files/dynamo0p3/infrastructure/field/integer_field_mod.f90 @@ -10,7 +10,7 @@ !------------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Modifications copyright (c) 2021, Science and Technology Facilities +! Modifications copyright (c) 2021-2023, Science and Technology Facilities ! Council ! All rights reserved. ! @@ -40,13 +40,13 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Modified by: I. Kavcic, Met Office - +! !> @brief A module providing integer field related classes. !> !> @details This is a version of a field object that can hold integer data !> values. It contains both a representation of an integer field which provides !> no access to the underlying data (to be used in the algorithm layer) and an -!> accessor class (to be used in the Psy layer) are provided. +!> accessor class (to be used in the Psy layer). module integer_field_mod @@ -54,7 +54,6 @@ module integer_field_mod str_def, integer_type use function_space_mod, only: function_space_type use mesh_mod, only: mesh_type - use field_parent_mod, only: field_parent_type, & field_parent_proxy_type, & write_interface, read_interface, & @@ -82,6 +81,9 @@ module integer_field_mod !> The integer values of the field integer(kind=i_def), allocatable :: data( : ) + !> Enable field to point to bespoke data provided by application + !> instead of having allocated data + integer(kind=i_def), pointer :: override_data( : ) ! IO interface procedure pointers @@ -95,7 +97,7 @@ module integer_field_mod procedure, public :: initialise => field_initialiser ! Routine to return a deep copy of a field including all its data - procedure, public :: copy_field + procedure, public :: copy_field_serial ! Routine to return a deep, but empty copy of a field procedure, public :: copy_field_properties @@ -155,6 +157,9 @@ module integer_field_mod !> Routine to destroy field_type procedure :: field_final + !> Checks if field has been initialised + procedure, public :: is_initialised + !> Finalizers for scalar and arrays of field_type objects final :: field_destructor_scalar, & field_destructor_array1d, & @@ -165,22 +170,23 @@ module integer_field_mod end type integer_field_type -!______integer_field_pointer_type_______________________________________________ +!______integer_field_pointer_type_______________________________________________________ !> a class to hold a pointer to an integer field in an object that is a child !> of the pure abstract field class type, extends(pure_abstract_field_type), public :: integer_field_pointer_type + private !> A pointer to an integer field type(integer_field_type), pointer, public :: field_ptr contains !> Initialiser for a field. May only be called once. - procedure, public :: integer_field_pointer_initialiser + procedure, public :: initialise => integer_field_pointer_initialiser !> Finaliser for a field pointer object final :: integer_field_pointer_destructor end type integer_field_pointer_type -!______integer_field_proxy_type_______________________________________________ +!______integer_field_proxy_type_______________________________________________________ !> Psy layer representation of a field. !> @@ -255,7 +261,12 @@ type(integer_field_proxy_type) function get_proxy(self) ! Call the routine that initialises the proxy for data held in the parent call self%field_parent_proxy_initialiser(get_proxy) - get_proxy%data => self%data + if (allocated(self%data))then + get_proxy%data => self%data + else + ! Fields can alternatively point to bespoke data + get_proxy%data => self%override_data + end if end function get_proxy @@ -265,37 +276,49 @@ end function get_proxy !> @param [in] name The name of the field. 'none' is a reserved name !> @param [in] ndata_first Whether mutlidata fields have data ordered by !> the multidata dimension first - !> @param [in] advection_flag Whether the field is to be advected + !> @param [in] override_data Optional alternative data that can be attached to field !> subroutine field_initialiser(self, & vector_space, & name, & ndata_first, & - advection_flag) + override_data) - use log_mod, only : log_event, & - LOG_LEVEL_ERROR implicit none class(integer_field_type), intent(inout) :: self type(function_space_type), pointer, intent(in) :: vector_space character(*), optional, intent(in) :: name logical, optional, intent(in) :: ndata_first - logical, optional, intent(in) :: advection_flag + integer(i_def), target, optional, intent(in) :: override_data( : ) + + character(str_def) :: local_name - ! If there's already data in the field, destruct it - ! ready for re-initialisation - if(allocated(self%data))call field_destructor_scalar(self) + if ( present(name) ) then + local_name = name + else + local_name = 'none' + end if + + ! In case the field is already initialised, destruct it ready for + ! re-initialisation + call field_destructor_scalar(self) call self%field_parent_initialiser(vector_space, & - name=name, & + name=local_name, & fortran_type=integer_type, & fortran_kind=i_def, & - ndata_first= ndata_first, & - advection_flag=advection_flag) + ndata_first=ndata_first) - ! Create space for holding field data - allocate( self%data(self%vspace%get_last_dof_halo()) ) + ! Associate data with the field + if (present(override_data))then + ! Override normal field data if an alternative was provided + self%override_data => override_data + else + ! Create space for holding field data + allocate( self%data(vector_space%get_last_dof_halo()) ) + self%override_data => null() + end if end subroutine field_initialiser @@ -314,7 +337,7 @@ end subroutine integer_field_pointer_initialiser ! ! The following finaliser doesn't do anything. Without it, the Gnu compiler ! tries to create its own, but only ends up producing an Internal Compiler - ! Error, so its included here to prevent that. + ! Error, so it is included here to prevent that. subroutine integer_field_pointer_destructor(self) implicit none type(integer_field_pointer_type), intent(inout) :: self @@ -325,13 +348,20 @@ end subroutine integer_field_pointer_destructor !> !> @param[out] dest field object into which the copy will be made !> @param[in] name An optional argument that provides an identifying name - subroutine copy_field(self, dest, name) + subroutine copy_field_serial(self, dest, name) + use log_mod, only : log_event, & + LOG_LEVEL_ERROR implicit none class(integer_field_type), target, intent(in) :: self class(integer_field_type), target, intent(out) :: dest character(*), optional, intent(in) :: name + if ( .not. allocated(self%data) ) then + call log_event( 'Error: copy_field_serial: Copied field must have field data', & + LOG_LEVEL_ERROR ) + end if + if (present(name)) then call self%copy_field_properties(dest, name) else @@ -341,24 +371,30 @@ subroutine copy_field(self, dest, name) dest%data(:) = self%data(:) call self%copy_field_parent(dest) - end subroutine copy_field + end subroutine copy_field_serial !> Create a new empty field that inherits the properties of the source field. !> !> @param[out] dest field object into which the copy will be made !> @param[in] name An optional argument that provides an identifying name subroutine copy_field_properties(self, dest, name) - use log_mod, only : log_event, & - LOG_LEVEL_ERROR + implicit none class(integer_field_type), target, intent(in) :: self class(integer_field_type), target, intent(out) :: dest character(*), optional, intent(in) :: name + type(function_space_type), pointer :: function_space => null() + + ! Get function space from parent + function_space => self%get_function_space() + if (present(name)) then - call dest%initialise(self%vspace, name, self%is_advected()) + call dest%initialise(vector_space = function_space, & + name = name) else - call dest%initialise( self%vspace, self%get_name(), self%is_advected() ) + call dest%initialise(vector_space = function_space, & + name = self%get_name()) end if dest%write_method => self%write_method @@ -386,8 +422,8 @@ subroutine field_type_assign(dest, source) write(log_scratch_space,'(A,A)')& '"field2=field1" syntax no longer supported. '// & - 'Use "call field1%copy_field(field2)". Field: ', & - source%name + 'Use "setval_X(field2, field1)". Field: ', & + source%get_name() call log_event(log_scratch_space,LOG_LEVEL_INFO ) allocate(dest%data(1)) ! allocate the same memory twice, to force allocate(dest%data(2)) ! an error and generate a stack trace @@ -409,14 +445,23 @@ subroutine field_final(self) deallocate(self%data) end if - nullify( self%vspace, & - self%write_method, & + nullify( self%write_method, & self%read_method, & self%checkpoint_write_method, & self%checkpoint_read_method ) end subroutine field_final + !> Return a logical indicating whether the field has been initialised + function is_initialised(self) result(initialised) + implicit none + class(integer_field_type), intent(in) :: self + logical(l_def) :: initialised + + initialised = allocated(self%data) + + end function is_initialised + !> Finalizer for a scalar field_type instance. subroutine field_destructor_scalar(self) @@ -486,7 +531,6 @@ subroutine get_write_behaviour(self, write_behaviour) end subroutine get_write_behaviour !> Setter for read behaviour - !> @param[in,out] self field_type !> @param [in] read_behaviour - pointer to procedure implementing read method subroutine set_read_behaviour(self, read_behaviour) implicit none @@ -606,18 +650,22 @@ subroutine log_field( self, dump_level, label ) integer(i_def), intent(in) :: dump_level character( * ), intent(in) :: label - integer(i_def) :: cell - integer(i_def) :: layer - integer(i_def) :: df - integer(i_def), pointer :: map(:) => null() + type(function_space_type), pointer :: function_space => null() + integer(i_def) :: cell + integer(i_def) :: layer + integer(i_def) :: df + integer(i_def), pointer :: map(:) => null() + + ! Get function space from parent + function_space => self%get_function_space() write( log_scratch_space, '( A, A)' ) trim( label ), " =[" call log_event( log_scratch_space, dump_level ) - do cell=1,self%vspace%get_ncell() - map => self%vspace%get_cell_dofmap( cell ) - do df=1,self%vspace%get_ndf() - do layer=0,self%vspace%get_nlayers()-1 + do cell=1,function_space%get_ncell() + map => function_space%get_cell_dofmap( cell ) + do df=1,function_space%get_ndf() + do layer=0,function_space%get_nlayers()-1 write( log_scratch_space, '( I6, I6, I6, I16 )' ) & cell, df, layer+1, self%data( map( df ) + layer ) call log_event( log_scratch_space, dump_level ) @@ -644,11 +692,15 @@ subroutine log_dofs( self, log_level, label ) integer(i_def), intent(in) :: log_level character( * ), intent(in) :: label + type(function_space_type), pointer :: function_space => null() integer(i_def) :: df + ! Get function space from parent + function_space => self%get_function_space() + call log_event( label, log_level ) - do df=1,self%vspace%get_undf() + do df=1,function_space%get_undf() write( log_scratch_space, '( I6, I16 )' ) df,self%data( df ) call log_event( log_scratch_space, log_level ) end do @@ -670,25 +722,30 @@ subroutine log_minmax( self, log_level, label ) class( integer_field_type ), target, intent(in) :: self integer(i_def), intent(in) :: log_level character( * ), intent(in) :: label + type(function_space_type), pointer :: function_space => null() integer(i_def) :: i integer(i_def) :: l_min, l_max integer(i_def) :: answer_min, answer_max + ! Get function space from parent + function_space => self%get_function_space() + ! If we aren't going to log the min and max then we don't need to ! do any further work here. if ( log_level < application_log_level() ) return l_max = self%data(1) l_min = self%data(1) - do i = 2, self%vspace%get_last_dof_owned() + do i = 2, function_space%get_last_dof_owned() if( self%data(i) > l_max ) l_max = self%data(i) if( self%data(i) < l_min ) l_min = self%data(i) end do - ! Functions global_max() and global_min() rely on MPI comms - ! so these calls are disabled here. Max and min are simply - ! l_max and l_min. - !call global_max( l_max, answer_max ) - !call global_min( l_min, answer_min ) + ! Functions 'global_max()' and 'global_min()' rely on MPI + ! comms which are not supported by the PSyclone test LFRic + ! infrastructure. Hence, these calls are disabled here and + ! 'max' and 'min' are simply 'l_max' and 'l_min'. + !call global_mpi%global_max( l_max, answer_max ) + !call global_mpi%global_min( l_min, answer_min ) answer_max = l_max answer_min = l_min @@ -714,20 +771,25 @@ subroutine log_absmax( self, log_level, label ) class( integer_field_type ), target, intent(in) :: self integer(i_def), intent(in) :: log_level character( * ), intent(in) :: label + type(function_space_type), pointer :: function_space => null() integer(i_def) :: i integer(i_def) :: l_max, answer + ! Get function space from parent + function_space => self%get_function_space() + ! If we aren't going to log the abs max then we don't need to ! do any further work here. if ( log_level < application_log_level() ) return l_max = abs(self%data(1)) - do i = 2, self%vspace%get_last_dof_owned() + do i = 2, function_space%get_last_dof_owned() if( abs(self%data(i)) > l_max ) l_max = abs(self%data(i)) end do - ! Function global_max() relies on MPI comms so this call - ! is disabled here. Max is simply l_max. - !call global_max( l_max, answer ) + ! Function 'global_max()' relies on MPI comms which are not + ! supported by the PSyclone test LFRic infrastructure. Hence, + ! this call is disabled here and 'max' is simply 'l_max'. + !call global_mpi%global_max( l_max, answer ) answer = l_max write( log_scratch_space, '( A, A, E16.8 )' ) & @@ -766,8 +828,7 @@ end subroutine write_field !> @param [in] field_name - field name / id to read subroutine read_field( self, field_name) use log_mod, only : log_event, & - LOG_LEVEL_ERROR, & - LOG_LEVEL_INFO + LOG_LEVEL_ERROR implicit none @@ -906,6 +967,7 @@ subroutine halo_exchange_start( self, depth ) call log_event( 'Error in field: '// & 'attempt to exchange halos with depth out of range.', & LOG_LEVEL_ERROR ) + else call log_event( 'Error in field: '// & 'attempt to exchange halos (a write operation) on a read-only field.', & @@ -914,7 +976,7 @@ subroutine halo_exchange_start( self, depth ) end subroutine halo_exchange_start - !! Wait for a halo exchange to complete + !! Wait for an asynchronous halo exchange to complete !! subroutine halo_exchange_finish( self, depth ) diff --git a/src/psyclone/tests/test_files/dynamo0p3/int_real_literal_scalar.f90 b/src/psyclone/tests/test_files/dynamo0p3/int_real_literal_scalar.f90 index 16f62c2812d..9809e22bea3 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/int_real_literal_scalar.f90 +++ b/src/psyclone/tests/test_files/dynamo0p3/int_real_literal_scalar.f90 @@ -1,7 +1,7 @@ !------------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2022, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -30,7 +30,7 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author: R. W. Ford, STFC Daresbury Lab -! Modified: I. Kavcic, Met Office +! Modified: I. Kavcic and L. Turner, Met Office ! Modified: J. Henrichs, Bureau of Meteorology @@ -41,7 +41,7 @@ program int_real_literal_scalar use constants_mod, only: r_def, i_def use field_mod, only: field_type use quadrature_xyoz_mod, only: quadrature_xyoz_type - use testkern_qr, only: testkern_qr_type + use testkern_qr_mod, only: testkern_qr_type implicit none diff --git a/src/psyclone/tests/test_files/dynamo0p3/kernels/dead_end/no_really/testkern_qr.F90 b/src/psyclone/tests/test_files/dynamo0p3/kernels/dead_end/no_really/testkern_qr_mod.F90 similarity index 97% rename from src/psyclone/tests/test_files/dynamo0p3/kernels/dead_end/no_really/testkern_qr.F90 rename to src/psyclone/tests/test_files/dynamo0p3/kernels/dead_end/no_really/testkern_qr_mod.F90 index 9eb8ca93c41..18d622eb82f 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/kernels/dead_end/no_really/testkern_qr.F90 +++ b/src/psyclone/tests/test_files/dynamo0p3/kernels/dead_end/no_really/testkern_qr_mod.F90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2022, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -30,7 +30,7 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author: R. W. Ford, STFC Daresbury Lab -! Modified: I. Kavcic, Met Office +! Modified: I. Kavcic and L. Turner, Met Office module testkern_qr diff --git a/src/psyclone/tests/test_files/dynamo0p3/testkern_invalid_fortran.F90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_invalid_fortran_mod.f90 similarity index 95% rename from src/psyclone/tests/test_files/dynamo0p3/testkern_invalid_fortran.F90 rename to src/psyclone/tests/test_files/dynamo0p3/testkern_invalid_fortran_mod.f90 index 3c917e86e94..afc02e4158a 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/testkern_invalid_fortran.F90 +++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_invalid_fortran_mod.f90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2021, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ ! POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author R. W. Ford, STFC Daresbury Lab -! Modified I. Kavcic, Met Office +! Modified I. Kavcic and L. Turner, Met Office module testkern diff --git a/src/psyclone/tests/test_files/dynamo0p3/testkern_no_datatype.F90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_no_datatype_mod.f90 similarity index 95% rename from src/psyclone/tests/test_files/dynamo0p3/testkern_no_datatype.F90 rename to src/psyclone/tests/test_files/dynamo0p3/testkern_no_datatype_mod.f90 index 61c08f30c82..046614d751f 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/testkern_no_datatype.F90 +++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_no_datatype_mod.f90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2021, Science and Technology Facilities Council. +! Copyright (c) 2017-2023, Science and Technology Facilities Council. ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -30,7 +30,7 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author R. W. Ford, STFC Daresbury Lab -! Modified I. Kavcic, Met Office +! Modified I. Kavcic and L. Turner, Met Office module testkern_mod diff --git a/src/psyclone/tests/test_files/dynamo0p3/testkern_qr_mod.F90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_qr_mod.F90 new file mode 100644 index 00000000000..364b883e230 --- /dev/null +++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_qr_mod.F90 @@ -0,0 +1,88 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2017-2021, Science and Technology Facilities Council +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +! SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +! OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Author R. W. Ford STFC Daresbury Lab +! Modified I. Kavcic, Met Office + +module testkern_qr_mod + + use constants_mod + use argument_mod + use fs_continuity_mod + use kernel_mod + + implicit none + + type, extends(kernel_type) :: testkern_qr_type + type(arg_type), dimension(6) :: meta_args = & + (/ 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_scalar, gh_real, gh_read), & + arg_type(gh_field, gh_real, gh_read, w3), & + arg_type(gh_scalar, gh_integer, gh_read) & + /) + type(func_type), dimension(3) :: meta_funcs = & + (/ func_type(w1, gh_basis), & + func_type(w2, gh_diff_basis), & + func_type(w3, gh_basis, gh_diff_basis) & + /) + integer :: operates_on = cell_column + integer :: gh_shape = gh_quadrature_XYoZ + contains + procedure, nopass :: code => testkern_qr_code + end type testkern_qr_type + + contains + + subroutine testkern_qr_code(nlayers, f1, f2, f3, ascalar, f4, iscalar, & + ndf_w1, undf_w1, map_w1, basis_w1, ndf_w2, & + undf_w2, map_w2, diff_basis_w2, ndf_w3, & + undf_w3, map_w3, basis_w3, diff_basis_w3, & + nqp_h, nqp_v, wh, wv) + + implicit none + + integer(kind=i_def), intent(in) :: nlayers, iscalar + integer(kind=i_def), intent(in) :: ndf_w1, undf_w1, ndf_w2, undf_w2, ndf_w3, undf_w3 + integer(kind=i_def), intent(in) :: nqp_h, nqp_v + integer(kind=i_def), intent(in), dimension(:) :: map_w1, map_w2, map_w3 + real(kind=r_def), intent(in) :: ascalar + real(kind=r_def), dimension(:), intent(inout) :: f1 + real(kind=r_def), dimension(:), intent(in) :: f2, f3, f4 + real(kind=r_def), dimension(:), intent(in) :: wh, wv + real(kind=r_def), dimension(:,:,:,:), intent(in) :: basis_w1, diff_basis_w2 + real(kind=r_def), dimension(:,:,:,:), intent(in) :: basis_w3, diff_basis_w3 + + end subroutine testkern_qr_code + + end module testkern_qr_mod + \ No newline at end of file diff --git a/src/psyclone/tests/test_files/dynamo0p3/testkern_short_name.F90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_short_name_mod.f90 similarity index 95% rename from src/psyclone/tests/test_files/dynamo0p3/testkern_short_name.F90 rename to src/psyclone/tests/test_files/dynamo0p3/testkern_short_name_mod.f90 index 7d47001f216..42772122115 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/testkern_short_name.F90 +++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_short_name_mod.f90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2021, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! ! Redistribution and use in source and binary forms, with or without ! modification, are permitted provided that the following conditions are met: @@ -31,7 +31,7 @@ ! POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author R. W. Ford, STFC Daresbury Lab -! Modified I. Kavcic, Met Office +! Modified I. Kavcic and L. Turner, Met Office module jo diff --git a/src/psyclone/tests/test_files/dynamo0p3/simple.f90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_simple_mod.f90 similarity index 95% rename from src/psyclone/tests/test_files/dynamo0p3/simple.f90 rename to src/psyclone/tests/test_files/dynamo0p3/testkern_simple_mod.f90 index 5d3f0491dfe..7e9ba03bbfd 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/simple.f90 +++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_simple_mod.f90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2021, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -30,7 +30,7 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author R. W. Ford, STFC Daresbury Lab -! Modified I. Kavcic, Met Office +! Modified I. Kavcic and L. Turner, Met Office module simple_mod diff --git a/src/psyclone/tests/test_files/dynamo0p3/simple_with_reduction.f90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_simple_with_reduction_mod.f90 similarity index 95% rename from src/psyclone/tests/test_files/dynamo0p3/simple_with_reduction.f90 rename to src/psyclone/tests/test_files/dynamo0p3/testkern_simple_with_reduction_mod.f90 index a9af8b7763d..28922e1c39a 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/simple_with_reduction.f90 +++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_simple_with_reduction_mod.f90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2017-2021, Science and Technology Facilities Council +! Copyright (c) 2017-2023, Science and Technology Facilities Council ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -30,7 +30,7 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author A. R. Porter, STFC Daresbury Lab -! Modified I. Kavcic, Met Office +! Modified I. Kavcic and L. Turner, Met Office module simple_with_reduction_mod diff --git a/src/psyclone/tests/test_files/dynamo0p3/testkern_qr.F90 b/src/psyclone/tests/test_files/dynamo0p3/testkern_wrong_file_name.F90 similarity index 99% rename from src/psyclone/tests/test_files/dynamo0p3/testkern_qr.F90 rename to src/psyclone/tests/test_files/dynamo0p3/testkern_wrong_file_name.F90 index 7e9ec060c97..41e6a42ba68 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/testkern_qr.F90 +++ b/src/psyclone/tests/test_files/dynamo0p3/testkern_wrong_file_name.F90 @@ -30,7 +30,7 @@ ! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ! ----------------------------------------------------------------------------- ! Author R. W. Ford STFC Daresbury Lab -! Modified I. Kavcic Met Office +! Modified I. Kavcic, Met Office module testkern_qr diff --git a/src/psyclone/tests/test_files/dynamo0p3/xyz b/src/psyclone/tests/test_files/dynamo0p3/testkern_xyz_mod.f90 similarity index 100% rename from src/psyclone/tests/test_files/dynamo0p3/xyz rename to src/psyclone/tests/test_files/dynamo0p3/testkern_xyz_mod.f90 diff --git a/src/psyclone/tests/utilities_test.py b/src/psyclone/tests/utilities_test.py index 7cc18d9dec1..8989dfedf4b 100644 --- a/src/psyclone/tests/utilities_test.py +++ b/src/psyclone/tests/utilities_test.py @@ -152,7 +152,7 @@ def test_compiler_with_flags(monkeypatch): with open("hello_world.f90", "w", encoding="utf-8") as ffile: ffile.write(HELLO_CODE) _compile = Compile() - _compile._f90flags = "not-a-flag" + _compile._f90flags = "-not-a-flag" with pytest.raises(CompileError) as excinfo: _compile.compile_file("hello_world.f90") diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 1a32da371d0..97d72ba88ac 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -35,6 +35,7 @@ # A. B. G. Chalk STFC Daresbury Lab # J. Henrichs, Bureau of Meteorology # Modified I. Kavcic, Met Office +# Modified J. G. Wallwork, Met Office ''' This module provides the various transformations that can be applied to PSyIR nodes. There are both general and API-specific transformation @@ -496,6 +497,8 @@ def __init__(self): # to the loop directive. self._independent = True self._sequential = False + self._gang = False + self._vector = False super().__init__() def __str__(self): @@ -508,13 +511,15 @@ def _directive(self, children, collapse=None): :param children: list of child nodes of the new directive Node. :type children: list of :py:class:`psyclone.psyir.nodes.Node` - :param int collapse: number of nested loops to collapse or None if \ + :param int collapse: number of nested loops to collapse or None if no collapse attribute is required. ''' directive = ACCLoopDirective(children=children, collapse=collapse, independent=self._independent, - sequential=self._sequential) + sequential=self._sequential, + gang=self._gang, + vector=self._vector) return directive def apply(self, node, options=None): @@ -534,15 +539,21 @@ def apply(self, node, options=None): :py:meth:`psyclone.psyir.nodes.ACCLoopDirective.gen_code` is called), this node must be within (i.e. a child of) a PARALLEL region. - :param node: the supplied node to which we will apply the \ + :param node: the supplied node to which we will apply the Loop transformation. :type node: :py:class:`psyclone.psyir.nodes.Loop` :param options: a dictionary with options for transformations. :type options: Optional[Dict[str, Any]] :param int options["collapse"]: number of nested loops to collapse. - :param bool options["independent"]: whether to add the "independent" \ - clause to the directive (not strictly necessary within \ + :param bool options["independent"]: whether to add the "independent" + clause to the directive (not strictly necessary within PARALLEL regions). + :param bool options["sequential"]: whether to add the "seq" clause to + the directive. + :param bool options["gang"]: whether to add the "gang" clause to the + directive. + :param bool options["vector"]: whether to add the "vector" clause to + the directive. ''' # Store sub-class specific options. These are used when @@ -551,6 +562,8 @@ def apply(self, node, options=None): options = {} self._independent = options.get("independent", True) self._sequential = options.get("sequential", False) + self._gang = options.get("gang", False) + self._vector = options.get("vector", False) # Call the apply() method of the base class super().apply(node, options) @@ -1988,18 +2001,17 @@ def make_constant(symbol_table, arg_position, value, 'arg_position' into a compile-time constant with value 'value'. - :param symbol_table: the symbol table for the kernel \ - holding the argument that is going to be modified. + :param symbol_table: the symbol table for the kernel holding + the argument that is going to be modified. :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable` - :param int arg_position: the argument's position in the \ - argument list. - :param value: the constant value that this argument is \ - going to be given. Its type depends on the type of the \ - argument. + :param int arg_position: the argument's position in the + argument list. + :param value: the constant value that this argument is going to + be given. Its type depends on the type of the argument. :type value: int, str or bool - :type str function_space: the name of the function space \ - if there is a function space associated with this \ - argument. Defaults to None. + :type str function_space: the name of the function space if there + is a function space associated with this argument. Defaults + to None. ''' arg_index = arg_position - 1 @@ -2033,7 +2045,7 @@ def make_constant(symbol_table, arg_position, value, # #321). orig_name = symbol.name local_symbol = DataSymbol(orig_name+"_dummy", INTEGER_TYPE, - constant_value=value) + is_constant=True, initial_value=value) symbol_table.add(local_symbol) symbol_table.swap_symbol_properties(symbol, local_symbol) @@ -2762,7 +2774,7 @@ def apply(self, node, options=None): # Resolve the data type information if it is not available # pylint: disable=unidiomatic-typecheck - if (type(imported_var) == Symbol or + if (type(imported_var) is Symbol or isinstance(imported_var.datatype, DeferredType)): updated_sym = imported_var.resolve_deferred() # If we have a new symbol then we must update the symbol table @@ -2780,11 +2792,14 @@ def apply(self, node, options=None): # Convert the symbol to an argument and add it to the argument list current_arg_list = symtab.argument_list - if updated_sym.is_constant: + # An argument does not have an initial value. + was_constant = updated_sym.is_constant + updated_sym.is_constant = False + updated_sym.initial_value = None + if was_constant: # Imported constants lose the constant value but are read-only # TODO: When #633 and #11 are implemented, warn the user that # they should transform the constants to literal values first. - updated_sym.constant_value = None updated_sym.interface = ArgumentInterface( ArgumentInterface.Access.READ) else: diff --git a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/Makefile b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/Makefile index 609104472db..022866f983b 100644 --- a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/Makefile +++ b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -54,10 +54,10 @@ EXEC = ./simple_kernels_part1 # LFRic infrastructure library PSYCLONE_RELPATH = ../../../../../.. LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrastructure -LFRIC_NAME=lfric -LFRIC_LIB=$(LFRIC_PATH)/lib$(LFRIC_NAME).a +LFRIC_NAME = lfric +LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # Object rules @@ -70,7 +70,10 @@ EXAMPLE_OBJ = $(DRIVER_OBJ) $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) # Targets .DEFAULT_GOAL := compile -.PHONY: compile transform clean +.PHONY: transform compile run clean allclean + +# Test PSyclone target for continuous integration +transform: $(PSY_SRC) $(ALGORITHM_SRC) compile: transform $(EXEC) @@ -78,7 +81,8 @@ run: compile $(EXEC) $(EXEC): $(LFRIC_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) @@ -87,25 +91,25 @@ $(LFRIC_LIB): $(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) $(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) $(PSY_OBJ): $(KERNEL_OBJ) +$(KERNEL_OBJ): $(LFRIC_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) # Keep the generated Alg and PSy files .precious: $(ALGORITHM_SRC) $(PSY_SRC) -# transform: PSyclone target for continuous integration -transform: $(PSY_SRC) $(ALGORITHM_SRC) - %_psy.f90: %.x90 psyclone $(PSYCLONE_CMD) --config $(PSYCLONE_RELPATH)/config/psyclone.cfg \ -opsy $*_psy.f90 -oalg $*.f90 $< clean: rm -f *.o *.mod $(EXEC) $(ALGORITHM_SRC) $(PSY_SRC) - make -C $(LFRIC_PATH) clean + +allclean: clean + $(MAKE) -C $(LFRIC_PATH) allclean diff --git a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/solutions/Makefile b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/solutions/Makefile index be64ec9b8d7..be391c55bc5 100644 --- a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/solutions/Makefile +++ b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part1/solutions/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -58,10 +58,10 @@ EXEC = ./simple_kernels_part1 # LFRic infrastructure library PSYCLONE_RELPATH = ../../../../../../.. LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrastructure -LFRIC_NAME=lfric -LFRIC_LIB=$(LFRIC_PATH)/lib$(LFRIC_NAME).a +LFRIC_NAME = lfric +LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # Object rules @@ -85,22 +85,23 @@ run: compile $(EXEC) $(EXEC): $(LFRIC_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) # Dependencies -$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) -$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) -$(PSY_OBJ): $(KERNEL_OBJ) $(LFRIC_LIB) +$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) +$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) +$(PSY_OBJ): $(KERNEL_OBJ) $(KERNEL_OBJ): $(LFRIC_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) diff --git a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/Makefile b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/Makefile index 91b1e3c0d5a..0d654f95650 100644 --- a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/Makefile +++ b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrast LFRIC_NAME=lfric LFRIC_LIB=$(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # Object rules @@ -72,36 +72,38 @@ EXAMPLE_OBJ = $(DRIVER_OBJ) $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) .PHONY: transform compile run clean allclean +# Test PSyclone target for continuous integration +transform: $(PSY_SRC) $(ALGORITHM_SRC) + compile: transform $(EXEC) run: compile $(EXEC) $(EXEC): $(LFRIC_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) # Dependencies -$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) -$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) +$(DRIVER_OBJ): $(ALGORITHM_OBJ) +$(ALGORITHM_OBJ): $(PSY_OBJ) $(PSY_OBJ): $(KERNEL_OBJ) +$(KERNEL_OBJ): $(LFRIC_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) # Keep the generated Alg and PSy files .precious: $(ALGORITHM_SRC) $(PSY_SRC) -# Transform: PSyclone target for continuous integration -transform: $(PSY_SRC) $(ALGORITHM_SRC) - %_psy.f90: %.x90 psyclone $(PSYCLONE_CMD) --config $(PSYCLONE_RELPATH)/config/psyclone.cfg \ -opsy $*_psy.f90 -oalg $*.f90 $< diff --git a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/solutions/Makefile b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/solutions/Makefile index 0b7ebd458c9..ce8bfe25353 100644 --- a/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/solutions/Makefile +++ b/tutorial/practicals/LFRic/building_code/1_simple_kernels/part2/solutions/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -58,10 +58,10 @@ EXEC = ./simple_kernels_part2 # LFRic infrastructure library PSYCLONE_RELPATH = ../../../../../../.. LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrastructure -LFRIC_NAME=lfric -LFRIC_LIB=$(LFRIC_PATH)/lib$(LFRIC_NAME).a +LFRIC_NAME = lfric +LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # Object rules @@ -85,22 +85,23 @@ run: compile $(EXEC) $(EXEC): $(LFRIC_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) # Dependencies -$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) -$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) -$(PSY_OBJ): $(KERNEL_OBJ) $(LFRIC_LIB) +$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) +$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) +$(PSY_OBJ): $(KERNEL_OBJ) $(KERNEL_OBJ): $(LFRIC_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) @@ -112,6 +113,9 @@ $(ALGORITHM_SRC): $(PSY_SRC) -opsy $*_psy.f90 -oalg $*.f90 $< clean: + # We need to clean the base directory, otherwise invalid files in there + # will interfere with compiling the solution properly. + $(MAKE) -C .. clean rm -f *.o *.mod $(EXEC) $(ALGORITHM_SRC) $(PSY_SRC) allclean: clean diff --git a/tutorial/practicals/LFRic/building_code/2_built_ins/Makefile b/tutorial/practicals/LFRic/building_code/2_built_ins/Makefile index c63b2258cc5..5759b76e7a1 100644 --- a/tutorial/practicals/LFRic/building_code/2_built_ins/Makefile +++ b/tutorial/practicals/LFRic/building_code/2_built_ins/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -48,15 +48,15 @@ PSY_SRC = $(ALGORITHM_SRC:.f90=_psy.f90) PSYCLONE_CMD = -nodm -l all # Executable -EXEC = builtins +EXEC = ./builtins # LFRic infrastructure library PSYCLONE_RELPATH = ../../../../.. LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrastructure -LFRIC_NAME=lfric -LFRIC_LIB=$(LFRIC_PATH)/lib$(LFRIC_NAME).a +LFRIC_NAME = lfric +LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # Object rules @@ -68,12 +68,19 @@ EXAMPLE_OBJ = $(DRIVER_OBJ) $(ALGORITHM_OBJ) $(PSY_OBJ) # Targets .DEFAULT_GOAL := compile -.PHONY: compile transform clean +.PHONY: transform compile run clean allclean + +# Test PSyclone target for continuous integration +transform: $(PSY_SRC) $(ALGORITHM_SRC) compile: transform $(EXEC) +run: compile + $(EXEC) + $(EXEC): $(LFRIC_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) @@ -81,25 +88,25 @@ $(LFRIC_LIB): # Dependencies $(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(ALGORITHM_OBJ): $(PSY_OBJ) +$(PSY_OBJ): $(LFRIC_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) # Keep the generated Alg and PSy files .precious: $(ALGORITHM_SRC) $(PSY_SRC) -# Test PSyclone target for continuous integration -transform: $(PSY_SRC) $(ALGORITHM_SRC) - %_psy.f90: %.x90 psyclone $(PSYCLONE_CMD) --config $(PSYCLONE_RELPATH)/config/psyclone.cfg \ -opsy $*_psy.f90 -oalg $*.f90 $< clean: rm -f *.o *.mod $(EXEC) $(ALGORITHM_SRC) $(PSY_SRC) + +allclean: clean make -C $(LFRIC_PATH) clean diff --git a/tutorial/practicals/LFRic/building_code/2_built_ins/solutions/Makefile b/tutorial/practicals/LFRic/building_code/2_built_ins/solutions/Makefile index 2ca4344c430..eb44cda7ec8 100644 --- a/tutorial/practicals/LFRic/building_code/2_built_ins/solutions/Makefile +++ b/tutorial/practicals/LFRic/building_code/2_built_ins/solutions/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -57,10 +57,10 @@ EXEC = ./builtins # LFRic infrastructure library PSYCLONE_RELPATH = ../../../../../.. LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrastructure -LFRIC_NAME=lfric -LFRIC_LIB=$(LFRIC_PATH)/lib$(LFRIC_NAME).a +LFRIC_NAME = lfric +LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # Object rules @@ -74,36 +74,37 @@ EXAMPLE_OBJ = $(DRIVER_OBJ) $(ALGORITHM_OBJ) $(PSY_OBJ) .PHONY: transform compile run clean allclean +# Test PSyclone target for continuous integration +transform: $(PSY_SRC) $(ALGORITHM_SRC) + compile: transform $(EXEC) run: compile $(EXEC) $(EXEC): $(LFRIC_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) # Dependencies -$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(LFRIC_LIB) -$(ALGORITHM_OBJ): $(PSY_OBJ) $(LFRIC_LIB) +$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) +$(ALGORITHM_OBJ): $(PSY_OBJ) $(PSY_OBJ): $(LFRIC_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) # Keep the generated Alg and PSy files .precious: $(ALGORITHM_SRC) $(PSY_SRC) -# Test PSyclone target for continuous integration -transform: $(PSY_SRC) $(ALGORITHM_SRC) - %_psy.f90: %.x90 psyclone $(PSYCLONE_CMD) --config $(PSYCLONE_RELPATH)/config/psyclone.cfg \ -opsy $*_psy.f90 -oalg $*.f90 $< diff --git a/tutorial/practicals/LFRic/building_code/3_time_evolution/Makefile b/tutorial/practicals/LFRic/building_code/3_time_evolution/Makefile index 8b6a7866eaa..38fec8fe5d3 100644 --- a/tutorial/practicals/LFRic/building_code/3_time_evolution/Makefile +++ b/tutorial/practicals/LFRic/building_code/3_time_evolution/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -50,7 +50,7 @@ KERNEL_SRC = $(wildcard *_kernel_mod.f90) PSYCLONE_CMD = -nodm -l all # Executable -EXEC = time_evolution +EXEC = ./time_evolution # LFRic infrastructure library PSYCLONE_RELPATH = ../../../../.. @@ -58,7 +58,7 @@ LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrast LFRIC_NAME = lfric_netcdf LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # GungHo auxiliary libraries @@ -77,13 +77,20 @@ EXAMPLE_OBJ = $(DRIVER_OBJ) $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) # Targets .DEFAULT_GOAL := compile -.PHONY: compile test clean +.PHONY: transform compile run clean allclean -compile: test $(EXEC) +# Test PSyclone target for continuous integration +transform: $(PSY_SRC) $(ALGORITHM_SRC) + +compile: transform $(EXEC) + +run: compile + $(EXEC) $(EXEC): $(LFRIC_LIB) $(GHLIB_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) \ - -l$(LFRIC_NAME) $$(nf-config --flibs) -L$(GHLIB_PATH) -l$(GHLIB_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) \ + $$(nf-config --flibs) -L$(GHLIB_PATH) -l$(GHLIB_NAME) $(GHLIB_LIB): $(MAKE) -C $(GHLIB_PATH) @@ -92,30 +99,35 @@ $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) netcdf # Dependencies -$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) $(GHLIB_LIB) -$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) $(GHLIB_LIB) -$(PSY_OBJ): $(KERNEL_OBJ) $(LFRIC_LIB) $(GHLIB_LIB) +$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) +$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) +$(PSY_OBJ): $(KERNEL_OBJ) $(KERNEL_OBJ): $(LFRIC_LIB) $(GHLIB_LIB) +time_evolution_alg_mod.o: init_perturbation_kernel_mod.o \ + prop_perturbation_kernel_mod.o time_evolution_alg_mod_psy.o \ + $(GHLIB_LIB) $(LFRIC_LIB) +time_evolution_alg_mod_psy.o: init_perturbation_kernel_mod.o \ + prop_perturbation_kernel_mod.o $(GHLIB_LIB) $(LFRIC_LIB) +prop_perturbation_kernel_mod.o: $(GHLIB_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) # Keep the generated Alg and PSy files .precious: $(ALGORITHM_SRC) $(PSY_SRC) -# Test PSyclone target for continuous integration -test: $(PSY_SRC) $(ALGORITHM_SRC) - %_psy.f90: %.x90 psyclone $(PSYCLONE_CMD) --config $(PSYCLONE_RELPATH)/config/psyclone.cfg \ -opsy $*_psy.f90 -oalg $*.f90 $< clean: rm -f *.o *.mod *.txt *.png $(EXEC) $(ALGORITHM_SRC) $(PSY_SRC) + +allclean: clean make -C $(GHLIB_PATH) clean make -C $(LFRIC_PATH) clean diff --git a/tutorial/practicals/LFRic/building_code/3_time_evolution/README.md b/tutorial/practicals/LFRic/building_code/3_time_evolution/README.md index a43391125b1..36e73dce86d 100644 --- a/tutorial/practicals/LFRic/building_code/3_time_evolution/README.md +++ b/tutorial/practicals/LFRic/building_code/3_time_evolution/README.md @@ -65,9 +65,9 @@ no modifications are: main program that sets up the model run, runs the main time-step loop by calling subroutines from the `time_evolution_alg_mod.x90` algorithm and outputs results calling an I/O routine from the - `write_diagnostics_mod.f90` module; + `write_diagnostics_alg_mod.x90` module; -* [`write_diagnostics_mod.f90`](../gungho_lib/write_diagnostics_mod.f90) +* [`write_diagnostics_alg_mod.x90`](../gungho_lib/write_diagnostics_alg_mod.x90) calls a diagnostic output routine to write the "model state" consisting of the coordinate and perturbation fields to a file; diff --git a/tutorial/practicals/LFRic/building_code/3_time_evolution/solutions/Makefile b/tutorial/practicals/LFRic/building_code/3_time_evolution/solutions/Makefile index 0a02c47e7d1..3a6dee31607 100644 --- a/tutorial/practicals/LFRic/building_code/3_time_evolution/solutions/Makefile +++ b/tutorial/practicals/LFRic/building_code/3_time_evolution/solutions/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -61,7 +61,7 @@ LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrast LFRIC_NAME = lfric_netcdf LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc # GungHo auxiliary libraries @@ -82,14 +82,18 @@ EXAMPLE_OBJ = $(DRIVER_OBJ) $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) .PHONY: transform compile run clean allclean +# Test PSyclone target for continuous integration +transform: $(PSY_SRC) $(ALGORITHM_SRC) + compile: transform $(EXEC) run: compile $(EXEC) $(EXEC): $(LFRIC_LIB) $(GHLIB_LIB) $(EXAMPLE_OBJ) - $(F90) $(F90FLAGS) $(EXAMPLE_OBJ) -o $(EXEC) -L$(LFRIC_PATH) \ - -l$(LFRIC_NAME) $$(nf-config --flibs) -L$(GHLIB_PATH) -l$(GHLIB_NAME) + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) $(EXAMPLE_OBJ) \ + -o $(EXEC) -L$(LFRIC_PATH) -l$(LFRIC_NAME) \ + $$(nf-config --flibs) -L$(GHLIB_PATH) -l$(GHLIB_NAME) $(GHLIB_LIB): $(LFRIC_LIB) $(MAKE) -C $(GHLIB_PATH) @@ -98,9 +102,9 @@ $(LFRIC_LIB): $(MAKE) -C $(LFRIC_PATH) netcdf # Dependencies -$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) $(GHLIB_LIB) -$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) $(LFRIC_LIB) $(GHLIB_LIB) -$(PSY_OBJ): $(KERNEL_OBJ) $(LFRIC_LIB) $(GHLIB_LIB) +$(DRIVER_OBJ): $(ALGORITHM_OBJ) $(PSY_OBJ) $(KERNEL_OBJ) +$(ALGORITHM_OBJ): $(PSY_OBJ) $(KERNEL_OBJ) +$(PSY_OBJ): $(KERNEL_OBJ) $(KERNEL_OBJ): $(LFRIC_LIB) $(GHLIB_LIB) time_evolution_alg_mod.o: init_perturbation_kernel_mod.o \ prop_perturbation_kernel_mod.o time_evolution_alg_mod_psy.o \ @@ -110,25 +114,22 @@ time_evolution_alg_mod_psy.o: init_perturbation_kernel_mod.o \ prop_perturbation_kernel_mod.o: $(GHLIB_LIB) %.o %.mod: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o %.mod: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< $(ALGORITHM_SRC): $(PSY_SRC) # Keep the generated Alg and PSy files .precious: $(ALGORITHM_SRC) $(PSY_SRC) -# Test PSyclone target for continuous integration -transform: $(PSY_SRC) $(ALGORITHM_SRC) - %_psy.f90: %.x90 psyclone $(PSYCLONE_CMD) --config $(PSYCLONE_RELPATH)/config/psyclone.cfg \ -opsy $(notdir $*_psy.f90) -oalg $(notdir $*.f90) $< clean: - rm -f *.o *.mod *.txt *.png $(EXEC) $(ALGORITHM_SRC) $(PSY_SRC) + rm -f *.o *.mod *.txt *.png $(EXEC) $(ALGORITHM_SRC) $(PSY_SRC) allclean: clean make -C $(GHLIB_PATH) clean diff --git a/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.inc b/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.inc index a74a0c511bd..ae63692cd0d 100755 --- a/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.inc +++ b/tutorial/practicals/LFRic/building_code/4_psydata/Makefile.inc @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -73,7 +73,7 @@ include $(LFRIC_PATH)/lfric_include_flags.inc GHLIB_PATH = ../gungho_lib GHLIB_NAME = gungho GHLIB_LIB = $(GHLIB_PATH)/lib$(GHLIB_NAME).a -F90FLAGS += -I$(GHLIB_PATH) +F90FLAGS += -I$(GHLIB_PATH) $(LFRIC_INCLUDE_FLAGS) # Targets diff --git a/tutorial/practicals/LFRic/building_code/gungho_lib/.gitignore b/tutorial/practicals/LFRic/building_code/gungho_lib/.gitignore new file mode 100644 index 00000000000..898697f6c79 --- /dev/null +++ b/tutorial/practicals/LFRic/building_code/gungho_lib/.gitignore @@ -0,0 +1,5 @@ +*.o +*.mod +*.a +*_alg_mod.f90 +*_alg_mod_psy.f90 diff --git a/tutorial/practicals/LFRic/building_code/gungho_lib/Makefile b/tutorial/practicals/LFRic/building_code/gungho_lib/Makefile index 4cc584d6504..5b3acac9b2b 100644 --- a/tutorial/practicals/LFRic/building_code/gungho_lib/Makefile +++ b/tutorial/practicals/LFRic/building_code/gungho_lib/Makefile @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020-2022, Science and Technology Facilities Council. +# Copyright (c) 2020-2023, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -43,12 +43,23 @@ LFRIC_PATH = $(PSYCLONE_RELPATH)/src/psyclone/tests/test_files/dynamo0p3/infrast LFRIC_NAME = lfric_netcdf LFRIC_LIB = $(LFRIC_PATH)/lib$(LFRIC_NAME).a -# This will add the required include flags to F90FLAGS +# This will add the required include flags to LFRIC_INCLUDE_FLAGS include $(LFRIC_PATH)/lfric_include_flags.inc +# Source rules +ALGORITHMS = $(wildcard *_alg_mod.x90) +ALGORITHM_SRC = $(ALGORITHMS:.x90=.f90) +PSY_SRC = $(ALGORITHM_SRC:.f90=_psy.f90) GHLIB_SRC = $(wildcard *.F90) $(wildcard *.f90) -GHLIB_OBJ = $(filter %.o,$(GHLIB_SRC:.F90=.o) $(GHLIB_SRC:.f90=.o)) +# PSyclone command-line options +PSYCLONE_CMD = -nodm -l all + +# Object rules +ALGORITHM_OBJ = $(filter %.o,$(ALGORITHM_SRC:.f90=.o)) +PSY_OBJ = $(filter %.o,$(PSY_SRC:.f90=.o)) +GHLIB_OBJ = $(filter %.o,$(GHLIB_SRC:.F90=.o) $(GHLIB_SRC:.f90=.o)) \ + $(ALGORITHM_OBJ) $(PSY_OBJ) default: $(LFRIC_LIB) $(GHLIB_OBJ) $(AR) $(ARFLAGS) libgungho.a $(GHLIB_OBJ) @@ -64,13 +75,22 @@ configuration_mod.o: base_mesh_config_mod.o extrusion_uniform_config_mod.o \ finite_element_config_mod.o io_utility_mod.o partitioning_config_mod.o \ perturbation_bell_config_mod.o planet_config_mod.o timestepping_config_mod.o \ write_methods_mod.o -write_diagnostics_mod.o: write_methods_mod.o +write_diagnostics_alg_mod.o: write_methods_mod.o write_diagnostics_alg_mod_psy.o %.o: %.F90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< %.o: %.f90 - $(F90) $(F90FLAGS) -c $< + $(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $< + +$(ALGORITHM_SRC): $(PSY_SRC) + +# Keep the generated Alg and PSy files +.precious: $(ALGORITHM_SRC) $(PSY_SRC) + +%_psy.f90: %.x90 + psyclone $(PSYCLONE_CMD) --config $(PSYCLONE_RELPATH)/config/psyclone.cfg \ + -opsy $*_psy.f90 -oalg $*.f90 $< clean: rm -f *.o *.mod *.a diff --git a/tutorial/practicals/LFRic/building_code/gungho_lib/write_diagnostics_mod.f90 b/tutorial/practicals/LFRic/building_code/gungho_lib/write_diagnostics_alg_mod.x90 similarity index 95% rename from tutorial/practicals/LFRic/building_code/gungho_lib/write_diagnostics_mod.f90 rename to tutorial/practicals/LFRic/building_code/gungho_lib/write_diagnostics_alg_mod.x90 index 2230eb5d4e8..5188bce3323 100644 --- a/tutorial/practicals/LFRic/building_code/gungho_lib/write_diagnostics_mod.f90 +++ b/tutorial/practicals/LFRic/building_code/gungho_lib/write_diagnostics_alg_mod.x90 @@ -1,7 +1,7 @@ ! ----------------------------------------------------------------------------- ! BSD 3-Clause License ! -! Copyright (c) 2020, Science and Technology Facilities Council. +! Copyright (c) 2020-2023, Science and Technology Facilities Council. ! All rights reserved. ! ! Redistribution and use in source and binary forms, with or without @@ -85,7 +85,8 @@ subroutine write_diagnostics(diag_field, chi, tstep) ! Populate components of the temporary output diagnostic field (all ! same here) do i = 1, dim_fs - call diag_field%copy_field(diag_write_field(i)) + call diag_field%copy_field_properties(diag_write_field(i)) + call invoke( setval_X(diag_write_field(i), diag_field) ) end do ! Output data