diff --git a/src/psyclone/domain/common/psylayer/global_reduction.py b/src/psyclone/domain/common/psylayer/global_reduction.py index bba1715cb2..8bc0ebd210 100644 --- a/src/psyclone/domain/common/psylayer/global_reduction.py +++ b/src/psyclone/domain/common/psylayer/global_reduction.py @@ -8,6 +8,7 @@ ''' This module provides the GlobalReduction base class. ''' import copy +from typing import List from psyclone.configuration import Config from psyclone.core import AccessType @@ -82,3 +83,21 @@ def args(self) -> list[KernelArgument]: ''':returns: the list of arguments associated with this node. Override the base method and simply return our argument.''' return [self._scalar] + + def next_accesses(self) -> List[Node]: + ''' + next_accesses on base GlobalReduction class just uses the base + Statement implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().next_accesses() + + def previous_accesses(self) -> List[Node]: + ''' + previous_accesses on base GlobalReduction class just uses the base + Statement implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().previous_accesses() diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 75ed4d6ede..4a19189ca3 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -739,6 +739,24 @@ def node_str(self, colour=True): f"type='{self._halo_type}', depth={self._halo_depth}, " f"check_dirty={self._check_dirty}]") + def next_accesses(self) -> list[Node]: + ''' + next_accesses on base HaloExchange class just uses the base Statement + implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().next_accesses() + + def previous_accesses(self) -> list[Node]: + ''' + previous_accesses on base HaloExchange class just uses the base + Statement implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().previous_accesses() + class Kern(Statement): '''Base class representing a call to a sub-program unit from within the @@ -1131,6 +1149,24 @@ def lower_to_language_level(self): return super().lower_to_language_level() + def next_accesses(self) -> list[Node]: + ''' + next_accesses on base Kern class just uses the base Statement + implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().next_accesses() + + def previous_accesses(self) -> list[Node]: + ''' + previous_accesses on base Kern class just uses the base Statement + implementation, returning an empty list. + + :returns: an empty list. + ''' + return super().previous_accesses() + class CodedKern(Kern): ''' diff --git a/src/psyclone/psyir/nodes/assignment.py b/src/psyclone/psyir/nodes/assignment.py index 4a64a23aaf..dad0028ea6 100644 --- a/src/psyclone/psyir/nodes/assignment.py +++ b/src/psyclone/psyir/nodes/assignment.py @@ -7,7 +7,7 @@ ''' This module contains the Assignment node implementation.''' -from psyclone.core import VariablesAccessMap, AccessType, Signature +from psyclone.core import VariablesAccessMap, AccessType from psyclone.errors import InternalError from psyclone.psyir.nodes.literal import Literal from psyclone.psyir.nodes.array_reference import ArrayReference @@ -213,7 +213,7 @@ def is_literal_assignment(self): ''' return isinstance(self.rhs, Literal) - def previous_accesses(self) -> dict[Signature, list[Node]]: + def previous_accesses(self) -> list[Node]: ''' :returns: the nodes containing the previous accesses of the symbols accessed within this node. It can be multiple nodes for @@ -229,9 +229,15 @@ def previous_accesses(self) -> dict[Signature, list[Node]]: # pylint: disable=import-outside-toplevel from psyclone.psyir.tools import DefinitionUseChain chain = DefinitionUseChain(refs) - return chain.find_backward_accesses() - - def next_accesses(self) -> dict[Signature, list[Node]]: + accesses = chain.find_backward_accesses() + results = [] + for sig in accesses: + for access in accesses[sig]: + if all(x is not access for x in results): + results.append(access) + return results + + def next_accesses(self) -> list[Node]: ''' :returns: the nodes containing the next accesses of the symbols accessed within this node. It can be multiple nodes for @@ -247,4 +253,10 @@ def next_accesses(self) -> dict[Signature, list[Node]]: # pylint: disable=import-outside-toplevel from psyclone.psyir.tools import DefinitionUseChain chain = DefinitionUseChain(refs) - return chain.find_forward_accesses() + accesses = chain.find_forward_accesses() + results = [] + for sig in accesses: + for access in accesses[sig]: + if all(x is not access for x in results): + results.append(access) + return results diff --git a/src/psyclone/psyir/nodes/call.py b/src/psyclone/psyir/nodes/call.py index 8689705b58..d67b7a3a74 100644 --- a/src/psyclone/psyir/nodes/call.py +++ b/src/psyclone/psyir/nodes/call.py @@ -11,7 +11,7 @@ from __future__ import annotations from collections.abc import Iterable -from typing import List, Tuple, Union, Optional +from typing import Tuple, Union, Optional from psyclone.configuration import Config from psyclone.core import AccessType, VariablesAccessMap @@ -20,6 +20,7 @@ from psyclone.psyir.nodes.container import Container from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.routine import Routine from psyclone.psyir.symbols import ( @@ -502,7 +503,7 @@ def copy(self): return new_copy - def get_callees(self) -> List[Routine]: + def get_callees(self) -> list[Routine]: ''' Searches for the implementation(s) of all potential target routines for this Call. It does *not* attempt to resolve static polymorphism @@ -758,7 +759,7 @@ def type_symbols_match(type1: Union[DataTypeSymbol, DataType], f"'{routine_arg_str}' ({dummy_type})" ) - def get_argument_map(self, routine: Routine) -> List[int]: + def get_argument_map(self, routine: Routine) -> list[int]: '''Return a list of indices mapping from each argument of this call to the corresponding entry in the argument list of the supplied routine. @@ -774,7 +775,7 @@ def get_argument_map(self, routine: Routine) -> List[int]: ''' # Create a copy of the list of actual arguments to the routine. # Once an argument has been successfully matched, set it to 'None' - routine_argument_list: List[DataSymbol] = ( + routine_argument_list: list[DataSymbol] = ( routine.symbol_table.argument_list[:] ) @@ -859,7 +860,7 @@ def get_argument_map(self, routine: Routine) -> List[int]: def get_callee( self, use_first_callee_and_no_arg_check: bool = False - ) -> Tuple[Routine, List[int]]: + ) -> Tuple[Routine, list[int]]: ''' Searches for the implementation(s) of the target routine for this Call including argument checks. @@ -911,3 +912,37 @@ def get_callee( f"No matching routine found for '{call_str}':" "\n" + error_msg ) + + def next_accesses(self) -> list[Node]: + ''' + :returns: the next_accesses of children of this Call. + ''' + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + next_accesses = [] + # Find all the children references + refs = [ref for ref in self.walk(Reference) if not + isinstance(ref.symbol, RoutineSymbol)] + chain = DefinitionUseChain(refs) + access_dict = chain.find_forward_accesses() + for access in access_dict: + self._merge_accesses(next_accesses, access_dict[access]) + return next_accesses + + def previous_accesses(self) -> list[Node]: + ''' + :returns: the previous_accesses of children of this Call. + ''' + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + prev_accesses = [] + # Find all the children references + refs = [ref for ref in self.walk(Reference) if not + isinstance(ref.symbol, RoutineSymbol)] + chain = DefinitionUseChain(refs) + access_dict = chain.find_backward_accesses() + for access in access_dict: + self._merge_accesses(prev_accesses, access_dict[access]) + return prev_accesses diff --git a/src/psyclone/psyir/nodes/codeblock.py b/src/psyclone/psyir/nodes/codeblock.py index 97bb20af12..978db7658f 100644 --- a/src/psyclone/psyir/nodes/codeblock.py +++ b/src/psyclone/psyir/nodes/codeblock.py @@ -227,6 +227,36 @@ def get_fortran_lines(self) -> list[str]: return [] raise NotImplementedError("Use appropriate CodeBlock subclass") + def next_accesses(self) -> list[Node]: + ''' + :returns: the next_accesses for the child References of this + CodeBlock. + ''' + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + next_accesses = [] + chain = DefinitionUseChain(self.children) + accesses = chain.find_forward_accesses() + for access in accesses: + self._merge_accesses(next_accesses, accesses[access]) + return next_accesses + + def previous_accesses(self) -> list[Node]: + ''' + :returns: the previous_accesses for the child References of this + CodeBlock. + ''' + # Avoid circular import + # pylint: disable=import-outside-toplevel + from psyclone.psyir.tools import DefinitionUseChain + prev_accesses = [] + chain = DefinitionUseChain(self.children) + accesses = chain.find_backward_accesses() + for access in accesses: + self._merge_accesses(prev_accesses, accesses[access]) + return prev_accesses + class Fparser2CodeBlock(CodeBlock): ''' The fparser2 implementation of CodeBlock. ''' diff --git a/src/psyclone/psyir/nodes/directive.py b/src/psyclone/psyir/nodes/directive.py index 60ea0fc048..bae5593f90 100644 --- a/src/psyclone/psyir/nodes/directive.py +++ b/src/psyclone/psyir/nodes/directive.py @@ -17,6 +17,7 @@ from psyclone.psyir.nodes.array_of_structures_reference import ( ArrayOfStructuresReference) from psyclone.psyir.nodes.clause import Clause +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -147,6 +148,22 @@ def create_data_movement_deep_copy_refs(self): *base_args, members) return read_only, write_only, readwrites + def next_accesses(self) -> list[Node]: + ''' + Directive classes don't have next_accesses to compute. + + :returns: an empty list. + ''' + return [] + + def previous_accesses(self) -> list[Node]: + ''' + Directive classes don't have previous_accesses to compute. + + :returns: an empty list. + ''' + return [] + class RegionDirective(Directive): ''' diff --git a/src/psyclone/psyir/nodes/if_block.py b/src/psyclone/psyir/nodes/if_block.py index 5f84061c57..4185d7341e 100644 --- a/src/psyclone/psyir/nodes/if_block.py +++ b/src/psyclone/psyir/nodes/if_block.py @@ -10,6 +10,8 @@ from psyclone.core import VariablesAccessMap from psyclone.errors import InternalError, GenerationError from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node +from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -164,3 +166,40 @@ def reference_accesses(self) -> VariablesAccessMap: if self.else_body: var_accesses.update(self.else_body.reference_accesses()) return var_accesses + + def next_accesses(self) -> list[Node]: + ''' + :returns: the combined next_accesses for the children of this IfBlock. + ''' + next_accesses = [] + # Find all the next_accesses for the References in the condition. + for ref in self.condition.walk(Reference): + new_accesses = ref.next_accesses() + self._merge_accesses(next_accesses, new_accesses) + for child in self.if_body: + self._merge_accesses(next_accesses, child.next_accesses()) + if self.else_body: + for child in self.else_body: + self._merge_accesses(next_accesses, child.next_accesses()) + + # FIXME Should we sort the output in some way? + return next_accesses + + def previous_accesses(self) -> list[Node]: + ''' + :returns: the combined previous_accesses for the children of this + IfBlock. + ''' + prev_accesses = [] + # Find all the next_accesses for the References in the condition. + for ref in self.condition.walk(Reference): + new_accesses = ref.previous_accesses() + self._merge_accesses(prev_accesses, new_accesses) + for child in self.if_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + if self.else_body: + for child in self.else_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + + # FIXME Should we sort the output in some way? + return prev_accesses diff --git a/src/psyclone/psyir/nodes/loop.py b/src/psyclone/psyir/nodes/loop.py index 799103d5a7..f519c8d792 100644 --- a/src/psyclone/psyir/nodes/loop.py +++ b/src/psyclone/psyir/nodes/loop.py @@ -11,6 +11,7 @@ from psyclone.core import VariablesAccessMap from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.nodes.routine import Routine from psyclone.psyir.nodes.reference import Reference @@ -513,3 +514,32 @@ def enters_scope(self, scope, visited_nodes=None) -> bool: ''' # pylint: disable=unused-argument return False + + def next_accesses(self) -> list[Node]: + ''' + :returns: the combined next_accesses for the children of this Loop. + ''' + next_accesses = [] + # Loop through the non loop_body children and compute accesses. + for child in self.children[0:4]: + for ref in child.walk(Reference): + var_accesses = ref.next_accesses() + self._merge_accesses(next_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(next_accesses, child.next_accesses()) + return next_accesses + + def previous_accesses(self) -> list[Node]: + ''' + :returns: the combined previous_accesses for the children of this + Loop. + ''' + prev_accesses = [] + # Loop through the non loop_body children and compute accesses. + for child in self.children[0:4]: + for ref in child.walk(Reference): + var_accesses = ref.previous_accesses() + self._merge_accesses(prev_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + return prev_accesses diff --git a/src/psyclone/psyir/nodes/psy_data_node.py b/src/psyclone/psyir/nodes/psy_data_node.py index 53c3cf4be9..33482919b7 100644 --- a/src/psyclone/psyir/nodes/psy_data_node.py +++ b/src/psyclone/psyir/nodes/psy_data_node.py @@ -702,6 +702,19 @@ def gen_type_bound_call(typename, methodname, argument_list=None, self.detach() return self.parent + def next_accesses(self) -> list[Node]: + ''' + :returns: an empty list as next_accesses isn't needed for PSyDataNode. + ''' + return [] + + def previous_accesses(self) -> list[Node]: + ''' + :returns: an empty list as previous_accesses isn't needed for + PSyDataNode. + ''' + return [] + # For AutoAPI documentation generation __all__ = ['PSyDataNode'] diff --git a/src/psyclone/psyir/nodes/return_stmt.py b/src/psyclone/psyir/nodes/return_stmt.py index 1f4290490a..f5ab075a88 100644 --- a/src/psyclone/psyir/nodes/return_stmt.py +++ b/src/psyclone/psyir/nodes/return_stmt.py @@ -7,6 +7,7 @@ ''' This module contains the Return node implementation.''' +from psyclone.psyir.nodes.node import Node from psyclone.psyir.nodes.statement import Statement @@ -20,3 +21,15 @@ class Return(Statement): _children_valid_format = "" _text_name = "Return" _colour = "yellow" + + def next_accesses(self) -> list[Node]: + ''' + :returns: an empty list as there are no next_accesses after a Return. + ''' + return [] + + def previous_accesses(self) -> list[Node]: + ''' + :returns: the previous_accesses of the return statement's child. + ''' + return [] diff --git a/src/psyclone/psyir/nodes/routine.py b/src/psyclone/psyir/nodes/routine.py index c96729f574..23e6c91dc3 100644 --- a/src/psyclone/psyir/nodes/routine.py +++ b/src/psyclone/psyir/nodes/routine.py @@ -567,6 +567,20 @@ def replace_with(self, node, keep_name_in_context=True): "replaced.") super().replace_with(node, keep_name_in_context=keep_name_in_context) + def next_accesses(self) -> list[Node]: + ''' + :returns: an empty list as PSyclone doesn't know about dependencies + that go outside of Routine scope. + ''' + return [] + + def previous_accesses(self) -> list[Node]: + ''' + :returns: an empty list as PSyclone doesn't know about dependencies + that go outside of Routine scope. + ''' + return [] + # For automatic documentation generation __all__ = ["Routine"] diff --git a/src/psyclone/psyir/nodes/statement.py b/src/psyclone/psyir/nodes/statement.py index 51f68cc999..d456e28755 100644 --- a/src/psyclone/psyir/nodes/statement.py +++ b/src/psyclone/psyir/nodes/statement.py @@ -7,15 +7,63 @@ ''' This module contains the Statement abstract node implementation.''' +import abc + from psyclone.psyir.nodes.node import Node from psyclone.psyir.commentable_mixin import CommentableMixin -class Statement(Node, CommentableMixin): +class Statement(Node, CommentableMixin, metaclass=abc.ABCMeta): ''' Abstract node representing a general PSyIR Statement. ''' + @abc.abstractmethod + def next_accesses(self) -> list[Node]: + ''' + Abstract method for finding the next_accesses of a statement. + Subclasses should override this according to their own structure to + return future accesses to any References contained in the statement. + + :returns: an empty list. + ''' + return [] + + @abc.abstractmethod + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + + :returns: an empty list. + ''' + return [] + + def _merge_accesses( + self, current_accesses: list[Node], new_accesses: list[Node] + ) -> None: + ''' + Helper function to merge access lists together for Statement + subclass next/previous_accesses functions. + Take all the accesses from new_accesses and adds them to the + current_accesses list if they're not already present and are + not contained in this nodes subtree. + + :param current_accesses: The list of currently computed dependent + accesses for this node. + :param new_accesses: The list of accesses to merge into + current_accesses. + ''' + for access in new_accesses: + in_subtree = access.is_descendant_of(self) + # If the access is not in the subtree of this node, and + # is not already present in the current_accesses array + # then add it to the array + if not in_subtree and all( + [acc is not access for acc in current_accesses]): + current_accesses.append(access) + # For automatic API documentation generation __all__ = ["Statement"] diff --git a/src/psyclone/psyir/nodes/while_loop.py b/src/psyclone/psyir/nodes/while_loop.py index dfe0fdb1da..3240bdf636 100644 --- a/src/psyclone/psyir/nodes/while_loop.py +++ b/src/psyclone/psyir/nodes/while_loop.py @@ -10,6 +10,8 @@ from psyclone.core import VariablesAccessMap from psyclone.errors import InternalError, GenerationError from psyclone.psyir.nodes.datanode import DataNode +from psyclone.psyir.nodes.node import Node +from psyclone.psyir.nodes.reference import Reference from psyclone.psyir.nodes.schedule import Schedule from psyclone.psyir.nodes.statement import Statement @@ -125,3 +127,30 @@ def reference_accesses(self) -> VariablesAccessMap: var_accesses = self.condition.reference_accesses() var_accesses.update(self.loop_body.reference_accesses()) return var_accesses + + def next_accesses(self) -> list[Node]: + ''' + :returns: the combined next_accesses for the children of this + WhileLoop + ''' + next_accesses = [] + for ref in self.condition.walk(Reference): + var_accesses = ref.next_accesses() + self._merge_accesses(next_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(next_accesses, child.next_accesses()) + return next_accesses + + def previous_accesses(self) -> list[Node]: + ''' + Abstract method for finding the previous_accesses of a statement. + Subclasses should override this according to their own structure to + return previous accesses to any References contained in the statement. + ''' + prev_accesses = [] + for ref in self.condition.walk(Reference): + var_accesses = ref.previous_accesses() + self._merge_accesses(prev_accesses, var_accesses) + for child in self.loop_body: + self._merge_accesses(prev_accesses, child.previous_accesses()) + return prev_accesses diff --git a/src/psyclone/tests/dependency_test.py b/src/psyclone/tests/dependency_test.py index e8a34b99de..828e9eff86 100644 --- a/src/psyclone/tests/dependency_test.py +++ b/src/psyclone/tests/dependency_test.py @@ -588,3 +588,11 @@ def test_lfric_stub_boundary_dofmap(): create_arg_list = KernStubArgList(kernel) create_arg_list.generate(var_accesses=var_accesses) assert "boundary_dofs_op_1: READ" in str(var_accesses) + + +def test_lfrickern_accesses(): + '''Check that next/previous_accesses methods on Kern classes + return an empty list.''' + kernel = LFRicKern() + assert kernel.next_accesses() == [] + assert kernel.previous_accesses() == [] diff --git a/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py b/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py index 8a85e669eb..d3e6225ee6 100644 --- a/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py +++ b/src/psyclone/tests/domain/common/psylayer/global_reduction_test.py @@ -129,3 +129,14 @@ def test_globalreduction_reference_accesses(): "GlobalReduction is held in a bespoke '_scalar' " "property.") assert list(vam) == ["FIXME"] + + +def test_globalreduction_accesses(): + '''Test the next/previous_accesses methods return an empty list + for a GlobalReduction.''' + _, invoke = get_invoke("15.14.3_sum_setval_field_builtin.f90", + api="lfric", dist_mem=True, idx=0) + schedule = invoke.schedule + global_sum = schedule.children[2] + assert global_sum.next_accesses() == [] + assert global_sum.previous_accesses() == [] diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index 1b93ce34f2..b244dc1dbc 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -975,6 +975,14 @@ def test_haloexchange_unknown_halo_depth(): assert halo_exchange._halo_depth is None +def test_haloexchange_accesses(): + '''Test that the next/previous_accesses on HaloExchange class returns + an empty list.''' + halo_exchange = HaloExchange(None) + assert halo_exchange.next_accesses() == [] + assert halo_exchange.previous_accesses() == [] + + def test_args_filter(): '''the args_filter() method is in both Loop() and Arguments() classes with the former method calling the latter. This example tests the diff --git a/src/psyclone/tests/psyir/backend/debug_writer_test.py b/src/psyclone/tests/psyir/backend/debug_writer_test.py index 48d4929452..c000c34be5 100644 --- a/src/psyclone/tests/psyir/backend/debug_writer_test.py +++ b/src/psyclone/tests/psyir/backend/debug_writer_test.py @@ -56,6 +56,12 @@ def lower_to_language_level(self): def validate_global_constraints(self): raise NotImplementedError("This should not be called") + def next_accesses(self): + '''Needed on all Statement subclasses.''' + + def previous_accesses(self): + '''Needed on all Statement subclasses.''' + class MyDSLDataNode(DataNode): ''' A dummy DSL DataNode that doesn't like being lowered ''' def lower_to_language_level(self): diff --git a/src/psyclone/tests/psyir/backend/visitor_test.py b/src/psyclone/tests/psyir/backend/visitor_test.py index 67a02ab961..7ca432ebf9 100644 --- a/src/psyclone/tests/psyir/backend/visitor_test.py +++ b/src/psyclone/tests/psyir/backend/visitor_test.py @@ -184,6 +184,12 @@ def lower_to_language_level(self): self.replace_with(new_node) return new_node + def next_accesses(self): + '''Needed on all Statement subclasses.''' + + def previous_accesses(self): + '''Needed on all Statement subclasses.''' + class MyVisitor(PSyIRVisitor): ''' Simple Visitor for Schedules and Return statements ''' diff --git a/src/psyclone/tests/psyir/nodes/assignment_test.py b/src/psyclone/tests/psyir/nodes/assignment_test.py index cfe2a13641..1d8709a00c 100644 --- a/src/psyclone/tests/psyir/nodes/assignment_test.py +++ b/src/psyclone/tests/psyir/nodes/assignment_test.py @@ -369,11 +369,9 @@ def test_next_accesses(fortran_reader): assigns = psyir.walk(Assignment) reaches = assigns[0].next_accesses() - sig = assigns[0].lhs.get_signature_and_indices()[0] - assert len(reaches) == 1 - assert len(reaches[sig]) == 2 - assert reaches[sig][0] is assigns[1].rhs.children[0] - assert reaches[sig][1] is assigns[1].lhs + assert len(reaches) == 2 + assert reaches[0] is assigns[1].rhs.children[0] + assert reaches[1] is assigns[1].lhs # Next test multiple References to different symbols in the # Assignment @@ -388,14 +386,10 @@ def test_next_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[0].next_accesses() - a_sig = assigns[0].lhs.get_signature_and_indices()[0] - b_sig = assigns[0].rhs.get_signature_and_indices()[0] - assert len(reaches) == 2 - assert len(reaches[a_sig]) == 2 - assert reaches[a_sig][0] is assigns[1].rhs.children[0] - assert reaches[a_sig][1] is assigns[1].lhs - assert len(reaches[b_sig]) == 1 - assert reaches[b_sig][0] is assigns[2].lhs + assert len(reaches) == 3 + assert reaches[0] is assigns[1].rhs.children[0] + assert reaches[1] is assigns[1].lhs + assert reaches[2] is assigns[2].lhs # Test References inside an inquiry function are ignored psyir = fortran_reader.psyir_from_source( @@ -409,11 +403,7 @@ def test_next_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[0].next_accesses() - a_sig = assigns[0].lhs.get_signature_and_indices()[0] - b_sig = assigns[0].rhs.children[0].get_signature_and_indices()[0] - assert len(reaches) == 2 - assert len(reaches[a_sig]) == 0 - assert len(reaches[b_sig]) == 0 + assert len(reaches) == 0 def test_previous_accesses(fortran_reader): @@ -430,10 +420,8 @@ def test_previous_accesses(fortran_reader): assigns = psyir.walk(Assignment) reaches = assigns[1].previous_accesses() - sig = assigns[0].lhs.get_signature_and_indices()[0] assert len(reaches) == 1 - assert len(reaches[sig]) == 1 - assert reaches[sig][0] is assigns[0].lhs + assert reaches[0] is assigns[0].lhs # Next test multiple References to different symbols in the # Assignment @@ -448,13 +436,9 @@ def test_previous_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[2].previous_accesses() - a_sig = assigns[2].lhs.get_signature_and_indices()[0] - b_sig = assigns[2].rhs.get_signature_and_indices()[0] assert len(reaches) == 2 - assert len(reaches[a_sig]) == 1 - assert reaches[a_sig][0] is assigns[1].lhs - assert len(reaches[b_sig]) == 1 - assert reaches[b_sig][0] is assigns[0].lhs + assert reaches[0] is assigns[1].lhs + assert reaches[1] is assigns[0].lhs # Test References inside an inquiry function are ignored psyir = fortran_reader.psyir_from_source( @@ -468,8 +452,4 @@ def test_previous_accesses(fortran_reader): ) assigns = psyir.walk(Assignment) reaches = assigns[1].previous_accesses() - a_sig = assigns[1].lhs.get_signature_and_indices()[0] - b_sig = assigns[1].rhs.children[0].get_signature_and_indices()[0] - assert len(reaches) == 2 - assert len(reaches[a_sig]) == 0 - assert len(reaches[b_sig]) == 0 + assert len(reaches) == 0 diff --git a/src/psyclone/tests/psyir/nodes/call_test.py b/src/psyclone/tests/psyir/nodes/call_test.py index 13c7ae813d..fd2f5d2246 100644 --- a/src/psyclone/tests/psyir/nodes/call_test.py +++ b/src/psyclone/tests/psyir/nodes/call_test.py @@ -13,7 +13,7 @@ from psyclone.core import Signature from psyclone.errors import GenerationError from psyclone.psyir.nodes import ( - ArrayReference, BinaryOperation, Call, Literal, + ArrayReference, Assignment, BinaryOperation, Call, Literal, Node, Reference, Routine, Schedule, CallMatchingArgumentsNotFound) from psyclone.psyir.nodes.node import colored from psyclone.psyir.symbols import ( @@ -2051,3 +2051,41 @@ def test_call_datatype(fortran_reader): # TODO #1799: Improve datatype inference, the following one # has a definition that says is an ArrayType assert isinstance(calls[3].datatype, UnresolvedType) + + +def test_call_next_accesses(fortran_reader): + ''' Test the next_accesses method of Call gives the correct results.''' + psyir = fortran_reader.psyir_from_source(""" + subroutine test + use some_mod + integer :: i, j, k + call my_fun(i) + j = i + i = k + end subroutine test""") + call = psyir.walk(Call)[0] + access = call.next_accesses() + assigns = psyir.walk(Assignment) + # The result is the 2 following accesses to i. + assert len(access) == 2 + assert access[0] is assigns[0].rhs + assert access[1] is assigns[1].lhs + + +def test_call_previous_accesses(fortran_reader): + ''' Test the previous_accesses method of Call gives the correct results.''' + psyir = fortran_reader.psyir_from_source(""" + subroutine test + use some_mod + integer :: i, j, k + i = k + j = i + call my_fun(i) + end subroutine test""") + call = psyir.walk(Call)[0] + access = call.previous_accesses() + assigns = psyir.walk(Assignment) + # The result is the 2 following accesses to i. + assert len(access) == 2 + assert access[0] is assigns[1].rhs + assert access[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/codeblock_test.py b/src/psyclone/tests/psyir/nodes/codeblock_test.py index 689d373070..08158fde24 100644 --- a/src/psyclone/tests/psyir/nodes/codeblock_test.py +++ b/src/psyclone/tests/psyir/nodes/codeblock_test.py @@ -12,7 +12,7 @@ from fparser.common.readfortran import FortranStringReader from psyclone.configuration import Config from psyclone.psyir.frontend.fortran import FortranReader -from psyclone.psyir.nodes import Reference, Schedule +from psyclone.psyir.nodes import Assignment, Reference, Schedule from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.frontend.fortran_treesitter_reader import \ FortranTreeSitterReader @@ -346,3 +346,47 @@ def test_codeblock_has_potential_control_flow_jump(fortran_reader): assert codeblocks[2].has_potential_control_flow_jump() # labelled statement assert codeblocks[3].has_potential_control_flow_jump() + + +def test_codeblock_next_accesses(fortran_reader): + """Test that the next_accesses method works correctly for CodeBlocks.""" + code = """subroutine test() + integer :: i, j, k + print *, i + k = 0 ! Extra assignment to separate the CodeBlocks. + print *, j, i + j = 3 + i = 1 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + codeblocks = psyir.walk(CodeBlock) + assigns = psyir.walk(Assignment) + accesses = codeblocks[0].next_accesses() + assert len(accesses) == 1 + assert accesses[0] is codeblocks[1].children[1] + accesses = codeblocks[1].next_accesses() + assert len(accesses) == 2 + assert accesses[0] is assigns[1].lhs + assert accesses[1] is assigns[2].lhs + + +def test_codeblock_previous_accesses(fortran_reader): + """Test that the previous_accesses method works correctly for + CodeBlocks.""" + code = """subroutine test() + integer :: i, j, k + i = 1 + j = 3 + print *, j, i + k = 0 ! Extra assignment to separate the CodeBlocks. + print *, i + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + codeblocks = psyir.walk(CodeBlock) + assigns = psyir.walk(Assignment) + accesses = codeblocks[1].previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is codeblocks[0].children[1] + accesses = codeblocks[0].previous_accesses() + assert accesses[0] is assigns[1].lhs + assert accesses[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/directive_test.py b/src/psyclone/tests/psyir/nodes/directive_test.py index 2f6fc275e8..7a3cd00e7a 100644 --- a/src/psyclone/tests/psyir/nodes/directive_test.py +++ b/src/psyclone/tests/psyir/nodes/directive_test.py @@ -170,3 +170,11 @@ def test_standalonedirective_children_validation(): cdir.addchild(schedule) assert ("Item 'Schedule' can't be child 0 of 'StandaloneDirective'. The " "valid format is: 'Clause*'." in str(excinfo.value)) + + +def test_directive_accesses(): + '''Test that the next_accesses and previous_accesses methods + of Directive return an empty list.''' + x = nodes.StandaloneDirective() + assert x.next_accesses() == [] + assert x.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/if_block_test.py b/src/psyclone/tests/psyir/nodes/if_block_test.py index d4affa9135..319ed1dcd5 100644 --- a/src/psyclone/tests/psyir/nodes/if_block_test.py +++ b/src/psyclone/tests/psyir/nodes/if_block_test.py @@ -8,8 +8,9 @@ ''' Performs py.test tests on the IfBlock PSyIR node. ''' import pytest -from psyclone.psyir.nodes import IfBlock, Literal, Reference, Schedule, \ - Return, Assignment +from psyclone.psyir.nodes import ( + IfBlock, Literal, Reference, Schedule, Return, Assignment +) from psyclone.psyir.symbols import DataSymbol, ScalarType from psyclone.errors import InternalError, GenerationError from psyclone.psyir.backend.fortran import FortranWriter @@ -256,3 +257,141 @@ def test_ifblock_children_validation(): ifblock.addchild(else_body) assert ("Item 'Schedule' can't be child 3 of 'If'. The valid format is: " "'DataNode, Schedule [, Schedule]'." in str(excinfo.value)) + + +def test_ifblock_next_accesses_condition(fortran_reader): + '''Test the next_accesses finds the next_accesses for references + in the ifblock's condition correctly.''' + + code = """subroutine test + integer :: i, j, k, l + + if(i > 3) then + j = 1 + else + k = 1 + end if + l = i + i = 1 + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.next_accesses() + # The next_accesses are the two accesses to i after the ifblock. + assert len(accesses) == 2 + assigns = psyir.walk(Assignment) + assert accesses[0] is assigns[2].rhs + assert accesses[1] is assigns[3].lhs + + # Check that accesses within the ifblock aren't found. + code = """subroutine test + integer :: i, j, k + if (i > 3) then + i = 2 + else + i = 4 + end if + k = 1 + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.next_accesses() + assert len(accesses) == 0 + + +def test_ifblock_next_accesses_bodies(fortran_reader): + '''Test the next_accesses method finds the next_accesses for + references in the ifblock's bodies.''' + code = """subroutine test + integer :: i, j, k, l + if(i > 3) then + j = 1 + else + k = 1 + end if + l = j + l = l + k + j = 3 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.next_accesses() + assigns = psyir.walk(Assignment) + assert len(accesses) == 3 + # First access after the ifblock to j. + assert accesses[0] is assigns[2].rhs + # First access after the ifblock to k. + assert accesses[2] is assigns[3].rhs.children[1] + # Write access after the ifblock to j. + assert accesses[1] is assigns[4].lhs + + +def test_ifblock_previous_accesses_condition(fortran_reader): + '''Test the previous_accesses finds the previous_accesses for references + in the ifblock's condition correctly.''' + + code = """subroutine test + integer :: i, j, k, l + + i = 1 + l = i + if(i > 3) then + j = 1 + else + k = 1 + end if + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.previous_accesses() + # The previous_accesses are the two accesses to i after the ifblock. + assert len(accesses) == 2 + assigns = psyir.walk(Assignment) + assert accesses[0] is assigns[1].rhs + assert accesses[1] is assigns[0].lhs + + # Check that accesses within the ifblock aren't found. + code = """subroutine test + integer :: i, j, k + k = 1 + if (i > 3) then + i = 2 + else + i = 4 + end if + end subroutine test""" + + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.previous_accesses() + assert len(accesses) == 0 + + +def test_ifblock_previous_accesses_bodies(fortran_reader): + '''Test the previous_accesses method finds the previous_accesses for + references in the ifblock's bodies.''' + code = """subroutine test + integer :: i, j, k, l + j = 3 + l = l + k + l = j + if(i > 3) then + j = 1 + else + k = 1 + end if + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + ifblock = psyir.walk(IfBlock)[0] + accesses = ifblock.previous_accesses() + assigns = psyir.walk(Assignment) + assert len(accesses) == 3 + # First access after the ifblock to j. + assert accesses[0] is assigns[2].rhs + # First access after the ifblock to k. + assert accesses[2] is assigns[1].rhs.children[1] + # Write access after the ifblock to j. + assert accesses[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/loop_test.py b/src/psyclone/tests/psyir/nodes/loop_test.py index 84e5645f4e..49936c09df 100644 --- a/src/psyclone/tests/psyir/nodes/loop_test.py +++ b/src/psyclone/tests/psyir/nodes/loop_test.py @@ -551,3 +551,195 @@ def test_loops_enters_scope(): # Always returns false, regardless of the scope, as the loop variable # by definition gets the first value assigned here from the loop bounds. assert not loop.enters_scope(None) + + +def test_loop_variable_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the loop + variable.""" + code = """subroutine test + integer :: i, j, k + do i = 1, 100 + j = 2 + end do + i = k + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_variable_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the loop + variable.""" + code = """subroutine test + integer :: i, j, k + i = k + do i = 1, 100 + j = 2 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + accesses = loop.previous_accesses() + assert len(accesses) == 0 + pytest.xfail(reason="#3486 Definition Use Chains don't yet account" + "for loop variables.") + # Correct implementation should result in: + # assigns = psyir.walk(Assignment) + # assert len(accesses) == 1 + # assert accesses[0] is assigns[0].lhs + + +def test_loop_start_value_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the start + condition.""" + code = """subroutine test + integer :: i, j, k + do i = j, 100 + k = 1 + end do + j = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_start_value_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the start + condition.""" + code = """subroutine test + integer :: i, j, k + j = 2 + do i = j, 100 + k = 1 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + + +def test_loop_stop_value_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the stop + condition.""" + code = """subroutine test + integer :: i, j, k + do i = 1, j + k = 1 + end do + j = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_stop_value_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the stop + condition.""" + code = """subroutine test + integer :: i, j, k + j = 2 + do i = 1, j + k = 1 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + + +def test_loop_step_value_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the step + condition.""" + code = """subroutine test + integer :: i, j, k + do i = 1, 100, j + k = 1 + end do + j = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_loop_step_value_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the step + condition.""" + code = """subroutine test + integer :: i, j, k + j = 2 + do i = 1, 100, j + k = 1 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + + +def test_loop_body_next_accesses(fortran_reader): + """Test that the next_accesses function works correctly for the + loop_body.""" + code = """subroutine test + integer :: i, j, k + do i = 1, 100 + j = 4 * i + k = 3 + i + end do + j = j * 2 + k = k + 3 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 4 + assert accesses[0] is assigns[2].rhs.children[0] + assert accesses[1] is assigns[2].lhs + assert accesses[2] is assigns[3].rhs.children[0] + assert accesses[3] is assigns[3].lhs + + +def test_loop_body_previous_accesses(fortran_reader): + """Test that the previous_accesses function works correctly for the + loop_body.""" + code = """subroutine test + integer :: i, j, k + k = k + 3 + j = j * 2 + do i = 1, 100 + j = 4 * i + k = 3 + i + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 2 + assert accesses[0] is assigns[1].lhs + assert accesses[1] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/nodes/node_test.py b/src/psyclone/tests/psyir/nodes/node_test.py index 1020069afa..aafe1619e2 100644 --- a/src/psyclone/tests/psyir/nodes/node_test.py +++ b/src/psyclone/tests/psyir/nodes/node_test.py @@ -20,14 +20,19 @@ from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory, Kern from psyclone.psyir.backend.debug_writer import DebugWriter -from psyclone.psyir.nodes import Schedule, Reference, Container, Routine, \ - Assignment, Return, Loop, Literal, Statement, node, KernelSchedule, \ +from psyclone.psyir.nodes import ( + Schedule, Reference, Container, Routine, node, + Assignment, Return, Loop, Literal, KernelSchedule, Statement, BinaryOperation, ArrayReference, Call, Range +) from psyclone.psyir.nodes.node import ChildrenList, Node -from psyclone.psyir.symbols import DataSymbol, SymbolError, \ - ScalarType, SymbolTable, ArrayType, RoutineSymbol, NoType +from psyclone.psyir.symbols import ( + DataSymbol, SymbolError, ScalarType, SymbolTable, ArrayType, + RoutineSymbol, NoType +) from psyclone.tests.utilities import get_invoke from psyclone.psyir.nodes.node import colored +from psyclone.tests.test_files.dummy_statement import DummyStatement BASE_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname( os.path.abspath(__file__)))), "test_files", "lfric") @@ -303,7 +308,7 @@ def test_compute_cached_abs_positions_error(): just happens with inconsistent parent-child connections). ''' parent = Schedule() - node1 = Statement() + node1 = DummyStatement() # Manually connect the _parent attribute which won't make a consistent # two-way relationship node1._parent = parent @@ -366,7 +371,7 @@ def test_node_abs_position_error(): happens with inconsistent parent-child connections). ''' parent = Schedule() - node1 = Statement() + node1 = DummyStatement() # Manually connect the _parent attribute which won't make a consistent # two-way relationship node1._parent = parent @@ -1122,8 +1127,8 @@ def test_children_setter(): assert isinstance(testnode.children, ChildrenList) # When is set up with a list, this becomes a ChildrenList - statement1 = Statement() - statement2 = Statement() + statement1 = DummyStatement() + statement2 = DummyStatement() testnode.children = [statement1, statement2] assert isinstance(testnode.children, ChildrenList) assert statement1.parent is testnode @@ -1145,8 +1150,8 @@ def test_children_setter(): def test_children_clear(): '''Test that the clear() method works correctly for a ChildrenList.''' testnode = Schedule() - stmt1 = Statement() - stmt2 = Statement() + stmt1 = DummyStatement() + stmt2 = DummyStatement() testnode.addchild(stmt1) testnode.addchild(stmt2) assert len(testnode.children) == 2 @@ -1242,11 +1247,11 @@ def test_lower_to_language_level(monkeypatch): # Monkeypatch the lower_to_language_level to just mark a flag def visited(self): self._visited_flag = True - monkeypatch.setattr(Statement, "lower_to_language_level", visited) + monkeypatch.setattr(DummyStatement, "lower_to_language_level", visited) testnode = Schedule() - node1 = Statement() - node2 = Statement() + node1 = DummyStatement() + node2 = DummyStatement() testnode.children = [node1, node2] # Execute method @@ -1265,9 +1270,9 @@ def test_replace_with(): '''Check that the replace_with method behaves as expected.''' parent_node = Schedule() - node1 = Statement() - node2 = Statement() - node3 = Statement() + node1 = DummyStatement() + node2 = DummyStatement() + node3 = DummyStatement() parent_node.children = [node1, node2, node3] new_node = Assignment() @@ -1343,8 +1348,8 @@ def test_replace_with_error2(): ''' parent = Schedule() - node1 = Statement() - node2 = Statement() + node1 = DummyStatement() + node2 = DummyStatement() with pytest.raises(TypeError) as info: node1.replace_with("hello") @@ -1387,9 +1392,9 @@ def test_pop_all_children(): # Create a PSyIR tree parent = Schedule() - node1 = Statement() + node1 = DummyStatement() parent.addchild(node1) - node2 = Statement() + node2 = DummyStatement() parent.addchild(node2) # Execute pop_all_children method @@ -1440,19 +1445,19 @@ def test_parent_references_coherency(): parent = Schedule() # Children addition methods - node1 = Statement() + node1 = DummyStatement() parent.addchild(node1) assert node1.parent is parent - node2 = Statement() + node2 = DummyStatement() parent.children.append(node2) assert node2.parent is parent - node3 = Statement() + node3 = DummyStatement() parent.children.extend([node3]) assert node3.parent is parent - node4 = Statement() + node4 = DummyStatement() parent.children.insert(0, node4) assert node4.parent is parent @@ -1487,7 +1492,7 @@ def test_node_constructor_with_parent(): wrong_parent = Schedule() # By default no parent reference is given - node = Statement() + node = DummyStatement() assert node.parent is None assert node.has_constructor_parent is False @@ -1599,8 +1604,8 @@ def test_equality(): parent1._symbol_table = symboltable parent2 = Schedule() parent2._symbol_table = symboltable - zero = Statement() - one = Statement() + zero = DummyStatement() + one = DummyStatement() assert parent1 != zero assert parent1 == parent2 @@ -1613,7 +1618,7 @@ def test_equality(): assert parent1 == parent2 # Add a second child to parent1 - two = Statement() + two = DummyStatement() parent1.addchild(two) assert parent1 != parent2 @@ -2099,3 +2104,10 @@ def test_get_last_descendant_node(fortran_reader): ifblock = psyir.children[0].children[0] assigns = psyir.walk(Assignment) assert ifblock.get_last_descendant_node() is assigns[2].rhs.children[1] + + +def test_statement_accesses(): + '''Test the Statement class accesses methods.''' + node = DummyStatement() + assert node.next_accesses() == [] + assert node.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/psy_data_node_test.py b/src/psyclone/tests/psyir/nodes/psy_data_node_test.py index 56e02db895..3d5b21d10b 100644 --- a/src/psyclone/tests/psyir/nodes/psy_data_node_test.py +++ b/src/psyclone/tests/psyir/nodes/psy_data_node_test.py @@ -16,13 +16,13 @@ from psyclone.psyir.nodes import ( CodeBlock, PSyDataNode, Schedule, Return, Routine) from psyclone.parse import ModuleManager -from psyclone.psyir.nodes.statement import Statement from psyclone.psyir.transformations import PSyDataTrans, TransformationError from psyclone.psyir.symbols import ( ContainerSymbol, ImportInterface, SymbolTable, DataTypeSymbol, UnresolvedType, DataSymbol, UnsupportedFortranType) from psyclone.psyGen import Kern from psyclone.tests.utilities import get_base_path, get_invoke +from psyclone.tests.test_files.dummy_statement import DummyStatement # ----------------------------------------------------------------------------- @@ -145,7 +145,7 @@ def test_psy_data_node_tree_correct(): # 3. No parent, but children: # =========================== - children = [Statement(), Statement()] + children = [DummyStatement(), DummyStatement()] psy_node = PSyDataNode.create(children, SymbolTable()) # The children must be connected to the schedule, which is @@ -164,10 +164,10 @@ def test_psy_data_node_tree_correct(): # ======================= parent = Schedule() # The children must be added to the parent before creating the ExtractNode - parent.addchild(Statement()) - parent.addchild(Statement()) + parent.addchild(DummyStatement()) + parent.addchild(DummyStatement()) # Add another child that must stay with the parent node - third_child = Statement() + third_child = DummyStatement() parent.addchild(third_child) assert parent.children[2] is third_child # Only move the first two children, leave the third where it is @@ -559,3 +559,11 @@ def test_psy_data_node_gocean_inside_of_loop(fortran_writer): r"call psy_data_1 % PostEnd.*") assert re.search(correct_re, code, re.I) is not None + + +def test_psy_data_node_accesses(): + '''Test that the next/previous_accesses methods of PSyDataNode + return empty lists.''' + psy_node = PSyDataNode() + assert psy_node.next_accesses() == [] + assert psy_node.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/return_stmt_test.py b/src/psyclone/tests/psyir/nodes/return_stmt_test.py index ec72176c9c..45e3d02eda 100644 --- a/src/psyclone/tests/psyir/nodes/return_stmt_test.py +++ b/src/psyclone/tests/psyir/nodes/return_stmt_test.py @@ -38,3 +38,19 @@ def test_return_children_validation(): return_stmt.addchild(return_stmt1) assert ("Item 'Return' can't be child 0 of 'Return'. Return is a" " LeafNode and doesn't accept children.") in str(excinfo.value) + + +def test_return_stmt_accesses(fortran_reader): + '''Test that the return statement next/previous_accesses return an empty + list.''' + code = """subroutine test + integer :: i + i = 1 + return + i = 2 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + print(psyir.view()) + stmt = psyir.walk(Return)[0] + assert stmt.next_accesses() == [] + assert stmt.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/routine_test.py b/src/psyclone/tests/psyir/nodes/routine_test.py index 986a7e3d1b..a8510ac9ce 100644 --- a/src/psyclone/tests/psyir/nodes/routine_test.py +++ b/src/psyclone/tests/psyir/nodes/routine_test.py @@ -691,3 +691,17 @@ def test_outer_scope_accesses_module_data(fortran_reader): rt1.check_outer_scope_accesses(call, "call") assert ("'second' contains accesses to 'vaar' which is declared in the " "callee module scope" in str(err.value)) + + +def test_routine_accesses(fortran_reader): + ''' Test that the next/previous_accesses methods on Routine return + an empty list.''' + code = """subroutine test + integer :: i + i = 3 + i = 4 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.children[0] + assert routine.next_accesses() == [] + assert routine.previous_accesses() == [] diff --git a/src/psyclone/tests/psyir/nodes/schedule_test.py b/src/psyclone/tests/psyir/nodes/schedule_test.py index 74fbef07e0..d6977464bc 100644 --- a/src/psyclone/tests/psyir/nodes/schedule_test.py +++ b/src/psyclone/tests/psyir/nodes/schedule_test.py @@ -9,12 +9,13 @@ import os import pytest -from psyclone.psyir.nodes import Schedule, Assignment, Range, Statement +from psyclone.psyir.nodes import Schedule, Assignment, Range from psyclone.psyir.nodes.node import colored from psyclone.psyir.symbols import SymbolTable from psyclone.psyGen import PSyFactory from psyclone.parse.algorithm import parse from psyclone.errors import GenerationError +from psyclone.tests.test_files.dummy_statement import DummyStatement BASE_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname( @@ -35,7 +36,8 @@ def test_sched_init(): # A custom symbol table and parent and children nodes can be given as # arguments of Schedule. symtab = SymbolTable() - sched2 = Schedule(parent=sched, children=[Statement(), Statement()], + sched2 = Schedule(parent=sched, children=[DummyStatement(), + DummyStatement()], symbol_table=symtab) assert isinstance(sched2, Schedule) assert sched2.parent is sched diff --git a/src/psyclone/tests/psyir/nodes/while_loop_test.py b/src/psyclone/tests/psyir/nodes/while_loop_test.py index 61d85c4151..eb7cbf7d10 100644 --- a/src/psyclone/tests/psyir/nodes/while_loop_test.py +++ b/src/psyclone/tests/psyir/nodes/while_loop_test.py @@ -10,8 +10,8 @@ import pytest from psyclone.errors import InternalError, GenerationError from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.nodes import Assignment, BinaryOperation, Literal, \ - Reference, Return, Schedule, WhileLoop +from psyclone.psyir.nodes import (Assignment, BinaryOperation, Literal, + Reference, Return, Schedule, WhileLoop) from psyclone.psyir.nodes.node import colored from psyclone.psyir.symbols import DataSymbol, ScalarType from psyclone.tests.utilities import check_links @@ -139,3 +139,83 @@ def test_whileloop_can_be_printed(): assert "WhileLoop[]\n" in str(loop) assert "condition1" in str(loop) # Test condition is printed assert "Return[]" in str(loop) # Test loop body is printed + + +def test_whileloop_condition_next_accesses(fortran_reader): + '''Test that the next_accesses method works correctly on References + in the WhileLoop condition.''' + code = """subroutine test + integer :: i, j, k + + do while(i < 2) + j = 2 + end do + i = k + 1 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_whileloop_condition_previous_accesses(fortran_reader): + '''Test that the previous_accesses method works correctly on References + in the WhileLoop condition.''' + code = """subroutine test + integer :: i, j, k + + i = k + 1 + do while(i < 2) + j = 2 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs + + +def test_whileloop_body_next_accesses(fortran_reader): + '''Test that the next_accesses method works correctly on the + WhileLoop body.''' + code = """subroutine test + integer :: i, j, k + + do while(i < 2) + j = 2 + end do + j = k + 1 + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.next_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[1].lhs + + +def test_whileloop_body_previous_accesses(fortran_reader): + '''Test that the previous_accesses method works correctly on the + WhileLoop body.''' + code = """subroutine test + integer :: i, j, k + + j = k + 1 + do while(i < 2) + j = 2 + end do + end subroutine test""" + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.walk(WhileLoop)[0] + assigns = psyir.walk(Assignment) + accesses = loop.previous_accesses() + assert len(accesses) == 1 + assert accesses[0] is assigns[0].lhs diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 408fb214c6..fe1738be4b 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -17,12 +17,13 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import ( Assignment, Call, IntrinsicCall, Loop, Node, Reference, - Routine, Statement, Literal) + Routine, Literal) from psyclone.psyir.symbols import ( AutomaticInterface, DataSymbol, ImportInterface, UnresolvedType, ScalarType) from psyclone.psyir.transformations import ( InlineTrans, TransformationError) +from psyclone.tests.test_files.dummy_statement import DummyStatement from psyclone.tests.utilities import Compile, get_invoke MY_TYPE = (" integer, parameter :: ngrids = 10\n" @@ -2661,10 +2662,10 @@ def test_validate_automatic_array_sized_by_arg(fortran_reader, monkeypatch): inline_trans.validate(call) # Break Reference.previous_accesses() to exercise the InternalError. monkeypatch.setattr(call.arguments[1], "previous_accesses", - lambda: [Statement()]) + lambda: [DummyStatement()]) with pytest.raises(InternalError) as err: inline_trans.validate(call) - assert ("Unexpected node type (Statement) returned from Reference." + assert ("Unexpected node type (DummyStatement) returned from Reference." "previous_accesses()" in str(err.value)) diff --git a/src/psyclone/tests/psyir/transformations/transformations_test.py b/src/psyclone/tests/psyir/transformations/transformations_test.py index 0258c452cb..5f00c0b488 100644 --- a/src/psyclone/tests/psyir/transformations/transformations_test.py +++ b/src/psyclone/tests/psyir/transformations/transformations_test.py @@ -14,7 +14,7 @@ import pytest from fparser.common.readfortran import FortranStringReader from psyclone.psyir.nodes import ( - CodeBlock, Literal, Loop, Node, Reference, Schedule, Statement, + CodeBlock, Literal, Loop, Node, Reference, Schedule, ACCLoopDirective, OMPMasterDirective, Fparser2CodeBlock, OMPDoDirective, OMPLoopDirective, Routine) from psyclone.psyir.symbols import ( @@ -28,6 +28,7 @@ OMPSingleTrans, OMPMasterTrans) from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory +from psyclone.tests.test_files.dummy_statement import DummyStatement GOCEAN_BASE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "test_files", @@ -63,7 +64,7 @@ def test_accloop(): assert trans.name == "ACCLoopTrans" assert str(trans) == "Adds an 'OpenACC loop' directive to a loop" - cnode = Statement() + cnode = DummyStatement() tdir = trans._directive([cnode]) assert isinstance(tdir, ACCLoopDirective) @@ -233,7 +234,7 @@ class DummyVAM: # pylint: disable=too-few-public-methods all_signatures = [DummySig()] def __getitem__(self, _): - return [DummyAccess(Statement())] + return [DummyAccess(DummyStatement())] monkeypatch.setattr(routine, "reference_accesses", lambda: DummyVAM()) with pytest.raises(TransformationError) as err: @@ -836,7 +837,7 @@ def test_profile_trans_invalid_name(value): # We need to have a schedule as parent, otherwise the node # (with no parent) will not be allowed. sched = Schedule() - node = Statement(parent=sched) + node = DummyStatement(parent=sched) sched.addchild(node) with pytest.raises(TransformationError) as excinfo: profile_trans.apply(node, options={"region_name": value}) diff --git a/src/psyclone/tests/test_files/dummy_statement.py b/src/psyclone/tests/test_files/dummy_statement.py new file mode 100644 index 0000000000..c5008b3f40 --- /dev/null +++ b/src/psyclone/tests/test_files/dummy_statement.py @@ -0,0 +1,53 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2026, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Author A. B. G. Chalk, STFC Daresbury Lab + +'''This module contains a DummyStatement class used for tests where Statement +nodes are initialised.''' + +from psyclone.psyir.nodes import Statement + + +class DummyStatement(Statement): + + def next_accesses(self) -> list: + '''Empty implementation of the required next_accesses + abstractmethod.''' + return super().next_accesses() + + def previous_accesses(self) -> list: + '''Empty implementation of the required previous_accesses + abstractmethod. + ''' + return super().previous_accesses()