Repository navigation
(Closes #2202) Update the generation of Fortran declarations to observe dependencies #3344
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
base: master
Are you sure you want to change the base?
Changes from all commits
c13926c
9097665
0985ae4
8f68d2f
453cd42
72f247e
06a2702
3ad77ac
669c1d4
1f815d4
4bca1f3
e1d2f35
1bd9004
7c53c0a
eb3dfdc
075cdbe
084a8bd
831a1d8
bf3336a
adca257
067999a
649054c
1aca6de
561119f
36e9a37
603070a
3b71578
a78ac86
83e443a
34a1313
437b89b
415271e
9c74394
7826257
80bc457
5eefa39
c19f445
e12a64b
3862ea4
eae56ef
36ae0f7
a9424f3
babe93c
5c13124
29c3405
8506716
f0f502f
77dcca8
d75b049
faa9d40
58598ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -218,8 +218,8 @@ def __init__(self, **kwargs): | |
| Fparser2Reader.binary_operators) | ||
|
|
||
| # Create and store a CallTreeUtils instance for use when ordering | ||
| # parameter declarations. Have to import it here as CallTreeUtils | ||
| # also uses this Fortran backend. | ||
| # declarations. Have to import it here as CallTreeUtils also uses | ||
| # this Fortran backend. | ||
| # pylint: disable=import-outside-toplevel | ||
| from psyclone.psyir.tools.call_tree_utils import CallTreeUtils | ||
| self._call_tree_utils = CallTreeUtils() | ||
|
|
@@ -712,7 +712,7 @@ def _gen_common_block_decls(self, symbols: list[Symbol]) -> str: | |
|
|
||
| :param symbols: symbols that may belong to common blocks. | ||
|
|
||
| :returns: the common-block declarations. | ||
| :returns: the common-block declarations, ordered by name. | ||
|
|
||
| :raises VisitorError: if two Symbols in the same common block have | ||
| the same position. | ||
|
|
@@ -726,9 +726,12 @@ def _gen_common_block_decls(self, symbols: list[Symbol]) -> str: | |
| name = symbol.interface.name.lower() | ||
| common_blocks.setdefault(name, []).append(symbol) | ||
|
|
||
| # Order the symbols by their commonblock interface position | ||
| declarations = "" | ||
| for name, members in common_blocks.items(): | ||
| # Order the common-block declarations alphabetically by name. | ||
| for name in sorted(common_blocks.keys()): | ||
| members = common_blocks[name] | ||
| # Order the symbols within the commonblock by their commonblock | ||
| # interface position | ||
| positions = [symbol.interface.position for symbol in members] | ||
| if len(positions) != len(set(positions)): | ||
| raise VisitorError( | ||
|
|
@@ -947,78 +950,17 @@ def gen_access_stmts(self, symbol_table: SymbolTable) -> str: | |
| return result | ||
| return "" | ||
|
|
||
| # pylint: disable=too-many-branches | ||
| def _gen_parameter_decls(self, symbol_table, is_module_scope=False): | ||
| ''' Create the declarations of all parameters present in the supplied | ||
| symbol table. Declarations are ordered so as to satisfy any inter- | ||
| dependencies between them. | ||
|
|
||
| :param symbol_table: the SymbolTable instance. | ||
| :type symbol: :py:class:`psyclone.psyir.symbols.SymbolTable` | ||
| :param bool is_module_scope: whether or not the declarations are in | ||
| a module scoping unit. Default is False. | ||
|
|
||
| :returns: Fortran code declaring all parameters. | ||
| :rtype: str | ||
|
|
||
| :raises VisitorError: if there is no way of resolving | ||
| interdependencies between parameter declarations. | ||
|
|
||
| ''' | ||
| declarations = "" | ||
| local_constants = [] | ||
|
|
||
| # First add the local constants | ||
| for sym in symbol_table.datasymbols: | ||
| if sym.is_import or sym.is_unresolved: | ||
| continue # Skip, these don't need declarations | ||
| if sym.is_constant: | ||
| local_constants.append(sym) | ||
|
|
||
| # There may be dependencies between these constants so setup a dict | ||
| # holding a set of all their dependencies. The checks have to be done | ||
| # with case-insensitive name comparisons because the dependent symbols | ||
| # are not always created in the same scope. | ||
| local_lowered_names = [sym.name.lower() for sym in local_constants] | ||
| decln_inputs = {} | ||
| for symbol in local_constants: | ||
| dependencies = symbol.get_all_accessed_symbols() | ||
| dependencies = {sym for sym in dependencies | ||
| # Discard self-dependencies: e.g. "a :: HUGE(a)" | ||
| if sym.name.lower() != symbol.name.lower() and | ||
| # Discard dependencies that are not local | ||
| sym.name.lower() in local_lowered_names} | ||
| decln_inputs[symbol] = dependencies | ||
|
|
||
| # We now iterate over the declarations, declaring those that have their | ||
| # inputs satisfied. Creating a declaration for a given symbol removes | ||
| # that symbol as a dependence from any outstanding declarations and | ||
| # adds it to the 'declared' set. | ||
| declared: set[Symbol] = set() | ||
| while local_constants: | ||
| for symbol in local_constants[:]: | ||
| inputs = decln_inputs[symbol] | ||
| if inputs.issubset(declared): | ||
| # All inputs are satisfied so this declaration can be added | ||
| declared.add(symbol) | ||
| local_constants.remove(symbol) | ||
| declarations += self.gen_vardecl( | ||
| symbol, include_visibility=is_module_scope) | ||
| break | ||
| else: | ||
| # We looped through all of the variables remaining to be | ||
| # declared and none had their dependencies satisfied. | ||
| raise VisitorError( | ||
| f"Unable to satisfy dependencies for the declarations of " | ||
| f"{[sym.name for sym in local_constants]}") | ||
| return declarations | ||
|
|
||
| def gen_decls(self, | ||
| symbol_table: SymbolTable, | ||
| is_module_scope: bool = False) -> str: | ||
| '''Create and return the Fortran declarations for the supplied | ||
| SymbolTable. | ||
|
|
||
| Declarations are ordered such that any given symbol is declared after | ||
| those upon which it depends. Stricly speaking, the Fortran standard | ||
| does not mandate this in the majority of cases but compiler | ||
| implementations do not always follow the standard. | ||
|
|
||
| :param symbol_table: the SymbolTable instance. | ||
| :param is_module_scope: whether or not the declarations are in | ||
| a module scoping unit. Default is False. | ||
|
|
@@ -1038,6 +980,8 @@ def gen_decls(self, | |
| RoutineSymbols) in the supplied table that do not have an | ||
| explicit declaration (UnresolvedInterface) and there are no | ||
| wildcard imports or unknown interfaces. | ||
| :raises VisitorError: if there is no way of resolving interdependencies | ||
| between symbol declarations. | ||
|
|
||
| ''' | ||
| # pylint: disable=too-many-branches | ||
|
|
@@ -1069,20 +1013,22 @@ def gen_decls(self, | |
|
|
||
| # If the symbol table contains any symbols with an | ||
| # UnresolvedInterface interface (they are not explicitly | ||
| # declared), we need to check that we have at least one | ||
| # wildcard import which could be bringing them into this | ||
| # scope, or an unknown interface which could be declaring | ||
| # them. | ||
| # declared), we need to check that we have at least one wildcard | ||
| # import which could be bringing them into this scope, or an | ||
| # unknown interface which could be declaring them. | ||
| unresolved_symbols = [] | ||
| for sym in all_symbols[:]: | ||
| if isinstance(sym.interface, UnresolvedInterface): | ||
| unresolved_symbols.append(sym) | ||
| all_symbols.remove(sym) | ||
| try: | ||
| internal_interface_symbol = symbol_table.lookup( | ||
| "_psyclone_internal_interface") | ||
| except KeyError: | ||
| internal_interface_symbol = None | ||
|
|
||
| internal_interface_symbol = symbol_table.lookup( | ||
| "_psyclone_internal_interface", otherwise=None) | ||
| if internal_interface_symbol and (internal_interface_symbol in | ||
| all_symbols): | ||
| # We will declare an abstract interface last. | ||
| all_symbols.remove(internal_interface_symbol) | ||
|
|
||
| if unresolved_symbols and not ( | ||
| symbol_table.wildcard_imports() or | ||
| internal_interface_symbol or | ||
|
|
@@ -1102,64 +1048,91 @@ def gen_decls(self, | |
| raise VisitorError( | ||
| f"Found a symbol '{sym.name}' with a name greater than " | ||
| f"{self.MAX_VARIABLE_NAME_LENGTH} characters in length. " | ||
| "This is not standards-compliant Fortran.") | ||
|
|
||
| # As a convention, we will declare the variables in the following | ||
| # order: | ||
| f"This is not standards-compliant Fortran.") | ||
|
|
||
| # 1: Routine declarations and interfaces. (Note that accessibility | ||
| # statements are generated in gen_access_stmts().) | ||
| for sym in all_symbols[:]: | ||
| 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 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, | ||
| is_module_scope) | ||
| for sym in all_symbols[:]: | ||
| if isinstance(sym, DataSymbol) and sym.is_constant: | ||
| all_symbols.remove(sym) | ||
| # There may be dependencies between the symbols so setup a dict | ||
| # holding a set of all their dependencies. The checks have to be done | ||
| # with case-insensitive name comparisons because the dependent symbols | ||
| # are not always created in the same scope. | ||
| local_lowered_names = [sym.name.lower() for sym in all_symbols] | ||
| decln_inputs: dict[str, set[Symbol]] = {} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't usually add type hints for variable declarations- this one has made it through. I seem to remember there was some discussion ongoing on this so I'm nmot sure if we made a decision?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's also some that have appeared in tests (so I don't forget) |
||
| for symbol in all_symbols: | ||
| dependencies = symbol.get_all_accessed_symbols() | ||
| dependencies = {sym for sym in dependencies | ||
| # Discard self-dependencies: e.g. "a :: HUGE(a)" | ||
| if sym.name.lower() != symbol.name.lower() and | ||
| # Discard dependencies that are not local | ||
| sym.name.lower() in local_lowered_names and | ||
| # Discard dependencies on RoutineSymbols (but | ||
| # *not* interfaces) | ||
| not (isinstance(sym, RoutineSymbol) and | ||
| sym is not internal_interface_symbol and | ||
| not isinstance(sym, GenericInterfaceSymbol))} | ||
| decln_inputs[symbol] = dependencies | ||
|
|
||
| # 3: Argument variable declarations | ||
| # Sanity check that we haven't got arguments if we're in a module scope | ||
| if symbol_table.argument_datasymbols and is_module_scope: | ||
| raise VisitorError( | ||
| f"Arguments are not allowed in this context but this symbol " | ||
| f"table contains argument(s): " | ||
| f"'{[sym.name for sym in symbol_table.argument_datasymbols]}'." | ||
| ) | ||
| # We use symbol_table.argument_datasymbols because it has the | ||
| # symbol order that we need | ||
| for symbol in symbol_table.argument_datasymbols: | ||
| declarations += self.gen_vardecl( | ||
| symbol, include_visibility=is_module_scope) | ||
| all_symbols.remove(symbol) | ||
|
|
||
| # 4: Derived-type declarations. These must come before any declarations | ||
| # of symbols of these types. | ||
| for symbol in all_symbols[:]: | ||
| if isinstance(symbol, DataTypeSymbol): | ||
| declarations += self.gen_typedecl( | ||
| symbol, include_visibility=is_module_scope) | ||
| all_symbols.remove(symbol) | ||
|
|
||
| # 5: The rest of the symbols | ||
| for symbol in all_symbols: | ||
| # We now iterate over the declarations, declaring those that have their | ||
| # inputs satisfied. Creating a declaration for a given symbol removes | ||
| # that symbol as a dependence from any outstanding declarations and | ||
| # adds it to the 'declared' set. | ||
| declared: set[Symbol] = set() | ||
|
|
||
| while all_symbols: | ||
|
|
||
| # Find those Symbols which have their dependencies satisfied. | ||
| do_next: list[Symbol] = [sym for sym in all_symbols if | ||
| decln_inputs[sym].issubset(declared)] | ||
| if not do_next: | ||
| # We looped through all of the variables remaining to be | ||
| # declared and none had their dependencies satisfied. | ||
| raise VisitorError( | ||
| f"Unable to satisfy dependencies for the declarations of " | ||
| f"{[sym.name for sym in all_symbols]}") | ||
|
|
||
| # Output the declarations of those Symbols that have their | ||
| # dependencies satisfied. | ||
| new_declns = [] | ||
| for symbol in do_next: | ||
| declared.add(symbol) | ||
| all_symbols.remove(symbol) | ||
| if isinstance(symbol, RoutineSymbol): | ||
| # Interfaces can be GenericInterfaceSymbols or | ||
| # RoutineSymbols of UnsupportedFortranType. | ||
| if isinstance(symbol, GenericInterfaceSymbol): | ||
| new_declns.append(self.gen_interfacedecl(symbol)) | ||
| elif isinstance(symbol.datatype, UnsupportedType): | ||
| new_declns.append(self.gen_vardecl( | ||
| symbol, include_visibility=is_module_scope)) | ||
| elif not (symbol.is_modulevar or symbol.is_automatic): | ||
| raise VisitorError( | ||
| f"Routine symbol '{symbol.name}' has " | ||
| f"'{symbol.interface}'. This is not supported " | ||
| f"by the Fortran back-end.") | ||
| elif isinstance(symbol, DataTypeSymbol): | ||
| new_declns.append(self.gen_typedecl( | ||
| symbol, include_visibility=is_module_scope)) | ||
| else: | ||
| new_declns.append(self.gen_vardecl( | ||
| symbol, include_visibility=is_module_scope)) | ||
| # Sort each section of declarations for readability. | ||
| declarations += "".join(sorted(new_declns)) | ||
| # Now that we've created new declaration(s) (and thus | ||
| # potentially resolved some dependencies) we go back to | ||
| # the start of the list of remaining symbols. | ||
|
|
||
| if internal_interface_symbol: | ||
| declarations += self.gen_vardecl( | ||
| symbol, include_visibility=is_module_scope) | ||
| internal_interface_symbol, | ||
| include_visibility=is_module_scope) | ||
|
|
||
| declarations += self._gen_common_block_decls(all_symbols) | ||
| declarations += self._gen_common_block_decls(list(declared)) | ||
|
|
||
| return declarations | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a local change for your linter?