Skip to content

Commit

Permalink
Merge pull request #2230 from stfc/2228_unknown_kernel_symbol
Browse files Browse the repository at this point in the history
Improve error message when a kernel functor is not explicitly included in a use statement (closes #2228)
  • Loading branch information
sergisiso authored Jul 25, 2023
2 parents fd61e6f + f6c0fdc commit 8270181
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 17 deletions.
3 changes: 3 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,9 @@
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.

release 2.3.1 17th of June 2022

1) PR #1747 for #1720. Adds support for If blocks to PSyAD.
Expand Down
61 changes: 44 additions & 17 deletions src/psyclone/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__

Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
91 changes: 91 additions & 0 deletions src/psyclone/tests/generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@

import os
import re
import shutil
import stat
from sys import modules

import pytest

from fparser.common.readfortran import FortranStringReader
Expand Down Expand Up @@ -1274,3 +1276,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))

0 comments on commit 8270181

Please sign in to comment.