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 #2125) Fix bugs in SymbolTable deep copy and InlineTrans when array bounds reference formal arg #2633

Merged
merged 62 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
6428962
#2125 permit use of force to allow CodeBlocks
arporter Jun 17, 2024
ecf9d35
#2125 update InlineTrans to check for formal args in any array formal…
arporter Jun 17, 2024
fab567d
#2125 fix bug in SymbolTable deep copy and begin testing
arporter Jun 18, 2024
53f67ff
#2125 allow for case where dimensioning symbol is not in table being …
arporter Jun 20, 2024
6764145
#2125 add check for symbols in initial values
arporter Jun 20, 2024
856ad73
#2125 add test showing problem in copy of array shapes [skip ci]
arporter Jun 21, 2024
8b80941
Merge branch 'master' into 2125_inline_fixes
arporter Jun 24, 2024
15c6c8b
#2125 begin adding copy() to ArrayType [skip ci]
arporter Jun 24, 2024
2f8c12b
Merge branch 'master' into 2125_inline_fixes
arporter Jun 25, 2024
a8686d5
#2125 add copy() methods to ArrayType and ArrayType.Extent
arporter Jun 25, 2024
4914e75
#2125 fix coverage of SymbolTable
arporter Jun 26, 2024
041286f
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jun 27, 2024
eefe088
#2125 tidying and get full coverage of datatypes.py
arporter Jun 28, 2024
c76f384
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jun 28, 2024
5586630
#2125 improve comments and tidy
arporter Jun 28, 2024
734c04b
#2115 fix error in deep-copy routine
arporter Jun 28, 2024
5cb372f
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jul 2, 2024
dd79b0e
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jul 2, 2024
b122dcc
#2125 override UnsupportedFortranType.copy() and test
arporter Jul 2, 2024
1a3b81e
#2125 add precision and intrinsic setters
arporter Jul 3, 2024
fbd9ed6
#2125 WIP extending deep-copy functionality and tests [skip ci]
arporter Jul 3, 2024
a4cf7ab
#2125 add setter for partial_datatype
arporter Jul 3, 2024
2a9b524
#2125 add deep-copy support for symbols of StructureType
arporter Jul 3, 2024
3005118
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jul 3, 2024
511a700
#2125 improve comments and DataSymbol testing
arporter Jul 3, 2024
23567ff
#2125 add more complex expression to test
arporter Jul 3, 2024
dda78cc
#2125 update copy() and tests for subclasses of TypedSymbol
arporter Jul 3, 2024
510a67a
#2125 mv functionality out of SymbolTable and into relink methods
arporter Jul 3, 2024
6f0290d
#2125 fix linting
arporter Jul 3, 2024
29e008b
#2125 improve testing [skip ci]
arporter Jul 4, 2024
584b4f3
#2125 replace Node.relink() with new Visitor
arporter Jul 4, 2024
8dcbab4
#2125 fix lint error
arporter Jul 4, 2024
3a5b31e
#2125 switch from visitor to Node.update_symbols_from() method
arporter Jul 4, 2024
817ea50
#2125 fix linting (again)
arporter Jul 4, 2024
0f73b67
#2125 get full coverage of datatypes
arporter Jul 5, 2024
5775a16
#2125 add tests for Literal and Reference and fix bug
arporter Jul 5, 2024
ed454f0
#2125 fix coverage of GenericInterfaceSymbol and Symbol
arporter Jul 5, 2024
e7aab1e
#2125 rm unnecessary tests
arporter Jul 5, 2024
bff1a18
#2125 improve comments and tests
arporter Jul 5, 2024
fa15577
#2125 test routinesymbol copy with interface
arporter Jul 5, 2024
24b1181
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jul 12, 2024
2d379c0
#2125 update dev guide [skip ci]
arporter Jul 12, 2024
018aee4
#2125 consistency improvements
arporter Jul 12, 2024
23c57d7
#2125 add TODOs to LFRicTypes
arporter Jul 12, 2024
2a08535
#2125 make docstrings consistent
arporter Jul 15, 2024
a651400
#2125 rename the method
arporter Jul 15, 2024
3c492ac
#2125 update ScopingNode.refine_copy to use new method and extend Loo…
arporter Jul 15, 2024
5a23841
#2125 fix linting error
arporter Jul 15, 2024
149cc56
#2125 add test for new Loop method
arporter Jul 15, 2024
2bd3b90
#2125 make calls to replace_symbols_using() non-recursive if within _…
arporter Jul 15, 2024
adbbb5b
#2125 add replace_symbols_using to ScopingNode
arporter Jul 16, 2024
9ade9a7
#2125 get full coverage of ScopingNode from associated test file
arporter Jul 16, 2024
0a0351c
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jul 16, 2024
03308a4
#2125 update dev guide with renamed implementation
arporter Jul 17, 2024
2216835
Merge branch 'master' into 2125_tuple_inline_fixes
arporter Jul 19, 2024
0f0a863
#2125 update docstrings and dev-guide with details on symbol-shadowin…
arporter Jul 19, 2024
b123609
#2125 use automethod to avoid duplicating warning from docstring
arporter Jul 19, 2024
5e83afd
#2125 updates for review
arporter Jul 22, 2024
ceb2f75
#2125 ensure Reference.replace_symbols_using does *not* call Symbol.r…
arporter Jul 22, 2024
8e10095
#2125 fix failing Reference test
arporter Jul 22, 2024
ffcd885
Merge remote-tracking branch 'origin/master' into 2125_tuple_inline_f…
sergisiso Jul 23, 2024
565c2c3
Update changelog
sergisiso Jul 23, 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
18 changes: 18 additions & 0 deletions doc/developer_guide/psyir.rst
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,24 @@ relationship.
Methods like ``node.detach()``, ``node.copy()`` and ``node.pop_all_children()``
can be used to move or replicate existing children into different nodes.

Tree Copying
============

The ability to create a deep-copy of a PSyIR tree is used heavily in PSyclone,
primarily by the PSyIR backends. (This is because those backends often need
to modify the tree while ensuring that the one provided by the caller remains
unchanged.) As mentioned in the previous section, the ``node.copy()`` method
provides this functionality:

.. automethod:: psyclone.psyir.nodes.Node.copy

As part of this copy operation, all Symbols referred to in the new tree must
also be replaced with their equivalents from the symbol tables in the new tree.
Since these symbol tables are associated with instances of ``ScopingNode``, it
is ``ScopingNode._refine_copy`` which handles this:

.. automethod:: psyclone.psyir.nodes.ScopingNode._refine_copy

.. _update_signals_label:

Dynamic Tree Updates
Expand Down
40 changes: 36 additions & 4 deletions doc/developer_guide/psyir_symbols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ explicitly listed may be assumed to be unsupported):
| |PRIVATE | |
+----------------------+--------------------+--------------------+

.. warning:: Checking for equality between Type objects is currently
only implemented for ``ScalarType``. This will be
completed in #1799.

It was decided to include datatype intrinsic as an attribute of ScalarType
rather than subclassing. So, for example, a 4-byte real scalar is
defined like this:
Expand Down Expand Up @@ -220,6 +216,42 @@ the `__contains__` method has no mechanism to pass a `scope_limit`
optional argument. This would probably require a separate `setter` and
`getter` to specify whether to check ancestors or not.

Copying Symbols and Symbol Tables
=================================

Since Symbols can contain PSyIR nodes and other Symbols (e.g. as part
of the definition of their precision or initial value), creating copies
is not entirely straightforward. Every `Symbol` has the `copy` method:

.. automethod:: psyclone.psyir.symbols.Symbol.copy

This ensures that the precision and initial-value PSyIR sub-trees are
copied appropriately while any Symbols referred to inside those nodes remain
unchanged (and therefore can still be used in the same scope).

However, when performing a *deep* copy of a PSyIR tree, all Symbols will
need to be replaced with their equivalents in the new tree. The
`SymbolTable.deep_copy()` method:

.. automethod:: psyclone.psyir.symbols.SymbolTable.deep_copy

handles this by first creating shallow copies of all Symbols in the
table and then ensuring that each is updated to refer to Symbols in
the new scope. This is achieved with the `replace_symbols_using`
method:

.. automethod:: psyclone.psyir.symbols.Symbol.replace_symbols_using

All PSyIR `Node` classes also implement this method and call it when a
`copy` operation is performed on the tree. The implementation in `Node`
walks down the PSyIR tree and updates any Symbols using those in the supplied
table. As the PSyIR supports nested scopes, each `ScopingNode` is associated
with a new symbol table. Therefore, the implementation within this class
is slightly different:

.. automethod:: psyclone.psyir.nodes.ScopingNode.replace_symbols_using


Specialising Symbols
====================

Expand Down
2 changes: 2 additions & 0 deletions src/psyclone/domain/lfric/lfric_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ def precision_for_type(self, data_type):
'''
for module_info in self.DATA_TYPE_MAP.values():
if module_info["type"] == data_type:
# TODO #2659 - this method should probably just return a name
# rather than create a symbol itself.
# pylint: disable=import-outside-toplevel
from psyclone.domain.lfric.lfric_types import LFRicTypes
return LFRicTypes(module_info["kind"].upper())
Expand Down
8 changes: 3 additions & 5 deletions src/psyclone/domain/lfric/lfric_extract_driver_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,7 @@ class LFRicExtractDriverCreator(BaseDriverCreator):
1. The corresponding :py:class:`psyclone.psyGen.Invoke` statement that
contains the kernel(s) is copied. This way we avoid affecting the tree
of the caller. We need the invoke since it contains the symbol table.
2. We remove all halo exchange nodes. For now, the extract transformation
will not work when distributed memory is enabled, but since this
restriction is expected to be lifted, the code to handle this is
already added.
2. We remove all halo exchange nodes.
3. We lower each kernel (child of the invoke) that was requested to
be extracted, all others are removed. This is required since the kernel
extraction will not contain the required data for the other kernels to
Expand Down Expand Up @@ -363,7 +360,8 @@ def _add_all_kernel_symbols(self, sched, symbol_table, proxy_name_mapping,
new_symbol = symbol_table.new_symbol(root_name=reference.name,
tag=reference.name,
symbol_type=DataSymbol,
datatype=datatype)
datatype=datatype.copy())
new_symbol.replace_symbols_using(symbol_table)
reference.symbol = new_symbol

# Now handle all derived type. The name of a derived type is
Expand Down
7 changes: 5 additions & 2 deletions src/psyclone/domain/lfric/lfric_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,14 @@ def _create_precision_from_const_module():
# Generate LFRic module symbols from definitions
for module_info in modules:
module_name = module_info.name.lower()
# Create the module (using a PSyIR ContainerSymbol)
# Create the module (using a PSyIR ContainerSymbol).
# TODO #2659 - this ContainerSymbol should be added to
# a SymbolTable!
LFRicTypes._name_to_class[module_name] = \
ContainerSymbol(module_info.name)
# Create the variables specified by the module (using
# PSyIR DataSymbols)
# PSyIR DataSymbols). TODO #2659 - these DataSymbols should
# be added to a SymbolTable!
for module_var in module_info.vars:
var_name = module_var.upper()
interface = ImportInterface(LFRicTypes(module_name))
Expand Down
4 changes: 3 additions & 1 deletion src/psyclone/psyir/backend/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,9 @@ def _visit(self, node):
# set so we ignore it and continue on down to any children.
results = []
for child in node.children:
results.append(self._visit(child))
result = self._visit(child)
if result is not None:
results.append(result)
return "".join(results)

raise VisitorError(
Expand Down
23 changes: 19 additions & 4 deletions src/psyclone/psyir/nodes/literal.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@

''' This module contains the Literal node implementation.'''

from __future__ import absolute_import

import re

from psyclone.psyir.nodes.datanode import DataNode
Expand Down Expand Up @@ -169,5 +167,22 @@ def node_str(self, colour=True):
:returns: description of this PSyIR node.
:rtype: str
'''
return f"{self.coloured_name(colour)}"\
f"[value:'{self._value}', {self.datatype}]"
return (f"{self.coloured_name(colour)}"
f"[value:'{self._value}', {self.datatype}]")

def replace_symbols_using(self, table):
'''
Replace any Symbols referred to by this object with those in the
supplied SymbolTable with matching names. If there
is no match for a given Symbol then it is left unchanged.

:param table: the symbol table in which to look up replacement symbols.
:type table: :py:class:`psyclone.psyir.symbols.SymbolTable`

'''
self.datatype.replace_symbols_using(table)
super().replace_symbols_using(table)


# For AutoAPI documentation generation
__all__ = ['Literal']
29 changes: 26 additions & 3 deletions src/psyclone/psyir/nodes/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ def __eq__(self, other):
@property
def loop_type(self):
'''
:returns: the type of this loop.
:rtype: str
:returns: the type of this loop, if set.
:rtype: Optional[str]
'''
if not self._variable:
return None
return self._loop_type_inference_rules.get(self.variable.name, None)

@classmethod
Expand Down Expand Up @@ -412,11 +414,32 @@ def variable(self, var):
self._check_variable(var)
self._variable = var

def replace_symbols_using(self, table):
'''
Replace the Symbol referred to by this object's `variable` property
with that in the supplied SymbolTable with a matching name. If there
is no match then it is left unchanged.

:param table: symbol table in which to look up the replacement symbol.
:type table: :py:class:`psyclone.psyir.symbols.SymbolTable`

'''
if self._variable:
try:
new_sym = table.lookup(self._variable.name)
self.variable = new_sym
except KeyError:
pass
super().replace_symbols_using(table)

def __str__(self):
# Give Loop sub-classes a specialised name
name = self.__class__.__name__
result = name + "["
result += f"variable:'{self.variable.name}'"
if self._variable:
result += f"variable:'{self.variable.name}'"
else:
result += "variable:None"
if self.loop_type:
result += f", loop_type:'{self.loop_type}'"
result += "]\n"
Expand Down
15 changes: 15 additions & 0 deletions src/psyclone/psyir/nodes/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,21 @@ def path_from(self, ancestor):
result_list.reverse()
return result_list

def replace_symbols_using(self, table):
'''
Replace any Symbols referred to by this object with those in the
supplied SymbolTable with matching names. If there
is no match for a given Symbol then it is left unchanged.

This base implementation simply propagates the call to any child Nodes.

:param table: the symbol table in which to look up replacement symbols.
:type table: :py:class:`psyclone.psyir.symbols.SymbolTable`

'''
for child in self.children:
child.replace_symbols_using(table)


# For automatic documentation generation
# TODO #913 the 'colored' routine shouldn't be in this module.
Expand Down
18 changes: 18 additions & 0 deletions src/psyclone/psyir/nodes/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,24 @@ def next_access(self):
return all_accesses[index+1].node
return None

def replace_symbols_using(self, table):
'''
Update any Symbols referenced by this Node with those in the
supplied table with matching names. If there is no match for a given
Symbol then it is left unchanged.

:param table: the symbol table in which to look up replacement symbols.
:type table: :py:class:`psyclone.psyir.symbols.SymbolTable`

'''
try:
self.symbol = table.lookup(self.symbol.name)
except KeyError:
pass

# Walk on down the tree.
super().replace_symbols_using(table)


# For AutoAPI documentation generation
__all__ = ['Reference']
87 changes: 63 additions & 24 deletions src/psyclone/psyir/nodes/scoping_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
''' This module contains the ScopingNode implementation.'''

from psyclone.psyir.nodes.node import Node
from psyclone.psyir.nodes.reference import Reference
from psyclone.psyir.symbols import SymbolTable
from psyclone.psyir.symbols import SymbolError, SymbolTable


class ScopingNode(Node):
Expand All @@ -54,7 +53,7 @@ class ScopingNode(Node):
:param parent: the parent node of this node in the PSyIR.
:type parent: Optional[:py:class:`psyclone.psyir.nodes.Node`]
:param symbol_table: attach the given symbol table to the new node.
:type symbol_table: \
:type symbol_table:
Optional[:py:class:`psyclone.psyir.symbols.SymbolTable`]

'''
Expand Down Expand Up @@ -90,35 +89,75 @@ def _refine_copy(self, other):
''' Refine the object attributes when a shallow copy is not the most
appropriate operation during a call to the copy() method.

This method creates a deep copy of the SymbolTable associated with
the `other` scoping node and then calls `replace_symbols_using` to
update all Symbols referenced in the tree below this node.

.. warning::

Since `replace_symbols_using` only uses symbol *names*, this won't
get the correct symbol if the PSyIR has symbols shadowed in nested
scopes, e.g.:

.. code-block::

subroutine test
integer :: a
integer :: b = 1
if condition then
! PSyIR declares a shadowed, locally-scoped a'
a' = 1
if condition2 then
! PSyIR declares a shadowed, locally-scoped b'
b' = 2
a = a' + b'

Here, the final assignment will end up being `a' = a' + b'` and
thus the semantics of the code are changed. TODO #2666.

:param other: object we are copying from.
:type other: :py:class:`psyclone.psyir.node.Node`

'''
super(ScopingNode, self)._refine_copy(other)
self._symbol_table = other.symbol_table.deep_copy()
# pylint: disable=protected-access
# pylint: disable-next=protected-access
self._symbol_table._node = self # Associate to self

# Update of children references to point to the equivalent symbols in
# the new symbol table attached to self.
# TODO #1377 Unfortunately Loop nodes currently store the associated
# loop variable in a `_variable` property rather than as a child so we
# must handle those separately. Also, in the LFRic API a Loop does not
# initially have the `_variable` property set which means that calling
# the `variable` getter causes an error (because it checks the
# internal-consistency of the Loop node). We therefore have to check
# the value of the 'private' `_variable` for now.
# We have to import Loop here to avoid a circular dependency.
# pylint: disable=import-outside-toplevel
from psyclone.psyir.nodes.loop import Loop
for node in self.walk((Reference, Loop)):
if isinstance(node, Reference):
if node.symbol in other.symbol_table.symbols:
node.symbol = self.symbol_table.lookup(node.symbol.name)
if isinstance(node, Loop) and node._variable:
if node.variable in other.symbol_table.symbols:
node.variable = self.symbol_table.lookup(
node.variable.name)
# Now we've updated the symbol table, walk back down the tree and
# update any Symbols.
# Since this will get called from every ScopingNode in the tree, there
# could be a lot of repeated walks back down the tree. If this becomes
# a performance issue we could keep track of the depth of the recursive
# call to _refine_copy and only do this call when that depth is zero.
self.replace_symbols_using(self._symbol_table)
sergisiso marked this conversation as resolved.
Show resolved Hide resolved

def replace_symbols_using(self, table):
'''
Update any Symbols referenced by this Node (and its descendants) with
those in the supplied table with matching names. If there is no match
for a given Symbol then it is left unchanged.

Since this is a ScopingNode, it is associated with a symbol table.
Therefore, if the supplied table is the one for the scope containing
this node (if any), the one passed to the child nodes is updated to be
the one associated with this node.

:param table: the symbol table in which to look up replacement symbols.
:type table: :py:class:`psyclone.psyir.symbols.SymbolTable`

'''
next_table = table
if self.parent:
try:
# If this node is not within a scope we get a SymbolError
if table is self.parent.scope.symbol_table:
next_table = self.symbol_table
except SymbolError:
pass

for child in self.children:
child.replace_symbols_using(next_table)

@property
def symbol_table(self):
Expand Down
4 changes: 2 additions & 2 deletions src/psyclone/psyir/symbols/data_type_symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ def copy(self):
original will not be affected so the copy will not be referred
to by any other object.

:returns: A symbol object with the same properties as this \
:returns: A symbol object with the same properties as this
symbol object.
:rtype: :py:class:`psyclone.psyir.symbols.TypeSymbol`

'''
return type(self)(self.name, self.datatype, visibility=self.visibility,
interface=self.interface)
interface=self.interface.copy())

def __str__(self):
return f"{self.name}: {type(self).__name__}"
Expand Down
Loading
Loading