Skip to content

Commit

Permalink
Merge pull request #2435 from stfc/2422_routine_interfaces
Browse files Browse the repository at this point in the history
(Closes #2422) add support for routine interfaces
  • Loading branch information
sergisiso authored Feb 28, 2024
2 parents ac2d03a + d769153 commit 949ed1b
Show file tree
Hide file tree
Showing 23 changed files with 1,471 additions and 683 deletions.
3 changes: 3 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
2) PR #2367 for #2296. Extends ModuleInfo so that it can be queried
for the Routines contained in a Module.

3) PR #2435 for #2422. Add PSyIR GenericInterface to capture the
information about which routines implement a specific interface symbol.

release 2.5.0 14th of February 2024

1) PR #2199 for #2189. Fix bugs with missing maps in enter data
Expand Down
32 changes: 17 additions & 15 deletions doc/developer_guide/psyir_symbols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -316,23 +316,25 @@ INTERFACE` where `generic-spec` is either (`R1207`) a `generic-name`
or one of `OPERATOR`, `ASSIGNMENT` or `dtio-spec` (see
``https://wg5-fortran.org/N1601-N1650/N1601.pdf``).

The PSyIR captures all forms of Fortran interface but is not able to
reason about the content of the interface as the text for this is
stored as an `UnsupportedFortranType`.

If the interface has a generic name and `generic-name` is not already
declared as a PSyIR symbol then the interface is captured as a
`RoutineSymbol` named as `generic-name`. The `generic-name` may
already be declared as a PSyIR symbol if it references a type
declaration or the interface may not have a name. In these two cases
the interface is still captured as a `RoutineSymbol`, but the root
name of the `RoutineSymbol` is `_psyclone_internal_<generic-name>`, or
Interfaces with a `generic-name` used to overload a procedure, e.g.

.. code-block:: fortran
interface dot_prod
module procedure :: dot_prod_r4, dot_prod_r8
end interface dot_prod
are captured in the PSyIR as symbols of `GenericInterfaceSymbol` type (a
sub-class of `RoutineSymbol`), provided that `generic-name` is not already
declared as a PSyIR symbol (as can happen for a constructor of a derived type).
If `generic-name` is not present or is already declared then the interface is
captured instead as a `RoutineSymbol`, but the root
name of this symbol is `_psyclone_internal_<generic-name>`, or
`_psyclone_internal_interface` respectively, i.e. it is given an
internal PSyclone name. The root name should not clash with any other
symbol names as names should not start with `_`, but providing a root
name ensures that unique names are used in any case.

As interfaces are captured as text in an `UnsupportedFortranType` the
`RoutineSymbol` name is not used in the Fortran backend, the text
stored in `UnsupportedFortranType` is simply output.
As such interfaces are captured as text in an `UnsupportedFortranType` the
`RoutineSymbol` name is not used in the Fortran backend; the text
stored in the `UnsupportedFortranType` is simply output.

2 changes: 2 additions & 0 deletions doc/user_guide/psyir.rst
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,8 @@ are:

- .. autoclass:: psyclone.psyir.symbols.RoutineSymbol

- .. autoclass:: psyclone.psyir.symbols.GenericInterfaceSymbol

See the reference guide for the full API documentation of the
:ref_guide:`SymbolTable psyclone.psyir.symbols.html#psyclone.psyir.symbols.SymbolTable`
and the :ref_guide:`Symbol types psyclone.psyir.symbols.html#module-psyclone.psyir.symbols`.
Expand Down
Binary file modified psyclone.pdf
Binary file not shown.
13 changes: 10 additions & 3 deletions src/psyclone/psyad/domain/common/adjoint_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@
INNER_PRODUCT_TOLERANCE = 1500.0


def create_adjoint_name(tl_name):
def create_adjoint_name(tl_name, table=None):
'''Create an adjoint name from the supplied tangent linear name. This
is done by stripping the TL_NAME_PREFIX from the name if it exists
and then adding the ADJOINT_NAME_PREFIX. The adjoint name is also
lower-cased.
lower-cased. If a symbol table is supplied, then this routine also
ensures that the new name is not present in that table.
:param str: the tangent-linear name.
:param table: an optional symbol table in which the new adjoint name must
be unique.
:type table: Optional[:py:class:`psyclone.psyir.symbols.SymbolTable`]
:returns: the adjoint name.
:rtype: str
Expand All @@ -70,7 +74,10 @@ def create_adjoint_name(tl_name):
adj_name = tl_name.lower()
if adj_name.startswith(TL_NAME_PREFIX):
adj_name = adj_name[len(TL_NAME_PREFIX):]
return ADJOINT_NAME_PREFIX + adj_name
base_name = ADJOINT_NAME_PREFIX + adj_name
if table:
return table.next_available_name(base_name)
return base_name


def create_real_comparison(sym_table, kernel, var1, var2):
Expand Down
19 changes: 9 additions & 10 deletions src/psyclone/psyad/domain/lfric/lfric_adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
from psyclone.psyad import AdjointVisitor
from psyclone.psyad.domain.common import create_adjoint_name
from psyclone.psyir.nodes import Routine
from psyclone.psyir.symbols import RoutineSymbol, ContainerSymbol
from psyclone.psyir.symbols import (
ContainerSymbol, GenericInterfaceSymbol, RoutineSymbol)
from psyclone.psyir.symbols.symbol import ArgumentInterface, ImportInterface


Expand Down Expand Up @@ -116,17 +117,15 @@ def generate_lfric_adjoint(tl_psyir, active_variables):
# help fix this problem as it would only be arguments that would
# need to have the same names.

ctr_table = ad_container.symbol_table

# Re-name the routines that we've adjointed.
for routine in routines:

# We need to re-name the kernel routine.
kernel_sym = ad_container.symbol_table.lookup(routine.name)
adj_kernel_name = create_adjoint_name(routine.name)
# A symbol's name is immutable so create a new RoutineSymbol
adj_kernel_sym = ad_container.symbol_table.new_symbol(
adj_kernel_name, symbol_type=RoutineSymbol,
visibility=kernel_sym.visibility)
ad_container.symbol_table.remove(kernel_sym)
routine.name = adj_kernel_sym.name
kernel_sym = ctr_table.lookup(routine.name)
adj_kernel_name = create_adjoint_name(routine.name, table=ctr_table)
ctr_table.rename_symbol(kernel_sym, adj_kernel_name)
routine.name = adj_kernel_name

logger.debug("AD LFRic kernel will be named '%s'", routine.name)

Expand Down
85 changes: 62 additions & 23 deletions src/psyclone/psyir/backend/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@
Operation, Range, Routine, Schedule, UnaryOperation)
from psyclone.psyir.symbols import (
ArgumentInterface, ArrayType, ContainerSymbol, DataSymbol, DataTypeSymbol,
UnresolvedType, RoutineSymbol, ScalarType, Symbol, IntrinsicSymbol,
SymbolTable, UnsupportedFortranType, UnsupportedType, UnresolvedInterface,
StructureType)
GenericInterfaceSymbol, IntrinsicSymbol, RoutineSymbol,
ScalarType, StructureType, Symbol, SymbolTable, UnresolvedInterface,
UnresolvedType, UnsupportedFortranType, UnsupportedType, )


# Mapping from PSyIR types to Fortran data types. Simply reverse the
Expand Down Expand Up @@ -497,9 +497,9 @@ def gen_vardecl(self, symbol, include_visibility=False):
or derived-type member.
:param symbol: the symbol or member instance.
:type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` or \
:type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` or
:py:class:`psyclone.psyir.nodes.MemberReference`
:param bool include_visibility: whether to include the visibility of \
:param bool include_visibility: whether to include the visibility of
the symbol in the generated declaration (default False).
:returns: the Fortran variable declaration as a string.
Expand Down Expand Up @@ -531,11 +531,11 @@ def gen_vardecl(self, symbol, include_visibility=False):
f" from a Fortran USE statement and should be "
f"generated by 'gen_use' instead of "
f"'gen_vardecl'.")
if isinstance(symbol, RoutineSymbol) and not \
isinstance(symbol.datatype, UnsupportedFortranType):
if (isinstance(symbol, RoutineSymbol) and
not isinstance(symbol.datatype, UnsupportedFortranType)):
raise InternalError(f"Symbol '{symbol.name}' is a RoutineSymbol "
f"which is not imported nor an interface "
f"(UnsupportedFortranType). This is already "
f"which is not imported or of "
f"UnsupportedFortranType. This is already "
f"implicitly declared by the routine itself "
f"and should not be provided to 'gen_vardecl'."
)
Expand Down Expand Up @@ -622,6 +622,44 @@ def gen_vardecl(self, symbol, include_visibility=False):

return result + "\n"

def gen_interfacedecl(self, symbol):
'''
Generate the declaration for a generic interface.
Since a GenericInterfaceSymbol is a subclass of RoutineSymbol, any
necessary accessibility statement will be generated in
gen_access_stmts().
:param symbol: the GenericInterfaceSymbol to be declared.
:type symbol: :py:class:`psyclone.psyir.symbols.GenericInterfaceSymbol`
:returns: the corresponding Fortran declaration.
:rtype: str
:raises InternalError: if passed something that is not a
GenericInterfaceSymbol.
'''
if not isinstance(symbol, GenericInterfaceSymbol):
raise InternalError(
f"gen_interfacedecl only supports 'GenericInterfaceSymbol's "
f"but got '{type(symbol).__name__}'")

decln = f"{self._nindent}interface {symbol.name}\n"
self._depth += 1
# Any module procedures.
routines = ", ".join([rsym.name for rsym in symbol.container_routines])
if routines:
decln += f"{self._nindent}module procedure :: {routines}\n"
# Any other (external) procedures.
routines = ", ".join([rsym.name for rsym in symbol.external_routines])
if routines:
decln += f"{self._nindent}procedure :: {routines}\n"
self._depth -= 1
decln += f"{self._nindent}end interface {symbol.name}\n"

return decln

def gen_typedecl(self, symbol, include_visibility=True):
'''
Creates a derived-type declaration for the supplied DataTypeSymbol.
Expand Down Expand Up @@ -939,22 +977,23 @@ def gen_decls(self, symbol_table, is_module_scope=False):
# As a convention, we will declare the variables in the following
# order:

# 1: Routines (Interfaces)
# 1: Routine declarations and interfaces. (Note that accessibility
# statements are generated in gen_access_stmts().)
for sym in all_symbols[:]:
if isinstance(sym, RoutineSymbol):
# Interfaces to module procedures are captured by the frontend
# as RoutineSymbols of UnsupportedFortranType. These must
# therefore be declared.
if isinstance(sym.datatype, UnsupportedType):
declarations += self.gen_vardecl(
if not isinstance(sym, RoutineSymbol):
continue
# Interfaces can be GenericInterfaceSymbols or RoutineSymbols
# of UnsupportedFortranType.
if isinstance(sym, GenericInterfaceSymbol):
declarations += self.gen_interfacedecl(sym)
elif isinstance(sym.datatype, UnsupportedType):
declarations += self.gen_vardecl(
sym, include_visibility=is_module_scope)
elif sym.is_modulevar or sym.is_automatic:
pass
else:
raise VisitorError(
f"Routine symbol '{sym.name}' has '{sym.interface}'. "
f"This is not supported by the Fortran back-end.")
all_symbols.remove(sym)
elif not (sym.is_modulevar or sym.is_automatic):
raise VisitorError(
f"Routine symbol '{sym.name}' has '{sym.interface}'. "
f"This is not supported by the Fortran back-end.")
all_symbols.remove(sym)

# 2: Constants.
declarations += self._gen_parameter_decls(symbol_table,
Expand Down
Loading

0 comments on commit 949ed1b

Please sign in to comment.