diff --git a/changelog b/changelog index fe5ec70972..1918cc6092 100644 --- a/changelog +++ b/changelog @@ -32,6 +32,9 @@ 11) PR #2814 for #2704. Adds backward accesses capabilities to the DefinitionUseChain tool. + 12) PR #2509 for #2394. Adds support for logical and character types + to the PSyData tooling. + release 3.0.0 6th of December 2024 1) PR #2477 for #2463. Add support for Fortran Namelist statements. diff --git a/doc/user_guide/libraries.rst b/doc/user_guide/libraries.rst index ea6ad8decc..fc0af07317 100644 --- a/doc/user_guide/libraries.rst +++ b/doc/user_guide/libraries.rst @@ -99,8 +99,9 @@ Read-only libraries check that a field declared as read-only is not modified during a kernel call. More information can be found in the :ref:`Read-Only Verification ` section. -The libraries for :ref:`LFRic ` and -:ref:`GOcean ` APIs are included with PSyclone in +The libraries for :ref:`LFRic `, +:ref:`GOcean ` APIs and generic Fortran code +are included with PSyclone in the ``lib/read_only`` `directory `__. For detailed instructions on how to build and use these libraries diff --git a/doc/user_guide/psy_data.rst b/doc/user_guide/psy_data.rst index eaf4b496b6..eb87e55f5f 100644 --- a/doc/user_guide/psy_data.rst +++ b/doc/user_guide/psy_data.rst @@ -120,6 +120,7 @@ be printed at runtime, e.g.:: The transformation that adds read-only-verification to an application can be applied for both the :ref:`LFRic ` and :ref:`GOcean API ` - no API-specific transformations are required. +It can also be used to verify existing Fortran code. Below is an example that searches for each loop in a PSyKAl invoke code (which will always surround kernel calls) and applies the transformation to each one. This code has been successfully used as a global transformation with the LFRic @@ -136,10 +137,10 @@ Gravity Wave application (the executable is named ``gravity_wave``) read_only_verify.apply(loop) Besides the transformation, a library is required to do the actual -verification at runtime. There are two implementations of the +verification at runtime. There are three implementations of the read-only-verification library included in PSyclone: one for LFRic, -and one for GOcean. -Both libraries support the environment variable ``PSYDATA_VERBOSE``. +one for GOcean, and one for generic Fortran code. +These libraries support the environment variable ``PSYDATA_VERBOSE``. This can be used to control how much output is generated by the read-only-verification library at runtime. If the variable is not specified or has the value '0', warnings will only @@ -230,6 +231,25 @@ An executable example for using the GOcean read-only-verification library is included in ``examples/gocean/eg5/readonly``, see :ref:`gocean_example_readonly`. + +Read-Only-Verification Library for Generic Fortran +++++++++++++++++++++++++++++++++++++++++++++++++++ + +This library is contained in the ``lib/read_only/generic`` directory and +it must be compiled before linking any existing Fortran code that uses +read-only verification. + +Compilation of the library is done by invoking ``make`` and setting +the required variables: + +.. code-block:: shell + + make F90=ifort F90FLAGS="--some-flag" + +This will create a library called ``lib_read_only.a``. +An executable example for using the generic read-only-verification +library is included in ``examples/nemo/eg6/``. + .. _psydata_value_range_check: Value Range Check diff --git a/doc/user_guide/tutorials_and_examples.rst b/doc/user_guide/tutorials_and_examples.rst index c7f836e32f..6b6ff19b4e 100644 --- a/doc/user_guide/tutorials_and_examples.rst +++ b/doc/user_guide/tutorials_and_examples.rst @@ -783,6 +783,14 @@ compilation instructions are in the ``README.md`` file, including how to switch from using the stand-alone extraction library to the NetCDF-based one (see :ref:`extraction_libraries` for details). +Example 6: Read-only Verification +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example shows the use of read-only verification with PSyclone for +generic Fortran code. It instruments each kernel in a small Fortran +program with the PSyData-based read-only verification code. Detailed +compilation instructions are in the ``README.md`` file. + Scripts ^^^^^^^ diff --git a/examples/nemo/README.md b/examples/nemo/README.md index 6f4717af5c..eaef163053 100644 --- a/examples/nemo/README.md +++ b/examples/nemo/README.md @@ -140,3 +140,9 @@ benchmark. Extraction of kernel data. Using the tra_adv benchmark, this example shows the extraction of kernel input- and output-data. + +## Example 6 + +A simple stand-alone example that shows verification that read-only data +is not modified, e.g. by out-of-bounds accesses to other variables. +This uses the PSyData interface to instrument generic Fortran code. diff --git a/examples/nemo/eg6/Makefile b/examples/nemo/eg6/Makefile new file mode 100644 index 0000000000..5c15c7cd5a --- /dev/null +++ b/examples/nemo/eg6/Makefile @@ -0,0 +1,83 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023-2025, 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 +# Modified J. Henrichs, Bureau of Meteorology + +# This example uses PSyclone (which must be installed) to generate Fortran for +# the tracer-advection benchmark. The script provided here instruments code to +# check that read-only variables are not modified. + +# The compiler to use may be specified via the F90 environment variable, +# it defaults to: +# export F90=gfortran +# export F90FLAGS="-g -O0" + +include ../../common.mk + +TYPE ?= standalone + +GENERATED_FILES += psy.f90 psy.o dummy + +PSYROOT=../../.. +READ_ONLY_CHECK_DIR ?= $(PSYROOT)/lib/read_only/generic + +F90FLAGS += -I$(READ_ONLY_CHECK_DIR) +LIB_NAME = lib_read_only.a + +.PHONY: allclean + +compile: dummy + +run: compile + ./dummy + +dummy: psy.o $(READ_ONLY_CHECK_DIR)/$(LIB_NAME) + $(F90) psy.o -o dummy $(READ_ONLY_CHECK_DIR)/$(LIB_NAME) $(LDFLAGS) + +transform: kernels + +# Need `-l all` to ensure line-lengths in generated code are less than the +# standard-mandated 132 chars. +kernels: read_only_check.py + $(PSYCLONE) -l all -s ./read_only_check.py -o psy.f90 dummy.f90 + +$(READ_ONLY_CHECK_DIR)/$(LIB_NAME): + make -C $(READ_ONLY_CHECK_DIR) + +# Compilation uses the 'kernels' transformed code +psy.f90: dummy.f90 read_only_check.py +psy.o: $(READ_ONLY_CHECK_DIR)/$(LIB_NAME) + +%.o: %.f90 + $(F90) $(F90FLAGS) -c $< diff --git a/examples/nemo/eg6/README.md b/examples/nemo/eg6/README.md new file mode 100644 index 0000000000..a0a72e1def --- /dev/null +++ b/examples/nemo/eg6/README.md @@ -0,0 +1,100 @@ +# PSyclone NEMO Example 6 - Read-only Verification + +**Author:** J. Henrichs, Bureau of Meteorology + +This example demonstrates the use of PSyclone to add code that checks +if variables that are only read are actually modified (e.g. because +of memory overwrite). + +Once you have installed PSyclone, you can transform the file using: + +```sh +psyclone -s ./read_only_check.py dummy.f90 +``` + +Executing this will output the transformed Fortran code with the +read-only-verification code added. + +The generic read-only verification library in +``../../../lib/read_only/generic`` is used, and will also be +automatically compiled if required. + +## Compiling and Execution + +To create and compile the example, type ``make compile``. +This example can be compiled and executed. It will report nothing, +since no read-only variable is overwritten. But you can verify that +the variables are checked by setting ``PSYDATA_VERBOSE=2``: + +```sh +$ PSYDATA_VERBOSE=2 ./dummy + PSyData: PreStart dummy r0 + PSyData: DeclareScalarChar: dummy r0: char_var + PSyData: DeclareScalarLogical: dummy r0: logical_var + PSyData: DeclareScalarChar: dummy r0: char_var + PSyData: DeclareScalarLogical: dummy r0: logical_var + PSyData: checked variable char_var + PSyData: checked variable logical_var + PSyData: PostEnd dummy r0 + 3.00000000 F +``` + +If you copy the lines 68 and 69 from ``dummy.f90`` into ``psy.f90``, +the code will modify ``logical_var`` (by using out-of-bound array accesses. +Or you could just manually set ``logical_var = .true.``). If you then +compile again (using `make compile`, otherwise the original file would +get processed again, overwriting your changes), an error will be produced. + +```sh +$ ./dummy + ------------- PSyData ------------------------- + Logical(kind=4) variable logical_var has been modified in dummy : r0 + Original value: F + New value: T + ------------- PSyData ------------------------- + 3.00000000 T +``` + +Note that adding the assignment to ``logical_var`` as above to the original +``dummy.f90`` file would mean that ``logical_var`` is not a read-only variable +anymore, so no test and therefore no error will be produced for this variable. +The code commented out can also not be processed by PSyclone (missing support +for ``loc`` and ``sizeof``, which are non-standard Fortran extensions). + +## Licence + +----------------------------------------------------------------------------- + +BSD 3-Clause License + +Copyright (c) 2025, 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. + +----------------------------------------------------------------------------- diff --git a/examples/nemo/eg6/dummy.f90 b/examples/nemo/eg6/dummy.f90 new file mode 100644 index 0000000000..76c936acfa --- /dev/null +++ b/examples/nemo/eg6/dummy.f90 @@ -0,0 +1,74 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2025, 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: J. Henrichs, Bureau of Meteorology + +! This is a very simple program that is used to show how PSyclone can +! detect if a read-only variable is modified (typically because of a +! memory overwrite elsewhere). In order to actually trigger the warning, +! you need to modify the code after processing it with PSyclone, see +! details in lines 68-69. + +program dummy + real, dimension(10, 10) :: umask + character(len=10) :: char_var + integer :: ji, jj + logical :: logical_var + integer(kind=8) :: offset + integer, intrinsic :: loc, sizeof + + logical_var = .false. + char_var = "test" + + do jj = 1, 10 + do ji = 1, 10 + if (char_var .eq. "abc" .and. logical_var) then + umask(ji,jj) = 1 + else + umask(ji,jj) = 3 + endif + end do + + ! VERY NAUGHTY CODE AHEAD - there be dragon!! + ! We modify the values of some read-only fields by using offsets + ! in a written array. This will abort if the code is compiled + ! with array bound check of course. + ! TODO: Unfortunately, PSyclone does not support loc or sizeof (which + ! are non-standard extensions), so the code below does not work. But + ! you can create the psy-layer, and then copy the following lines into + ! psy.f90, and recompile the program (use `make compile`). This will then + ! issue a warning about `logical_var` being overwritten. + ! offset = ( loc(logical_var) - loc(umask(1,1)) ) / sizeof(logical_var) + ! umask(1+offset, 1) = 123.0 + end do + + print *,umask(1,1), logical_var +end program dummy diff --git a/examples/nemo/eg6/read_only_check.py b/examples/nemo/eg6/read_only_check.py new file mode 100644 index 0000000000..6c209b42f1 --- /dev/null +++ b/examples/nemo/eg6/read_only_check.py @@ -0,0 +1,62 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2025, 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: J. Henrichs, Bureau of Meteorology + +'''A transformation script that applies read-only-verification +to a small Fortran program. You can use + $ psyclone --config ../../../psyclone.cfg \ + -s ./read_only_check.py -opsy psy.f90 dummy.f90 + +''' + +from psyclone.psyir.transformations import ReadOnlyVerifyTrans +from psyclone.psyir.nodes import Loop, Routine + + +def trans(psyir): + '''Applies the read-only verification transformation to every + subroutine in the file. + + :param psyir: the PSyIR of the provided file. + :type psyir: :py:class:`psyclone.psyir.nodes.FileContainer` + ''' + + rov = ReadOnlyVerifyTrans() + + for subroutine in psyir.walk(Routine): + print(f"Transforming subroutine: {subroutine.name}") + for kern in subroutine.children: + if not isinstance(kern, Loop): + continue + rov.apply(kern) diff --git a/examples/xdsl/Makefile b/examples/xdsl/Makefile index 414f586b0c..1e2a1a3c5c 100644 --- a/examples/xdsl/Makefile +++ b/examples/xdsl/Makefile @@ -39,10 +39,10 @@ EXAMPLES=$(sort $(wildcard eg*)) include ../top_level.mk transform: - @echo "No transformation supported for xdsl" + @echo "No transformation supported for xdsl" compile: transform - @echo "No compilation supported for xdsl" + @echo "No compilation supported for xdsl" run: compile - @echo "No run targets for xdsl" \ No newline at end of file + @echo "No run targets for xdsl" diff --git a/lib/Makefile b/lib/Makefile index e1b203c03f..520e6aa5ba 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -61,6 +61,12 @@ all: $(MAKE) -C read_only all $(MAKE) -C value_range_check all +allclean: + $(MAKE) -C extract clean + $(MAKE) -C profiling clean + $(MAKE) -C read_only clean + $(MAKE) -C value_range_check clean + %.f90: %.jinja process.py $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py $< > $*.f90 diff --git a/lib/extract/compare_variables_mod.jinja b/lib/extract/compare_variables_mod.jinja index c47a13ac82..6329b43545 100644 --- a/lib/extract/compare_variables_mod.jinja +++ b/lib/extract/compare_variables_mod.jinja @@ -23,18 +23,14 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% 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) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Character", "character(*)" ), + ("Long", "real(kind=int64)" ), + ("Int", "integer(kind=int32)") ] %} {% endif -%} @@ -99,7 +95,7 @@ module compare_variables_mod {# ------------------------------------------------------------------------- #} interface compare {% set all_compares=[] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} module procedure compare_scalar_{{name}} {% for dim in ALL_DIMS %} module procedure compare_array_{{dim}}d{{name}} @@ -158,7 +154,7 @@ contains end subroutine compare_summary ! ------------------------------------------------------------------------- -{% for name, type, bits in ALL_TYPES %} +{% for name, type in ALL_TYPES %} {# Logical needs EQV for its tests #} {% if name == "Logical" %} {% set EQUAL = ".EQV." -%} @@ -311,6 +307,6 @@ contains end subroutine Compare_array_{{dim}}d{{name}} {% endfor -%} {# for dim in ALL_DIMS #} -{%- endfor -%} {# for name, type, bits in ALL_TYPES #} +{%- endfor -%} {# for name, type in ALL_TYPES #} end module compare_variables_mod diff --git a/lib/extract/netcdf/dl_esm_inf/README.md b/lib/extract/netcdf/dl_esm_inf/README.md index 28a5615e40..3d18357a2e 100644 --- a/lib/extract/netcdf/dl_esm_inf/README.md +++ b/lib/extract/netcdf/dl_esm_inf/README.md @@ -86,7 +86,7 @@ The compilation process will create the wrapper library previously selected compiler flags. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. To also remove the compiled infrastructure library it is necessary to run ``make allclean`` (this is especially important if changing compilers diff --git a/lib/extract/netcdf/extract_netcdf_base.jinja b/lib/extract/netcdf/extract_netcdf_base.jinja index e112e0b6d3..d175142e7b 100644 --- a/lib/extract/netcdf/extract_netcdf_base.jinja +++ b/lib/extract/netcdf/extract_netcdf_base.jinja @@ -26,18 +26,14 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% 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) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Character", "character(*)" ), + ("Long", "real(kind=int64)" ), + ("Int", "integer(kind=int32)") ] %} {% endif -%} @@ -119,7 +115,7 @@ module extract_netcdf_base_mod {# ------------------------------------------------------------------------- -#} {% set all_declares=[] -%} {% set all_provides=[] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} procedure :: DeclareScalar{{name}} procedure :: WriteScalar{{name}} {{- all_declares.append("DeclareScalar"~name) or "" -}} @@ -318,7 +314,7 @@ contains "Int": "NF90_INT"} -%} {% set indent=" " %} -{% for name, type, bits in ALL_TYPES %} +{% for name, type in ALL_TYPES %} {% set NETCDF_TYPE = NCDF_TYPE_MAPPING[name] %} ! ------------------------------------------------------------------------- !> @brief This subroutine declares a scalar {{type}} value. @@ -506,6 +502,6 @@ contains {% endfor -%} {# for dim in ALL_DIMS #} -{%- endfor -%} {# for name, type, bits in ALL_TYPES #} +{%- endfor -%} {# for name, type in ALL_TYPES #} end module extract_netcdf_base_mod diff --git a/lib/extract/netcdf/generic/README.md b/lib/extract/netcdf/generic/README.md index ad3b0cd8c5..af858bfd53 100644 --- a/lib/extract/netcdf/generic/README.md +++ b/lib/extract/netcdf/generic/README.md @@ -58,7 +58,7 @@ The compilation process will create the wrapper library ``lib_extract.a``. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. ### Linking the wrapper library diff --git a/lib/extract/netcdf/lfric/README.md b/lib/extract/netcdf/lfric/README.md index 2920c09e98..373222d7af 100644 --- a/lib/extract/netcdf/lfric/README.md +++ b/lib/extract/netcdf/lfric/README.md @@ -90,7 +90,7 @@ infrastructure library, ``liblfric_netcdf.a``, if required, with the previously selected compiler flags. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. To also remove the compiled infrastructure library it is necessary to run ``make allclean`` (this is especially important if changing compilers diff --git a/lib/extract/netcdf/read_kernel_data_mod.jinja b/lib/extract/netcdf/read_kernel_data_mod.jinja index 6cfc390c2c..1e04f3ae31 100644 --- a/lib/extract/netcdf/read_kernel_data_mod.jinja +++ b/lib/extract/netcdf/read_kernel_data_mod.jinja @@ -19,18 +19,14 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% 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) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Char", "character(*)" ), + ("Logical","integer(kind=real32)"), + ("Int", "integer(kind=int32)" ) ] %} {% endif -%} @@ -99,7 +95,7 @@ module read_kernel_data_mod {# Collect and declare the various procedures for the same generic interface -#} {# ------------------------------------------------------------------------- -#} {% set all_reads=[] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} procedure :: ReadScalar{{name}} {{- all_reads.append("ReadScalar"~name) or "" }} {% for dim in ALL_DIMS %} @@ -197,7 +193,7 @@ contains "Logical":"NF90_INT", "Int": "NF90_INT"} -%} -{% for name, type, bits in ALL_TYPES %} +{% for name, type in ALL_TYPES %} {% set NETCDF_TYPE = NCDF_TYPE_MAPPING[name] %} ! ------------------------------------------------------------------------- @@ -329,6 +325,6 @@ contains {% endfor -%} {# for dim in ALL_DIMS #} -{%- endfor -%} {# for name, type, bits in ALL_TYPES #} +{%- endfor -%} {# for name, type in ALL_TYPES #} end module read_kernel_data_mod diff --git a/lib/extract/standalone/dl_esm_inf/README.md b/lib/extract/standalone/dl_esm_inf/README.md index 143a3bdcd8..be85e14ea8 100644 --- a/lib/extract/standalone/dl_esm_inf/README.md +++ b/lib/extract/standalone/dl_esm_inf/README.md @@ -77,7 +77,7 @@ The compilation process will create the wrapper library previously selected compiler flags. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. To also remove the compiled infrastructure library it is necessary to run ``make allclean`` (this is especially important if changing compilers diff --git a/lib/extract/standalone/extract_standalone_base.jinja b/lib/extract/standalone/extract_standalone_base.jinja index 38d807e2fd..710ec7a495 100644 --- a/lib/extract/standalone/extract_standalone_base.jinja +++ b/lib/extract/standalone/extract_standalone_base.jinja @@ -26,18 +26,14 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% 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) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Character", "character(*)" ), + ("Long", "real(kind=int64)" ), + ("Int", "integer(kind=int32)") ] %} {% endif -%} @@ -112,7 +108,7 @@ module extract_standalone_base_mod {# ------------------------------------------------------------------------- -#} {% set all_declares=[] -%} {% set all_provides=[] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} procedure :: WriteScalar{{name}} {{- all_provides.append("WriteScalar"~name) or "" }} {% for dim in ALL_DIMS %} @@ -215,7 +211,7 @@ contains end subroutine PostEnd -{% for name, type, bits in ALL_TYPES %} +{% for name, type in ALL_TYPES %} ! ------------------------------------------------------------------------- !> @brief This subroutine writes the value of a scalar {{type}} !! variable to the file. It takes the variable id from the @@ -275,6 +271,6 @@ contains {% endfor -%} {# for dim in ALL_DIMS #} -{%- endfor -%} {# for name, type, bits in ALL_TYPES #} +{%- endfor -%} {# for name, type in ALL_TYPES #} end module extract_standalone_base_mod diff --git a/lib/extract/standalone/generic/README.md b/lib/extract/standalone/generic/README.md index e5d5f681dc..e48d9e2038 100644 --- a/lib/extract/standalone/generic/README.md +++ b/lib/extract/standalone/generic/README.md @@ -47,7 +47,7 @@ The compilation process will create the wrapper library ``lib_extract.a``. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. ### Linking the wrapper library diff --git a/lib/extract/standalone/lfric/README.md b/lib/extract/standalone/lfric/README.md index e975a8b914..192d06219d 100644 --- a/lib/extract/standalone/lfric/README.md +++ b/lib/extract/standalone/lfric/README.md @@ -81,7 +81,7 @@ infrastructure library, ``liblfric.a``, if required, with the previously selected compiler flags. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. To also remove the compiled infrastructure library it is necessary to run ``make allclean`` (this is especially important if changing compilers diff --git a/lib/extract/standalone/read_kernel_data_mod.jinja b/lib/extract/standalone/read_kernel_data_mod.jinja index 8a0faac460..338f74e055 100644 --- a/lib/extract/standalone/read_kernel_data_mod.jinja +++ b/lib/extract/standalone/read_kernel_data_mod.jinja @@ -19,18 +19,14 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% 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) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Char", "character(*)" ), + ("Logical","integer(kind=real32)"), + ("Int", "integer(kind=int32)" ) ] %} {% endif -%} @@ -102,7 +98,7 @@ module read_kernel_data_mod {# Collect and declare the various procedures for the same generic interface -#} {# ------------------------------------------------------------------------- -#} {% set all_reads=[] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} procedure :: ReadScalar{{name}} {{- all_reads.append("ReadScalar"~name) or "" }} {% for dim in ALL_DIMS %} @@ -168,7 +164,7 @@ contains end subroutine OpenReadFileName -{% for name, type, bits in ALL_TYPES %} +{% for name, type in ALL_TYPES %} ! ------------------------------------------------------------------------- !> @brief This subroutine reads the value of a scalar {{type}} @@ -264,6 +260,6 @@ contains {% endfor -%} {# for dim in ALL_DIMS #} -{%- endfor -%} {# for name, type, bits in ALL_TYPES #} +{%- endfor -%} {# for name, type in ALL_TYPES #} end module read_kernel_data_mod diff --git a/lib/process.py b/lib/process.py index 3d21c891a0..14609d30cb 100755 --- a/lib/process.py +++ b/lib/process.py @@ -102,12 +102,12 @@ # --------------------------------------------------------- # This is a mapping from the command line option name # to the tuple that is required for the jinja templates: -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)} +TYPE_DATA = {"real": ("Real", "real(kind=real32)"), + "double": ("Double", "real(kind=real64)"), + "int": ("Int", "integer(kind=int32)"), + "char": ("Char", "character(*)"), + "logical": ("Logical", "Logical(kind=4)"), + "long": ("Long", "integer(kind=int64)")} # --------------------------------------------------------- # Check type information: diff --git a/lib/psy_data_base.jinja b/lib/psy_data_base.jinja index f71a4e0df4..3b6d095f10 100644 --- a/lib/psy_data_base.jinja +++ b/lib/psy_data_base.jinja @@ -12,16 +12,12 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% if ALL_TYPES is not defined %} - {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), - ("Real", "real(kind=real32)", 32), - ("Int", "integer(kind=int32)", 32) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Int", "integer(kind=int32)") ] %} {% endif %} ! ----------------------------------------------------------------------------- @@ -110,7 +106,7 @@ module psy_data_base_mod {# ------------------------------------------------------------- -#} {% set all_declares=[] -%} {% set all_provides=[] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} procedure :: DeclareScalar{{name}} procedure :: ProvideScalar{{name}} {{- all_declares.append("DeclareScalar"~name) or "" -}} @@ -316,7 +312,7 @@ contains ! ========================================================================= ! Jinja created code: ! ========================================================================= - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} ! ------------------------------------------------------------------------- !> @brief This subroutine declares a scalar {{type}} value. This !! implementation only increases the next index, and prints diff --git a/lib/read_only/Makefile b/lib/read_only/Makefile index ef3d023a7b..0cc4789b99 100644 --- a/lib/read_only/Makefile +++ b/lib/read_only/Makefile @@ -48,10 +48,10 @@ F90FLAGS ?= PSYDATA_LIB_DIR ?= ./.. # ----------------------------------------------------------------------------- -# The read-only verification library is implemented for int, real and -# double scalars and 2-dimension arrays -PROCESS_ARGS = -prefix=read_only_verify_ -types=int,real,double \ - -dims=2 +# The read-only verification library is implemented for int, logical, +# characters, real, and double precision scalars and 1 to 4 dimensional arrays +PROCESS_ARGS = -prefix=read_only_verify_ -types=char,int,logical,real,double \ + -dims=1,2,3,4 PROCESS = $$($(PSYDATA_LIB_DIR)/get_python.sh) $(PSYDATA_LIB_DIR)/process.py default: read_only_base.o psy_data_base.o @@ -63,6 +63,7 @@ process: read_only_base.f90 all: $(MAKE) -C dl_esm_inf $(MAKE) -C lfric + $(MAKE) -C generic %.f90: %.jinja $(PROCESS) $(PROCESS_ARGS) $< > $*.f90 @@ -81,3 +82,4 @@ clean: allclean: $(MAKE) -C dl_esm_inf allclean $(MAKE) -C lfric allclean + $(MAKE) -C generic allclean diff --git a/lib/read_only/README.md b/lib/read_only/README.md index 259ecba724..ecb85f83af 100644 --- a/lib/read_only/README.md +++ b/lib/read_only/README.md @@ -38,6 +38,10 @@ Contains the read-only, PSyData-API-based, wrapper library for the [LFRic (Dynamo 0.3) API]( https://psyclone.readthedocs.io/en/stable/dynamo0p3.html). +## [``generic``](./generic) directory + +Contains the generic read-only wrapper library. + diff --git a/lib/read_only/generic/read_only.f90 b/lib/read_only/generic/read_only.f90 new file mode 100644 index 0000000000..89e10a3a3a --- /dev/null +++ b/lib/read_only/generic/read_only.f90 @@ -0,0 +1,63 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2025, 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 J. Henrichs, Bureau of Meteorology + +!> This module implements a verification that read-only variables in generic +!! Fortran code are indeed not modified (e.g. because of memory overwrite) +!! This very slim module is implemented since PSyclone expects +!! certain module and type names, which the base class ReadOnlyBaseType +!! does not provide. + +module read_only_verify_psy_data_mod + + use, intrinsic :: iso_fortran_env, only : int64, int32, & + real32, real64, & + stderr => Error_Unit + + use read_only_base_mod, only : ReadOnlyBaseType, read_only_verify_PSyDataInit, & + read_only_verify_PSyDataShutdown, is_enabled, & + read_only_verify_PSyDataStart, read_only_verify_PSyDataStop + + implicit none + + !> This is the data type that stores a checksum for each read-only + !! variable. A static instance of this type is created for each + !! instrumented region with PSyclone. It is empty, this class is + !! only here to get the right name for PSyclone in case of generic + !! transformation usage. + type, extends(ReadOnlyBaseType), public :: read_only_verify_PSyDataType + + end type read_only_verify_PSyDataType + +end module read_only_verify_psy_data_mod diff --git a/lib/read_only/lfric/README.md b/lib/read_only/lfric/README.md index 32877f30cc..069b19da2f 100644 --- a/lib/read_only/lfric/README.md +++ b/lib/read_only/lfric/README.md @@ -63,7 +63,7 @@ The ``Makefile`` will compile the LFRic infrastructure library, ``liblfric.a``, if required, with the previously selected compiler flags. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. To also remove the compiled infrastructure library it is necessary to run ``make allclean`` (this is especially important if changing compilers diff --git a/lib/read_only/read_only_base.jinja b/lib/read_only/read_only_base.jinja index 64b094f541..6cfec22da4 100644 --- a/lib/read_only/read_only_base.jinja +++ b/lib/read_only/read_only_base.jinja @@ -29,16 +29,12 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% if ALL_TYPES is not defined %} - {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), - ("Real", "real(kind=real32)", 32), - ("Int", "integer(kind=int32)", 32) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Int", "integer(kind=int32)") ] %} {% endif %} ! ----------------------------------------------------------------------------- @@ -117,7 +113,7 @@ module read_only_base_mod {# Collect the various procedures for the same generic interface -#} {# ------------------------------------------------------------- -#} {% set all_provides=[] -%} - {%- for name, type, bits in ALL_TYPES %} + {%- for name, type in ALL_TYPES %} procedure :: ProvideScalar{{name}} {{- all_provides.append("ProvideScalar"~name) or "" }} {% for dim in ALL_DIMS %} @@ -145,7 +141,7 @@ module read_only_base_mod {# Create a generic interface for all ComputeChecksum functions #} {# ------------------------------------------------------------ #} {% set all_compute = [] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} {% for dim in ALL_DIMS %} {{- all_compute.append("ComputeChecksum"~dim~"d"~name) or "" -}} {% endfor %} @@ -265,7 +261,21 @@ contains LFRic which uses ComputeChecksum1dDouble) #} -{% for name, type, bits in ALL_TYPES %} +{% for name, type in ALL_TYPES %} + {# We need the number of bits since there is slightly different code + required for 32 and 64 bit values (due to the fact that the + Fortran transfer(value, mold) function leaves undefined bits + when mold is larger than value. #} + {% if name in ["Real", "Int", "Logical"] %} + {% set bits = 32 %} + {% elif name in ["Double", "Long"] %} + {% set bits = 64 %} + {% elif name in ["Char"] %} + {% set bits = 8 %} + {% else %} + ERROR - UNSUPPORTED BIT SIZE for {{name}}! + {% endif %} + ! ========================================================================= ! Implementation for all {{type}} types @@ -284,16 +294,26 @@ contains character(*), intent(in) :: name {{type}}, intent(in) :: value - {{type}} :: orig_value - integer(kind=int64) :: checksum, int_64 + {% if name == "Char" %} + character, allocatable :: orig_value + integer :: i + {% else %} + {{type}} :: orig_value + {% endif %} + integer(kind=int64) :: checksum, int_64 {% if bits==32 %} - integer(kind=int32) :: int_32 + integer(kind=int32) :: int_32 {% endif %} if (.not. is_enabled) return - {% if bits == 32 %} + {% if name == "Char" %} + checksum = 0 + do i=1, len(value) + checksum = checksum + ichar(value(i:i)) + enddo + {% elif bits == 32 %} ! `transfer` leaves undefined bits in a 64-bit value ! so assign to 32-bit, then assign to 64-bit to have all bits defined int_32 = transfer(value, int_32) @@ -315,20 +335,22 @@ contains write(stderr,*) "------------- PSyData -------------------------" write(stderr,*) "{{type}} variable ", name, " has been modified in ", & trim(this%module_name)," : ", trim(this%region_name) - ! We can recreate the original value which is stored as - ! 64-bit integer as the checksum: - {% if bits == 32 %} + {% if name != "Char" %} + ! We can recreate the original scalar value which is stored as + ! 64-bit integer in the checksum: + {% if bits == 32 %} int_32 = this%checksums(this%next_var_index) orig_value = transfer(int_32, orig_value) - {% elif bits == 64 %} + {% elif bits == 64 %} orig_value = transfer(this%checksums(this%next_var_index), orig_value) - {% elif name == "Logical" %} + {% elif name == "Logical" %} orig_value = this%checksums(this%next_var_index) .eq. 1 - {% else %} + {% else %} ERROR - UNSUPPORTED BIT SIZE {{bits}}! - {% endif %} + {% endif %} write(stderr,*) "Original value: ", orig_value write(stderr,*) "New value: ", value + {% endif %} write(stderr,*) "------------- PSyData -------------------------" else if(this%verbosity>1) then write(stderr,*) "PSyData: checked variable ", trim(name) @@ -400,7 +422,9 @@ contains {{type}}, dimension({{DIMENSION}}) :: field integer :: {{vars}} - {% if bits == 32 %} + {% if name == "Char" %} + integer :: i + {% elif bits == 32 %} integer(kind=int32) :: int_32 {% endif %} integer(kind=int64) :: checksum, int_64 @@ -410,7 +434,12 @@ contains {% for j in range(dim, 0, -1) %} {{" "*3*(dim-j)}}do i{{j}}=1, size(field, {{j}}) {% endfor %} - {% if bits == 32 %} + {% if name == "Char" %} + {{indent}}int_64 = 0 + {{indent}}do i=1, len(field({{vars}})) + {{indent}} int_64 = int_64 + ichar(field({{vars}})(i:i)) + {{indent}}enddo + {% elif bits == 32 %} {{indent}}! transfer leaves undefined bits in a 64-bit target {{indent}}! so we transfer to 32-bits and then assign to 64-bit {{indent}}int_32 = transfer(field({{vars}}), int_32) diff --git a/lib/value_range_check/Makefile b/lib/value_range_check/Makefile index b137bcbc3e..9427778a42 100644 --- a/lib/value_range_check/Makefile +++ b/lib/value_range_check/Makefile @@ -48,10 +48,10 @@ F90FLAGS ?= PSYDATA_LIB_DIR ?= ./.. # ----------------------------------------------------------------------------- -# The nan-test library is implemented for int, real and -# double scalars and 2-dimension arrays -PROCESS_ARGS = -prefix=value_range_check_ -types=int,real,double \ - -dims=2 +# The value-range check library is implemented for int, logical, characters, +# real, and double precision scalars and 1 to 4 dimensional arrays +PROCESS_ARGS = -prefix=value_range_check_ -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/value_range_check/dl_esm_inf/README.md b/lib/value_range_check/dl_esm_inf/README.md index 3fa1eea327..bfe8e05758 100644 --- a/lib/value_range_check/dl_esm_inf/README.md +++ b/lib/value_range_check/dl_esm_inf/README.md @@ -57,7 +57,7 @@ The ``Makefile`` will compile the ``dl_esm_inf`` infrastructure library, ``lib_fd.a``, if required, with the previously selected compiler flags. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. To also remove the compiled infrastructure library it is necessary to run ``make allclean`` (this is especially important if changing compilers diff --git a/lib/value_range_check/lfric/README.md b/lib/value_range_check/lfric/README.md index 52fbc98d1e..e29ef50b70 100644 --- a/lib/value_range_check/lfric/README.md +++ b/lib/value_range_check/lfric/README.md @@ -64,7 +64,7 @@ The ``Makefile`` will compile the LFRic infrastructure library, ``liblfric.a``, if required, with the previously selected compiler flags. Similar to compilation of the [examples]( -https://psyclone.readthedocs.io/en/latest/examples.html#compilation), the +https://psyclone.readthedocs.io/en/latest/tutorials_and_examples.html#compilation), the compiled wrapper library can be removed by running ``make clean``. To also remove the compiled infrastructure library it is necessary to run ``make allclean`` (this is especially important if changing compilers diff --git a/lib/value_range_check/value_range_check_base.jinja b/lib/value_range_check/value_range_check_base.jinja index e3a1c51f75..2f486669fc 100644 --- a/lib/value_range_check/value_range_check_base.jinja +++ b/lib/value_range_check/value_range_check_base.jinja @@ -26,16 +26,14 @@ {# The types that are supported. The first entry of each tuple is the name used when naming subroutines and in user messages. - The second entry is the Fortran declaration. The third entry - is the number of bits. There is slightly different code - required for 32 and 64 bit values (due to the fact that the - Fortran transfer(value, mould) function leaves undefined bits - when mould is larger than value.) #} + The second entry is the Fortran declaration. #} {% if ALL_TYPES is not defined %} - {% set ALL_TYPES = [ ("Double", "real(kind=real64)", 64), - ("Real", "real(kind=real32)", 32), - ("Int", "integer(kind=int32)", 32) ] %} + {% set ALL_TYPES = [ ("Double", "real(kind=real64)" ), + ("Real", "real(kind=real32)" ), + ("Char", "character", ), + ("Logical", "logical", ), + ("Int", "integer(kind=int32)") ] %} {% endif %} ! ----------------------------------------------------------------------------- @@ -126,7 +124,7 @@ module value_range_check_base_mod {# ------------------------------------------------------------------------- -#} {% set all_declares=[] -%} {% set all_provides=[] -%} - {% for name, type, bits in ALL_TYPES %} + {% for name, type in ALL_TYPES %} procedure :: ProvideScalar{{name}} {{- all_provides.append("ProvideScalar"~name) or "" }} {% for dim in ALL_DIMS %} @@ -315,7 +313,7 @@ contains ! Jinja created code. ! ========================================================================= -{% for name, type, bits in ALL_TYPES %} +{% for name, type in ALL_TYPES %} ! ========================================================================= ! Implementation for all {{type}} types @@ -338,6 +336,11 @@ contains character(*), intent(in) :: name {{type}}, intent(in) :: value + {% if name in ["Logical", "Char"] %} + ! We can't compare {{type}} values with a real, nor can + ! we check for non-finite values. So do nothing. + {% else %} + {# Now it's a floating point or integer value #} if (.not. is_enabled) return if (.not. this%env_var_have_been_read) then @@ -346,11 +349,9 @@ contains this%value_ranges(this%next_var_index,:)) endif - {% if name !="Logical" %} - if (this%has_checks(this%next_var_index) == has_min) then {# The spaces take care of proper indentation #} - {{indent}}if (value< this%value_ranges(this%next_var_index,1)) then + {{indent}}if (value < this%value_ranges(this%next_var_index,1)) then {{indent}} write(stderr, '(11G0)') "PSyData: Variable '", & {{indent}} name,"' has the value ", & {{indent}} value, " in module '", trim(this%module_name), & @@ -381,9 +382,11 @@ contains {{indent}} this%value_ranges(this%next_var_index,2),"'." {{indent}}endif endif - {% endif %} - {% if name not in ["Int", "Logical"] %} + {% if name in ["Int", "Long"] %} + ! Variables of type {{type}} do not have NANs, and cannot usefully be + ! checked for non-finite values. So nothing to do here. + {% else -%} {# floating point #} if (IEEE_SUPPORT_DATATYPE(value)) then if (.not. IEEE_IS_FINITE(value)) then write(stderr, '(8G0)') "PSyData: Variable '", name,"' has invalid value ", & @@ -391,10 +394,8 @@ contains "', region '", trim(this%region_name),"'." endif endif - {% else %} - ! Variables of type {{type}} do not have NANs, and cannot usefully be - ! checked for range. So nothing to do here. - {% endif %} + {% endif %} {# floating point value #} + {% endif %} {# floating point or integer #} call this%PSyDataBaseType%ProvideScalar{{name}}(name, value) @@ -430,11 +431,15 @@ contains character(*), intent(in) :: name {{type}}, dimension({{DIMENSION}}), intent(in) :: value - {# IEEE_SUPPORT_DATATYPE does not even compile for int data types #} - {% if name not in ["Int", "Logical"] %} integer :: {{vars}} + {% if name in ["Logical", "Char"] %} + ! We can't compare {{type}} values with a real, nor can + ! we check for non-finite values. So do nothing. + {% else %} + {# Now it's a floating point or integer value #} if (.not. is_enabled) return + if (.not. this%env_var_have_been_read) then call this%get_min_max(this%module_name, this%region_name, & name, this%has_checks(this%next_var_index), & @@ -496,6 +501,10 @@ contains {% endfor %} endif + {% if name in ["Int", "Long"] %} + ! Variables of type {{type}} do not have NANs, and cannot usefully be + ! checked for non-finite values. So nothing to do here. + {% else -%} {# floating point #} if (IEEE_SUPPORT_DATATYPE(value)) then {# The spaces take care of proper indentation #} {% for j in range(dim, 0, -1) %} @@ -512,10 +521,8 @@ contains {{" "*3*(j-1)}}enddo {% endfor %} endif - {% else %} - ! Variables of type {{type}} do not have NANs - ! So nothing to do here. - {% endif %} + {% endif %} {# floating point value #} + {% endif %} {# floating point or integer #} call this%PSyDataBaseType%ProvideArray{{dim}}d{{name}}(name, value) diff --git a/psyclone.pdf b/psyclone.pdf index 56b8d4e313..83e5e97263 100644 Binary files a/psyclone.pdf and b/psyclone.pdf differ