Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Closes #2642, initial step towards #2643) Implement extends(type), procedure in derived type, class in declarations. #2644

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a527543
Add `type, extends` and `procedure` support in `StructureType`.
JulienRemy Jun 25, 2024
b95abdc
Add support for `class` keyword in declarations.
JulienRemy Jul 1, 2024
349bd2b
Cleanup.
JulienRemy Jul 2, 2024
f3bb061
#2642 Implement `extends`, `procedure` in derived types, `class` keyw…
JulienRemy Jul 2, 2024
db3d952
#2642 Add missing `class` keyword in Fortran backend.
JulienRemy Jul 15, 2024
6d0bb74
#2642 Fix `replace` calls for #2643 workaround.
JulienRemy Jul 15, 2024
a449c31
Merge branch 'master' into 2642_implement_extends_procedure_and_class
Nov 28, 2024
16ed2e4
#2642 Fix flake8 formatting and some tests.
Nov 28, 2024
e182c18
#2642 Some codecov progress, some tests still missing.
Nov 28, 2024
ff993ea
Merge branch 'master' into 2642_implement_extends_procedure_and_class
Nov 28, 2024
8c789b7
#2642 More tests.
Nov 29, 2024
4247a00
Merge branch 'master' into 2642_implement_extends_procedure_and_class
Nov 29, 2024
1cd37e4
#2642 Edit dev doc
Nov 29, 2024
0719559
#2642 Cleanup
Nov 29, 2024
6698427
#2642 More codecov
Nov 29, 2024
cc6fb67
#2642 Flake8
Nov 29, 2024
c8f74cb
#2642 Remove nonsensical test. Edit psyGen w.r.t. procedure support.
Dec 2, 2024
c35d98b
#2642 Avoid useless extends DataTypeSymbol visibility and procedure d…
Dec 2, 2024
0923123
Merge branch 'master' into 2642_implement_extends_procedure_and_class
Dec 2, 2024
18679fa
#2642 Add test about unkown parent type not being declared in the mod…
Dec 2, 2024
706bffc
#2642 Add psyGen tests and cleanup.
Dec 2, 2024
b72336b
#2642 Edits w.r.t. Andy's review
Dec 11, 2024
809ebf2
#2642 codecov
Dec 11, 2024
6f18710
#2642 flake8...
Dec 11, 2024
9419a05
#2642 Refactor, codecov
Dec 11, 2024
421e546
Merge branch 'master' into 2642_implement_extends_procedure_and_class
arporter Dec 18, 2024
a862a21
#2642 Fix parent type to UnresolvedInterface and add to symbol table.
Dec 18, 2024
cbc5857
#2642 Edits wrt Andy's second review (all except `class(*)`)
Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions doc/developer_guide/psyir_symbols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,15 @@ explicitly listed may be assumed to be unsupported):
+----------------------+--------------------+--------------------+
| |Supported |Unsupported |
+======================+====================+====================+
|Variables |ALLOCATABLE |CLASS |
|Variables |ALLOCATABLE, CLASS | |
+----------------------+--------------------+--------------------+
| |CHARACTER, DOUBLE |COMPLEX, CHARACTER |
| |PRECISION, INTEGER, |with LEN or KIND |
| |LOGICAL, REAL | |
+----------------------+--------------------+--------------------+
| |Derived Types |'extends', |
| | |'abstract' or with |
| | |CONTAINS; Operator |
| | |overloading |
| |Derived Types, |'abstract', |
| |'extends', |operator overloading|
| |CONTAINS | |
+----------------------+--------------------+--------------------+
| |DIMENSION |Array extents |
| | |specified using |
Expand Down
20 changes: 14 additions & 6 deletions src/psyclone/domain/gocean/kernel/psyir.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@
from psyclone.errors import InternalError
from psyclone.parse.utils import ParseError
from psyclone.psyir.frontend.fortran import FortranReader
from psyclone.psyir.backend.fortran import FortranWriter
from psyclone.psyir.nodes import Container
from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType
from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType, \
StructureType


class GOceanContainer(Container):
Expand Down Expand Up @@ -222,16 +224,22 @@ def create_from_psyir(symbol):

datatype = symbol.datatype

if not isinstance(datatype, UnsupportedFortranType):
if not isinstance(datatype, StructureType):
raise InternalError(
f"Expected kernel metadata to be stored in the PSyIR as "
f"an UnsupportedFortranType, but found "
f"a StructureType, but found "
f"{type(datatype).__name__}.")

# In an UnsupportedFortranType, the declaration is stored as a
# string, so use create_from_fortran_string()
# TODO #2643: This is a temporary solution using FortranWriter
# to allow the current metadata extraction to work with StructureType,
# instead of relying on UnsupportedFortranType.
# This will be removed when the metadata is extracted from the PSyIR
# itself.
type_declaration = FortranWriter().gen_typedecl(symbol)
type_declaration = type_declaration.replace(", public", "")
type_declaration = type_declaration.replace(", private", "")
return GOceanKernelMetadata.create_from_fortran_string(
arporter marked this conversation as resolved.
Show resolved Hide resolved
datatype.declaration)
type_declaration)

@staticmethod
def create_from_fortran_string(fortran_string):
Expand Down
14 changes: 13 additions & 1 deletion src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@
from psyclone.errors import InternalError
from psyclone.parse.utils import ParseError
from psyclone.psyir.frontend.fortran import FortranReader
from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType
from psyclone.psyir.backend.fortran import FortranWriter
from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType, \
arporter marked this conversation as resolved.
Show resolved Hide resolved
StructureType

# pylint: disable=too-many-lines
# pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -698,6 +700,16 @@ def create_from_psyir(symbol):

datatype = symbol.datatype

# TODO #2643: This is a temporary solution using FortranWriter
# to allow the current metadata extraction to work with StructureType,
# instead of relying on UnsupportedFortranType.
# This will be removed when the metadata is extracted from the PSyIR
# itself.
if isinstance(datatype, StructureType):
type_declaration = FortranWriter().gen_typedecl(symbol, False)
return LFRicKernelMetadata.create_from_fortran_string(
arporter marked this conversation as resolved.
Show resolved Hide resolved
type_declaration)

if not isinstance(datatype, UnsupportedFortranType):
raise InternalError(
f"Expected kernel metadata to be stored in the PSyIR as "
Expand Down
45 changes: 44 additions & 1 deletion src/psyclone/psyGen.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
UnresolvedType,
ImportInterface, INTEGER_TYPE,
RoutineSymbol, Symbol)
from psyclone.psyir.symbols.datatypes import UnsupportedFortranType
from psyclone.psyir.symbols.datatypes import (UnsupportedFortranType,
StructureType)

# The types of 'intent' that an argument to a Fortran subroutine
# may have
Expand Down Expand Up @@ -1753,6 +1754,8 @@ def _rename_psyir(self, suffix):
# the kernel metadata.
container_table = container.symbol_table
for sym in container_table.datatypesymbols:
# Either the DataTypeSymbol is of UnsupportedFortranType,
# in which case we replace in its whole declaration.
if isinstance(sym.datatype, UnsupportedFortranType):
new_declaration = sym.datatype.declaration.replace(
orig_kern_name, new_kern_name)
Expand All @@ -1761,6 +1764,46 @@ def _rename_psyir(self, suffix):
new_declaration,
partial_datatype=sym.datatype.partial_datatype)
# pylint: enable=protected-access
# Or the DataTypeSymbol is a StructureType, in which case we
# go through its procedure components.
elif isinstance(sym.datatype, StructureType):
arporter marked this conversation as resolved.
Show resolved Hide resolved
for procedure_name, procedure_component \
in sym.datatype.procedure_components.items():
# Either the procedure component is of
# UnsupportedFortranType, in which case we replace in its
# whole declaration.
if isinstance(procedure_component.datatype,
UnsupportedFortranType):
new_declaration = \
procedure_component.datatype.declaration.replace(
orig_kern_name, new_kern_name)
new_procedure_component = StructureType.ComponentType(
procedure_component.name,
UnsupportedFortranType(new_declaration,
procedure_component.
datatype.partial_datatype),
procedure_component.visibility,
procedure_component.initial_value)
sym.datatype.procedure_components[procedure_name] = \
new_procedure_component
# Or the procedure component has an initial value that is
# a Reference to the original kernel name, in which case
# we replace it with a Reference to the new kernel name.
elif (procedure_component.initial_value is not None
and (procedure_component.initial_value.name.lower()
== orig_kern_name.lower())):
new_kernel_symbol = RoutineSymbol(new_kern_name)
arporter marked this conversation as resolved.
Show resolved Hide resolved
new_procedure_component = \
StructureType.ComponentType(procedure_component
.name,
procedure_component
.datatype,
procedure_component
.visibility,
Reference(
new_kernel_symbol))
sym.datatype.procedure_components[procedure_name] = \
new_procedure_component

@property
def modified(self):
Expand Down
7 changes: 4 additions & 3 deletions src/psyclone/psyad/domain/lfric/lfric_adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
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 ContainerSymbol, UnsupportedFortranType
from psyclone.psyir.symbols import (ContainerSymbol, StructureType)
arporter marked this conversation as resolved.
Show resolved Hide resolved
from psyclone.psyir.symbols.symbol import ArgumentInterface, ImportInterface


Expand Down Expand Up @@ -80,8 +80,9 @@ def generate_lfric_adjoint(tl_psyir, active_variables):
# linear kernel.
tl_container = find_container(tl_psyir)
for sym in tl_container.symbol_table.datatypesymbols:
if (isinstance(sym.datatype, UnsupportedFortranType) and
"extends(kernel_type)" in sym.datatype.declaration.lower()):
if (isinstance(sym.datatype, StructureType) and
sym.datatype.extends is not None and
sym.datatype.extends.name.lower() == "kernel_type"):
tl_metadata_name = sym.name
break
else:
Expand Down
79 changes: 72 additions & 7 deletions src/psyclone/psyir/backend/fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,15 @@ def gen_datatype(datatype, name):
'''
if isinstance(datatype, DataTypeSymbol):
# Symbol is of derived type
if datatype.is_class:
return f"class({datatype.name})"
return f"type({datatype.name})"

if (isinstance(datatype, ArrayType) and
isinstance(datatype.intrinsic, DataTypeSymbol)):
# Symbol is an array of derived types
if datatype.intrinsic.is_class:
return f"class({datatype.intrinsic.name})"
return f"type({datatype.intrinsic.name})"

try:
Expand Down Expand Up @@ -488,6 +492,54 @@ def gen_use(self, symbol, symbol_table):

return f"{self._nindent}use{intrinsic_str}{symbol.name}\n"

def gen_proceduredecl(self, symbol, include_visibility=True):
'''Create and return the Fortran procedure declaration for this Symbol.

:param symbol: the symbol instance.
:type symbol: Union[:py:class:`psyclone.psyir.symbols.DataSymbol`,
:py:class:`psyclone.psyir.symbols.datatypes.
StructureType.ComponentType`]
:param bool include_visibility: whether to include the visibility of
the symbol in the generated declaration (default True).

:returns: the Fortran procedure declaration as a string.
:rtype: str

:raises VisitorError: if the symbol is not a RoutineSymbol or a
StructureType.ComponentType.
:raises InternalError: if the visibility is to be included but is
neither PUBLIC nor PRIVATE.
'''
if not isinstance(symbol, (DataSymbol, StructureType.ComponentType)):
raise VisitorError(
f"gen_proceduredecl() expects a 'DataSymbol' or "
f"'StructureType.ComponentType' as its first "
f"argument but got '{type(symbol).__name__}'")

if isinstance(symbol.datatype, UnsupportedFortranType):
arporter marked this conversation as resolved.
Show resolved Hide resolved
return f"{self._nindent}{symbol.datatype.declaration}\n"

result = f"{self._nindent}procedure"

if include_visibility:
if symbol.visibility == Symbol.Visibility.PRIVATE:
result += ", private"
elif symbol.visibility == Symbol.Visibility.PUBLIC:
result += ", public"
else:
raise InternalError(
f"A Symbol must be either public or private but symbol "
f"'{symbol.name}' has visibility '{symbol.visibility}'")

# Specify name
result += f" :: {symbol.name}"

# Specify initialisation expression
if symbol.initial_value:
result += " => " + self._visit(symbol.initial_value)

return result + "\n"

def gen_vardecl(self, symbol, include_visibility=False):
'''Create and return the Fortran variable declaration for this Symbol
or derived-type member.
Expand Down Expand Up @@ -696,8 +748,17 @@ def gen_typedecl(self, symbol, include_visibility=True):
f"Fortran backend cannot generate code for symbol "
f"'{symbol.name}' of type '{type(symbol.datatype).__name__}'")

if not isinstance(symbol.datatype, StructureType):
raise VisitorError(
f"gen_typedecl expects a DataTypeSymbol with a StructureType "
f"as its datatype but got: '{type(symbol.datatype).__name__}'")

result = f"{self._nindent}type"

if symbol.datatype.extends:
# This is a component of a derived type
result += f", extends({symbol.datatype.extends.name})"

if include_visibility:
if symbol.visibility == Symbol.Visibility.PRIVATE:
result += ", private"
Expand All @@ -710,12 +771,6 @@ def gen_typedecl(self, symbol, include_visibility=True):
f"type '{type(symbol.visibility).__name__}'")
result += f" :: {symbol.name}\n"

if isinstance(symbol.datatype, UnresolvedType):
raise VisitorError(
f"Local Symbol '{symbol.name}' is of UnresolvedType and "
f"therefore no declaration can be created for it. Should it "
f"have an ImportInterface?")

self._depth += 1

for member in symbol.datatype.components.values():
Expand All @@ -724,6 +779,14 @@ def gen_typedecl(self, symbol, include_visibility=True):
# part of a module.
result += self.gen_vardecl(member,
include_visibility=include_visibility)
if len(symbol.datatype.procedure_components) > 0:
result += f"{self._nindent}contains\n"
self._depth += 1
for procedure in symbol.datatype.procedure_components.values():
result += self.gen_proceduredecl(procedure,
include_visibility)
self._depth -= 1

self._depth -= 1

result += f"{self._nindent}end type {symbol.name}\n"
Expand Down Expand Up @@ -947,7 +1010,9 @@ def gen_decls(self, symbol_table, is_module_scope=False):
unresolved_symbols = []
for sym in all_symbols[:]:
if isinstance(sym.interface, UnresolvedInterface):
unresolved_symbols.append(sym)
# Explicitly deal with '*' as in 'class(*) :: var'
if sym.name != "*":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should be ending up with a Symbol named "*" as there is no such symbol - it's just Fortran syntax? I guess this will become clearer as I do the rest of the review.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, but that's how the frontend deals with whatever in either class(whatever) or type(whatever). I'm not so sure how to deal with this case unless I modify the frontend even more extensively for this (awful) Fortran syntax? It's used in tests but I certainly hope no one actually uses class(*) polymorphism... at least it's not in any .f90 test file.
I've moved dealing with this '*' symbol in the backend (by removing it so that it can't be declared anywhere) to line 1003, as this feels a bit cleaner.
Let me know what you think

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this means we have to modify the frontend a bit more - conceptually a Symbol refers to a memory location. Here, the "*" is syntax meaning (I've just googled) "unlimited polymorphic type" apparently. I've a feeling this is used in LFRic and I'm sure other codes will as well. So, when we encounter a declaration like:

  class(*) :: some_arg

we know we have a Symbol named "some_arg" but we don't know the type. The simplest thing to do would be to just raise a NotImplementedError in this case so that we will get a Symbol of UnsupportedFortranType. If that's not sufficient for your particular use case, we maybe need a new type. We already have UnresolvedType - we could perhaps add an is_class property to that? What do @sergisiso and @hiker think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like using UnsupportedFortranType would be going slightly backward here, as we'd be back to string declarations for these symbols (and I'd have to edit a lot of tests again due to upper/lowercase fparser/PSyclone outputs, probably).

I'm open to whatever other option, if having this '*' symbol is too problematic.

unresolved_symbols.append(sym)
all_symbols.remove(sym)
try:
internal_interface_symbol = symbol_table.lookup(
Expand Down
Loading
Loading