-
Notifications
You must be signed in to change notification settings - Fork 30
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
(Towards #2271, closes #2278, closes #2849) Generalise reference_accesses() and use to tidy KernelModuleInlineTrans. #2825
base: master
Are you sure you want to change the base?
Changes from 13 commits
4435fa8
ec4045f
76026ac
b765e55
0920bf9
8501cba
d1127e8
0b1d398
4651016
d8e639b
f031e65
300c563
1846fa6
0d4846c
26eabf5
f417bc1
5a7671e
7991f62
c55c45e
b568451
e541be5
bf242e6
278b82e
b646667
1ed438e
3bab6b7
e389463
1b583ff
8541f08
ce58e0c
c5ee43f
125db1a
ade8c70
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 |
---|---|---|
|
@@ -37,7 +37,6 @@ | |
|
||
'''This module implements the AccessType used throughout PSyclone.''' | ||
|
||
from __future__ import print_function, absolute_import | ||
from enum import Enum | ||
from psyclone.configuration import Config | ||
|
||
|
@@ -55,36 +54,40 @@ class AccessType(Enum): | |
# This is used internally to indicate unknown access type of | ||
# a variable, e.g. when a variable is passed to a subroutine | ||
# and the access type of this variable in the subroutine | ||
# is unknown | ||
# is unknown. | ||
UNKNOWN = 7 | ||
# A symbol representing a routine is called. | ||
CALL = 8 | ||
|
||
def __str__(self): | ||
'''Convert to a string representation, returning just the | ||
enum (e.g. 'WRITE').. | ||
:return: API name for this string. | ||
:rtype: str | ||
enum (e.g. 'WRITE'). | ||
''' | ||
# pylint complains without str() that the return type is not a str | ||
return str(self.name) | ||
|
||
def api_specific_name(self): | ||
def api_specific_name(self) -> str: | ||
'''This convenience function returns the name of the type in the | ||
current API. E.g. in a lfric API, WRITE --> "gh_write" | ||
current API. E.g. in the lfric API, WRITE --> "gh_write". If no | ||
mapping is available then the generic name is returned. | ||
|
||
:returns: The API specific name. | ||
:rtype: str | ||
''' | ||
api_config = Config.get().api_conf() | ||
rev_access_mapping = api_config.get_reverse_access_mapping() | ||
return rev_access_mapping[self] | ||
return rev_access_mapping.get(self, str(self).lower()) | ||
|
||
@staticmethod | ||
def from_string(access_string): | ||
def from_string(access_string: str): | ||
'''Convert a string (e.g. "read") into the corresponding | ||
AccessType enum value (AccessType.READ). | ||
|
||
:param str access_string: Access type as string. | ||
:param access_string: Access type as a string. | ||
|
||
:returns: Corresponding AccessType enum. | ||
:Raises: ValueError if access_string is not a valid access type. | ||
:rtype: :py:class:`psyclone.core.access_type.AccessType` | ||
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. Maybe just 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. This needs the SELF syntax you mentioned because we're inside the AccessType class here. |
||
|
||
:raises: ValueError if access_string is not a valid access type. | ||
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. The second semicolon is in the wrong place 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. Oops. |
||
''' | ||
for access in AccessType: | ||
if access.name == access_string.upper(): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,13 +43,13 @@ | |
|
||
''' | ||
|
||
|
||
from psyclone.core import VariablesAccessInfo | ||
from psyclone.errors import InternalError | ||
from psyclone.psyGen import Transformation, CodedKern | ||
from psyclone.psyir.transformations import TransformationError | ||
from psyclone.psyir.symbols import ( | ||
ContainerSymbol, DataSymbol, DataTypeSymbol, DefaultModuleInterface, | ||
IntrinsicSymbol, RoutineSymbol, Symbol) | ||
RoutineSymbol, Symbol) | ||
from psyclone.psyir.nodes import ( | ||
Container, Reference, Routine, ScopingNode, | ||
Literal, CodeBlock, Call, IntrinsicCall) | ||
|
@@ -156,41 +156,20 @@ def validate(self, node, options=None): | |
# We do not support kernels that use symbols representing data | ||
# declared in their own parent module (we would need to new imports | ||
# from this module for those, and we don't do this yet). | ||
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. It was already here but I don't understand the parenthesis, did you mean "we would need to import them from this module, and we don't do this yet" 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. Yes, it wasn't quite right. I've updated it so that it hopefully makes sense. |
||
# These can only be found in References and CodeBlocks. | ||
for var in kernel_schedule.walk(Reference): | ||
symbol = var.symbol | ||
if isinstance(symbol, IntrinsicSymbol): | ||
continue | ||
if not symbol.is_import: | ||
if not var.scope.symbol_table.lookup( | ||
symbol.name, scope_limit=kernel_schedule, | ||
otherwise=False): | ||
raise TransformationError( | ||
f"{kern_or_call} '{kname}' contains accesses to " | ||
f"'{symbol.name}' which is declared in the same " | ||
f"module scope. Cannot inline such a {kern_or_call}.") | ||
for block in kernel_schedule.walk(CodeBlock): | ||
for name in block.get_symbol_names(): | ||
# Is this quantity declared within the kernel? | ||
sym = block.scope.symbol_table.lookup( | ||
name, scope_limit=kernel_schedule, otherwise=None) | ||
if not sym: | ||
# It isn't declared in the kernel. | ||
# Can we find the corresponding symbol at all? | ||
sym = block.scope.symbol_table.lookup(name, otherwise=None) | ||
if not sym: | ||
raise TransformationError( | ||
f"{kern_or_call} '{kname}' contains accesses to " | ||
f"'{name}' in a CodeBlock but the origin of this " | ||
f"symbol is unknown.") | ||
# We found it in an outer scope - is it from an import or a | ||
# declaration? | ||
if not sym.is_import: | ||
raise TransformationError( | ||
f"{kern_or_call} '{kname}' contains accesses to " | ||
f"'{name}' in a CodeBlock that is declared in the " | ||
f"same module scope. Cannot inline such a " | ||
f"{kern_or_call}.") | ||
vai = VariablesAccessInfo(kernel_schedule) | ||
table = kernel_schedule.symbol_table | ||
for sig in vai.all_signatures: | ||
symbol = table.lookup(sig.var_name, otherwise=None) | ||
Comment on lines
+163
to
+166
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. I like this new version, but note that this comes with the signatures limitations e.g. don't work with nested scopes:
and will fails with the TransformationError below.
it will fails with the "is declared in the same module scope". Both cases are wrong and the subroutine could be inlined. Granted that this won't happen often because it requires inlining subroutines that have previously been transformed with psyclone so that a symbol was declared in a inner scope. But maybe we should document - add an xfail to #2424 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. I couldn't get the first case to fail but I've added an x-failing test for the second one and added a TODO and text to a comment in the code too. |
||
if not symbol: | ||
raise TransformationError( | ||
f"{kern_or_call} '{kname}' contains accesses to " | ||
f"'{sig.var_name}' but the origin of this symbol is " | ||
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. s/symbol/signature/ ? 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. Yep. |
||
f"unknown.") | ||
if not symbol.is_import and symbol.name not in table: | ||
raise TransformationError( | ||
f"{kern_or_call} '{kname}' contains accesses to " | ||
f"'{symbol.name}' which is declared in the same " | ||
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. s/same/callee/ ? 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. Yes, that's better, thanks. |
||
f"module scope. Cannot inline such a {kern_or_call}.") | ||
|
||
# We can't transform subroutines that shadow top-level symbol module | ||
# names, because we won't be able to bring this into the subroutine | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -574,6 +574,11 @@ def _sym_is_field(sym): | |
read_var = f"{psy_data.name}%ReadVariable" | ||
mod_man = ModuleManager.get() | ||
|
||
# Construct a set of the names of all LFRic kind parameters. | ||
const = LFRicConstants() | ||
all_kind_names = set( | ||
[val["kind"] for val in const.DATA_TYPE_MAP.values()]) | ||
|
||
# First handle variables that are read: | ||
# ------------------------------------- | ||
for module_name, signature in read_write_info.read_list: | ||
|
@@ -583,6 +588,11 @@ def _sym_is_field(sym): | |
# variables have References, and will already have been declared | ||
# in the symbol table (in _add_all_kernel_symbols). | ||
sig_str = self._flatten_signature(signature) | ||
|
||
# Work-around for the fact that we want to exclude kind parameters. | ||
if sig_str in all_kind_names: | ||
continue | ||
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. Maybe this is a symptom that this (and the DataTypeSymbol check added below) are misclassified as READ (and therefore the driver was to make a copy) but they should be TYPE_INFO (and therefore the driver shouldn't consider them for the value copy)? 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. Yes, this has gone now. |
||
|
||
if module_name: | ||
mod_info = mod_man.get_module_info(module_name) | ||
orig_sym = mod_info.get_symbol(signature[0]) | ||
|
@@ -593,6 +603,10 @@ def _sym_is_field(sym): | |
else: | ||
orig_sym = original_symbol_table.lookup(signature[0]) | ||
|
||
if orig_sym and isinstance(orig_sym, DataTypeSymbol): | ||
# We don't want symbols representing data types. | ||
continue | ||
|
||
if orig_sym and orig_sym.is_array and _sym_is_field(orig_sym): | ||
# This is a field vector, so add all individual fields | ||
upper = int(orig_sym.datatype.shape[0].upper.value) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -315,10 +315,14 @@ def reference_accesses(self, var_accesses): | |
# We conservatively default to READWRITE otherwise (TODO #446). | ||
default_access = AccessType.READWRITE | ||
|
||
# TODO #2271: This may skip references in inner expressions of | ||
# structure calls, but to implement properly we new a new kind of | ||
# AccessType that represents being called (USED but not READ, maybe | ||
# the same that we need for INQUIRY type attributes?) | ||
# The RoutineSymbol has a CALL access. | ||
sig, indices_list = self.routine.get_signature_and_indices() | ||
var_accesses.add_access(sig, AccessType.CALL, self.routine) | ||
# Any symbols referenced in any index expressions are READ. | ||
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. Maybe As they are not all reads, e.g. there may be another CALL, even with and output argument which would be WRITE 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. Same in a similar comment below which was already here 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. Done and done. |
||
for indices in indices_list: | ||
for idx in indices: | ||
idx.reference_accesses(var_accesses) | ||
|
||
for arg in self.arguments: | ||
if isinstance(arg, Reference): | ||
# This argument is pass-by-reference. | ||
|
@@ -373,12 +377,13 @@ def is_elemental(self): | |
@property | ||
def is_pure(self): | ||
''' | ||
:returns: whether the routine being called is pure (guaranteed to \ | ||
return the same result when provided with the same argument \ | ||
:returns: whether the routine being called is pure (guaranteed to | ||
return the same result when provided with the same argument | ||
values). If this information is not known then it returns None. | ||
:rtype: NoneType | bool | ||
''' | ||
if self.routine and self.routine.symbol: | ||
if (self.routine and self.routine.symbol and | ||
isinstance(self.routine.symbol, RoutineSymbol)): | ||
return self.routine.symbol.is_pure | ||
return None | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,7 @@ | |
from enum import Enum | ||
from fparser.two import Fortran2003 | ||
from fparser.two.utils import walk | ||
from psyclone.core import AccessType, Signature, VariablesAccessInfo | ||
from psyclone.psyir.nodes.statement import Statement | ||
from psyclone.psyir.nodes.datanode import DataNode | ||
|
||
|
@@ -174,8 +175,28 @@ def get_symbol_names(self): | |
Fortran2003.End_If_Stmt)): | ||
# We don't want labels associated with loop or branch control. | ||
result.append(node.string) | ||
|
||
# Precision on literals requires special attention since they are just | ||
# stored in the tree as str (fparser/#456). | ||
for node in walk(parse_tree, (Fortran2003.Int_Literal_Constant, | ||
Fortran2003.Real_Literal_Constant)): | ||
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. Should we also do bools and chars ? 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. Oh yes, my mistake. For bonus points I've done complex too :-) The only other type is 'boz' and they don't have a kind parameter. |
||
if node.items[1]: | ||
result.append(node.items[1]) | ||
return result | ||
|
||
def reference_accesses(self, var_accesses: VariablesAccessInfo): | ||
''' | ||
Get all variable access information. Since this is a CodeBlock we | ||
only know the names of symbols accessed within it but not how they | ||
are accessed. Therefore we err on the side of caution and mark | ||
them all as READWRITE. | ||
|
||
:param var_accesses: VariablesAccessInfo instance that stores the | ||
information about variable accesses. | ||
|
||
''' | ||
for name in self.get_symbol_names(): | ||
var_accesses.add_access(Signature(name), AccessType.READWRITE, | ||
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. I see that we use READWRITE as worst case scenario, but shouldn't we use UNKNOWN here? It fits better for Codeblocks but it may require changes in the VAI helper methods which I am not sure they consider UNKNOWN, so maybe is for another PR if it needs more thought. 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. I agree it would fit better but, as you suspect, the VAI and associated helper methods never mention UNKNOWN so we'd need to take care there. |
||
self) | ||
|
||
def __str__(self): | ||
return f"CodeBlock[{len(self._fp2_nodes)} nodes]" |
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.
I know in the past we didn't do it for
__str__
but maybe now we can use the-> str
typehint for the sake of helping IDEs.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.
Sure, done.