Repository: stfc/PSyclone
Pull request #2477 (2024-01-15 05:22:23)

#2463 Support namelist in PSyIR (as an UnknownFortranType).
This basically mirrors the handling of common blocks - adding a namelist as an UnknownFortranType (with `_PSYCLONE_INTERNAL_NAMELIST` as name). For @sergisiso I'd guess.


Patch:
diff --git a/changelog b/changelog
index 36c5790bb..3a1da3161 100644
--- a/changelog
+++ b/changelog
@@ -1,3 +1,7 @@
+	1) PR #2477 for #2463. Add support for Fortran Namelist statements.
+
+release 2.5.0 14th of February 2024
+
 	1) PR #2199 for #2189. Fix bugs with missing maps in enter data
 	directive.
 
diff --git a/examples/nemo/scripts/acc_kernels_trans.py b/examples/nemo/scripts/acc_kernels_trans.py
index d3218949c..6b24da005 100755
--- a/examples/nemo/scripts/acc_kernels_trans.py
+++ b/examples/nemo/scripts/acc_kernels_trans.py
@@ -102,7 +102,9 @@ ACC_IGNORE = ["day_mth",  # Just calendar operations
               "copy_obfbdata", "merge_obfbdata",
               "turb_ncar",  # Transforming hurts performance
               "iom_open", "iom_get_123d", "iom_nf90_rp0123d",
-              "trc_bc_ini", "p2z_ini", "p4z_ini"]  # Str handling, init routine
+              "trc_bc_ini", "p2z_ini", "p4z_ini", "sto_par_init",
+              "bdytide_init", "bdy_init", "bdy_segs", "sbc_cpl_init",
+              "asm_inc_init", "dia_obs_init"]  # Str handling, init routine
 
 # Currently fparser has no way of distinguishing array accesses from
 # function calls if the symbol is imported from some other module.
diff --git a/examples/nemo/scripts/acc_loops_trans.py b/examples/nemo/scripts/acc_loops_trans.py
index 8ee5d5710..33dcd4da6 100755
--- a/examples/nemo/scripts/acc_loops_trans.py
+++ b/examples/nemo/scripts/acc_loops_trans.py
@@ -73,6 +73,13 @@ def trans(psy):
             print("Skipping", invoke.name)
             continue
 
+        # S-0074-Illegal number or type of arguments to ubound [and lbound]
+        # - keyword argument array; and  NVFORTRAN-S-0082-Illegal substring
+        # expression for variable filtide
+        if invoke.name in ("bdytide_init", "sbc_cpl_init"):
+            print("Skipping", invoke.name)
+            continue
+
         # TODO #1841: These files have a bug in the array-range-to-loop
         # transformation. One leads to the following compiler error
         # NVFORTRAN-S-0083-Vector expression used where scalar expression
@@ -83,7 +90,7 @@ def trans(psy):
 
         # This are functions with scalar bodies, we don't want to parallelise
         # them, but we could:
-        # - Inine them
+        # - Inline them
         # - Annotate them with 'omp declare target' and allow to call from gpus
         if invoke.name in ("q_sat", "sbc_dcy", "gamma_moist", "cd_neutral_10m",
                            "psi_h", "psi_m"):
@@ -95,7 +102,7 @@ def trans(psy):
         # Could not find allocated-variable index for symbol - xxx
         # This all happen on characters arrays, e.g. cd_nat
         if invoke.name in ("lbc_nfd_2d_ptr", "lbc_nfd_3d_ptr",
-                           "lbc_nfd_4d_ptr", "bdy_dyn"):
+                           "lbc_nfd_4d_ptr", "bdy_dyn", "dia_obs_init"):
             print("Skipping", invoke.name)
             continue
 
diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py
index 261cd0821..68383ab85 100755
--- a/examples/nemo/scripts/omp_cpu_trans.py
+++ b/examples/nemo/scripts/omp_cpu_trans.py
@@ -81,9 +81,11 @@ def trans(psy):
             print("Skipping normalisation for ", invoke.name)
 
         elif invoke.name in (
-                "trc_oce_rgb",  # Produces incorrect results
-                "removepoints"  # Compiler error: The shapes of the array
-                                # expressions do not conform
+                "trc_oce_rgb",   # Produces incorrect results
+                "removepoints",  # Compiler error: The shapes of the array
+                                 # expressions do not conform
+                "bdytide_init"   # An array-valued argument is required ...
+                                 # (string argument)
                 ):
             # TODO #1841: These subroutines have a bug in the
             # array-range-to-loop transformation.
diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py
index 7308ee685..0aa6fbe61 100755
--- a/examples/nemo/scripts/omp_gpu_trans.py
+++ b/examples/nemo/scripts/omp_gpu_trans.py
@@ -85,6 +85,12 @@ def trans(psy):
             print("Skipping", invoke.name)
             continue
 
+        # The nvidia compiler does not compile a loop over a string in
+        # this subroutine:
+        if invoke.name in ("bdytide_init", "sbc_cpl_init"):
+            print("Skipping", invoke.name)
+            continue
+
         # This are functions with scalar bodies, we don't want to parallelise
         # them, but we could:
         # - Inine them
diff --git a/examples/nemo/scripts/utils.py b/examples/nemo/scripts/utils.py
index 23fa5eb87..96b9e3e9e 100755
--- a/examples/nemo/scripts/utils.py
+++ b/examples/nemo/scripts/utils.py
@@ -318,8 +318,10 @@ def insert_explicit_loop_parallelism(
                 num_nested_loops += 1
 
                 # If it has more than one children, the next loop will not be
-                # perfectly nested, so stop searching
-                if len(next_loop.loop_body.children) > 1:
+                # perfectly nested, so stop searching. If there is no child,
+                # we have an empty loop (which would cause a crash when
+                # accessing the child next)
+                if len(next_loop.loop_body.children) != 1:
                     break
 
                 next_loop = next_loop.loop_body.children[0]
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index 2e30c32c3..fc064e53a 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -58,12 +58,12 @@ from psyclone.psyir.nodes.array_mixin import ArrayMixin
 from psyclone.psyir.nodes.array_of_structures_mixin import (
     ArrayOfStructuresMixin)
 from psyclone.psyir.symbols import (
-    ArgumentInterface, ArrayType, ContainerSymbol, DataSymbol, DataTypeSymbol,
-    UnresolvedType, ImportInterface, AutomaticInterface, NoType,
-    RoutineSymbol, ScalarType, StructureType, Symbol, SymbolError, SymbolTable,
-    UnsupportedFortranType, UnsupportedType, UnresolvedInterface, INTEGER_TYPE,
-    StaticInterface, DefaultModuleInterface, UnknownInterface,
-    CommonBlockInterface)
+    ArgumentInterface, ArrayType, AutomaticInterface, CommonBlockInterface,
+    ContainerSymbol, DataSymbol, DataTypeSymbol, DefaultModuleInterface,
+    ImportInterface, INTEGER_TYPE, NoType, RoutineSymbol, ScalarType,
+    StaticInterface, StructureType, Symbol, SymbolError, SymbolTable,
+    UnknownInterface, UnresolvedInterface, UnresolvedType,
+    UnsupportedFortranType, UnsupportedType)
 
 # fparser dynamically generates classes which confuses pylint membership checks
 # pylint: disable=maybe-no-member
@@ -1844,6 +1844,7 @@ class Fparser2Reader():
             attributes are found in a symbol declaration.
 
         '''
+        # pylint: disable=too-many-arguments
         (type_spec, attr_specs, entities) = decl.items
 
         # Parse the type_spec
@@ -2516,6 +2517,7 @@ class Fparser2Reader():
                                    Fortran2003.Use_Stmt)):
                 # These node types are handled separately
                 pass
+
             elif isinstance(node, Fortran2003.Implicit_Part):
                 # Anything other than a PARAMETER statement or an
                 # IMPLICIT NONE means we can't handle this code.
@@ -2532,6 +2534,18 @@ class Fparser2Reader():
                     raise NotImplementedError(
                         f"Error processing implicit-part: implicit variable "
                         f"declarations not supported but found '{node}'")
+
+            elif isinstance(node, Fortran2003.Namelist_Stmt):
+                # Place the declaration statement into the symbol table using
+                # an internal symbol name. In case that we need more details
+                # (e.g. to update symbol information), the following code
+                # loops over namelist and each symbol:
+                # for namelist_object in node.children:
+                #    for symbol_name in namelist_object[1].items:
+                parent.symbol_table.new_symbol(
+                    root_name="_PSYCLONE_INTERNAL_NAMELIST",
+                    symbol_type=DataSymbol,
+                    datatype=UnsupportedFortranType(str(node)))
             else:
                 raise NotImplementedError(
                     f"Error processing declarations: fparser2 node of type "
@@ -2542,9 +2556,9 @@ class Fparser2Reader():
         # symbols and can appear in any order.
         self._process_parameter_stmts(nodes, parent)
 
-        # We process the nodes again looking for common blocks. We do this
-        # here, after the main declarations loop, because they modify the
-        # interface of existing symbols and can appear in any order.
+        # We process the nodes again looking for common blocks.
+        # We do this here, after the main declarations loop, because they
+        # modify the interface of existing symbols and can appear in any order.
         self._process_common_blocks(nodes, parent)
 
         if visibility_map is not None:
diff --git a/src/psyclone/psyir/symbols/__init__.py b/src/psyclone/psyir/symbols/__init__.py
index 9a47f8184..df7362180 100644
--- a/src/psyclone/psyir/symbols/__init__.py
+++ b/src/psyclone/psyir/symbols/__init__.py
@@ -33,6 +33,7 @@
 # -----------------------------------------------------------------------------
 # Author S. Siso, STFC Daresbury Lab
 # Modified by A. R. Porter and R. W. Ford, STFC Daresbury Lab
+# Modified by J. Henrichs, Bureau of Meteorology
 # -----------------------------------------------------------------------------
 
 ''' Symbols package module '''
@@ -40,25 +41,27 @@
 from psyclone.psyir.symbols.datasymbol import DataSymbol
 from psyclone.psyir.symbols.containersymbol import ContainerSymbol
 from psyclone.psyir.symbols.data_type_symbol import DataTypeSymbol
-from psyclone.psyir.symbols.interfaces import AutomaticInterface, \
-    ImportInterface, ArgumentInterface, UnresolvedInterface, StaticInterface, \
-    DefaultModuleInterface, UnknownInterface, CommonBlockInterface
+from psyclone.psyir.symbols.interfaces import (
+     ArgumentInterface, AutomaticInterface, CommonBlockInterface,
+     DefaultModuleInterface, ImportInterface,
+     StaticInterface, UnknownInterface, UnresolvedInterface)
 from psyclone.psyir.symbols.intrinsic_symbol import IntrinsicSymbol
 from psyclone.psyir.symbols.routinesymbol import RoutineSymbol
 from psyclone.psyir.symbols.symbol import Symbol, SymbolError
 from psyclone.psyir.symbols.symbol_table import SymbolTable
 from psyclone.psyir.symbols.typed_symbol import TypedSymbol
-from psyclone.psyir.symbols.datatypes import DataType, ScalarType, \
-    ArrayType, TYPE_MAP_TO_PYTHON, REAL_TYPE, REAL_SINGLE_TYPE, \
-    REAL_DOUBLE_TYPE, REAL4_TYPE, REAL8_TYPE, INTEGER_TYPE, \
-    INTEGER_SINGLE_TYPE, INTEGER_DOUBLE_TYPE, INTEGER4_TYPE, INTEGER8_TYPE, \
-    BOOLEAN_TYPE, CHARACTER_TYPE, UnresolvedType, UnsupportedType, \
-    UnsupportedFortranType, StructureType, NoType
+from psyclone.psyir.symbols.datatypes import (
+     ArrayType, BOOLEAN_TYPE, CHARACTER_TYPE, DataType, INTEGER4_TYPE,
+     INTEGER8_TYPE, INTEGER_DOUBLE_TYPE, INTEGER_SINGLE_TYPE, INTEGER_TYPE,
+     NoType, REAL4_TYPE, REAL8_TYPE, REAL_DOUBLE_TYPE, REAL_SINGLE_TYPE,
+     REAL_TYPE, ScalarType, StructureType, TYPE_MAP_TO_PYTHON, UnresolvedType,
+     UnsupportedType, UnsupportedFortranType)
 
 # The entities in the __all__ list are made available to import directly from
 # this package e.g. 'from psyclone.psyir.symbols import DataSymbol'
 __all__ = ['ArgumentInterface',
            'ArrayType',
+           'AutomaticInterface',
            'BOOLEAN_TYPE',
            'CHARACTER_TYPE',
            'CommonBlockInterface',
@@ -74,7 +77,7 @@ __all__ = ['ArgumentInterface',
            'INTEGER_DOUBLE_TYPE',
            'INTEGER4_TYPE',
            'INTEGER8_TYPE',
-           'AutomaticInterface',
+           'IntrinsicSymbol',
            'NoType',
            'REAL_TYPE',
            'REAL_SINGLE_TYPE',
diff --git a/src/psyclone/psyir/symbols/interfaces.py b/src/psyclone/psyir/symbols/interfaces.py
index ac478b8d9..5da7024b6 100644
--- a/src/psyclone/psyir/symbols/interfaces.py
+++ b/src/psyclone/psyir/symbols/interfaces.py
@@ -32,6 +32,7 @@
 # POSSIBILITY OF SUCH DAMAGE.
 # -----------------------------------------------------------------------------
 # Author: S. Siso STFC Daresbury Lab
+# Modified by: J. Henrichs, Bureau of Meteorology
 # -----------------------------------------------------------------------------
 
 ''' This module contains the SymbolInterface class and its subclasses. '''


Test patch:
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_namelist_test.py b/src/psyclone/tests/psyir/frontend/fparser2_namelist_test.py
new file mode 100644
index 000000000..905523475
--- /dev/null
+++ b/src/psyclone/tests/psyir/frontend/fparser2_namelist_test.py
@@ -0,0 +1,135 @@
+# -----------------------------------------------------------------------------
+# BSD 3-Clause License
+#
+# Copyright (c) 2023-2024, 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
+#         J. Henrichs, Bureau of Meteorology
+# -----------------------------------------------------------------------------
+
+''' Tests Fortran namelist statements in the fparser2 PSyIR front-end '''
+
+import pytest
+from fparser.common.readfortran import FortranStringReader
+from fparser.two.Fortran2003 import Specification_Part
+from psyclone.psyir.frontend.fparser2 import Fparser2Reader
+from psyclone.psyir.nodes import Routine
+from psyclone.psyir.symbols import UnsupportedFortranType
+
+
+@pytest.mark.usefixtures("f2008_parser")
+def test_named_namelist():
+    ''' Test that namelists are correctly captured. '''
+
+    # Create a dummy test routine
+    routine = Routine("test_routine")
+    symtab = routine.symbol_table
+    processor = Fparser2Reader()
+
+    # Test with a single namelist
+    reader = FortranStringReader('''
+        integer :: a, b, c
+        NAMELIST /name1/ a, b,  c''')
+    fparser2spec = Specification_Part(reader)
+    processor.process_declarations(routine, fparser2spec.content, [])
+
+    # There is a name1 namelist symbol
+    namelist = symtab.lookup("_PSYCLONE_INTERNAL_NAMELIST")
+    assert isinstance(namelist.datatype, UnsupportedFortranType)
+    assert namelist.datatype.declaration == "NAMELIST /name1/ a, b, c"
+
+
+@pytest.mark.usefixtures("f2008_parser")
+def test_multiple_namelists_in_statement():
+    ''' Test that namelist statements with multiple namelists are handled
+    correctly.'''
+
+    # Create a dummy test routine
+    routine = Routine("test_routine")
+    symtab = routine.symbol_table
+    processor = Fparser2Reader()
+
+    # And provide a namelist statement containing two namelists
+    reader = FortranStringReader('''
+        integer :: a, b, c, d
+        namelist /name1/ a, b /name2/ c
+        namelist /name2/ d''')
+    fparser2spec = Specification_Part(reader)
+    processor.process_declarations(routine, fparser2spec.content, [])
+
+    # There is a UnsupportedFortranType symbol containing each the namelist
+    namelist = symtab.lookup("_PSYCLONE_INTERNAL_NAMELIST")
+    assert isinstance(namelist.datatype, UnsupportedFortranType)
+    assert (namelist.datatype.declaration ==
+            "NAMELIST /name1/ a, b, /name2/ c")
+    namelist = symtab.lookup("_PSYCLONE_INTERNAL_NAMELIST_1")
+    assert isinstance(namelist.datatype, UnsupportedFortranType)
+    assert namelist.datatype.declaration == "NAMELIST /name2/ d"
+
+
+@pytest.mark.usefixtures("f2008_parser")
+def test_namelist_with_posterior_declaration():
+    ''' Test that namelists with symbols that are declared after the
+    namelist statement are handled correctly.'''
+
+    # Create a dummy test routine
+    routine = Routine("test_routine")
+    symtab = routine.symbol_table
+    processor = Fparser2Reader()
+
+    reader = FortranStringReader('''
+        namelist /name1/ a, b
+        integer :: a, b''')
+    fparser2spec = Specification_Part(reader)
+    processor.process_declarations(routine, fparser2spec.content, [])
+
+    # There is an UnsupportedFortranType symbol containing the namelist
+    namelist = symtab.lookup("_PSYCLONE_INTERNAL_NAMELIST")
+    assert isinstance(namelist.datatype, UnsupportedFortranType)
+    assert namelist.datatype.declaration == "NAMELIST /name1/ a, b"
+
+
+def test_static_namelist_variables(fortran_reader, fortran_writer):
+    ''' Test that a variable in a namelist can be static (either
+    explicit using save, or implicit by assigning it a value in
+    the declaration.'''
+
+    code = '''program test
+    integer :: a=1
+    integer, save :: b=2
+    namelist /my_namelist/ a, b
+    end program test'''
+    psyir = fortran_reader.psyir_from_source(code)
+
+    # Make sure no exception is raised here (if 'a' gets a new interface,
+    # it's not static, and the FortranWriter raises an exception
+    out = fortran_writer(psyir)
+    assert "NAMELIST /my_namelist/ a, b" in out
diff --git a/src/psyclone/tests/psyir/symbols/interfaces_test.py b/src/psyclone/tests/psyir/symbols/interfaces_test.py
index 3f604e85b..95ed01944 100644
--- a/src/psyclone/tests/psyir/symbols/interfaces_test.py
+++ b/src/psyclone/tests/psyir/symbols/interfaces_test.py
@@ -41,10 +41,10 @@
 
 import pytest
 
-from psyclone.psyir.symbols.interfaces import SymbolInterface, \
-    AutomaticInterface, ArgumentInterface, ImportInterface, \
-    UnresolvedInterface, StaticInterface, DefaultModuleInterface, \
-    CommonBlockInterface, UnknownInterface
+from psyclone.psyir.symbols.interfaces import (
+     AutomaticInterface, ArgumentInterface, CommonBlockInterface,
+     DefaultModuleInterface, ImportInterface,
+     StaticInterface, SymbolInterface, UnknownInterface, UnresolvedInterface)
 from psyclone.psyir.symbols import ContainerSymbol