diff --git a/src/psyclone/core/access_type.py b/src/psyclone/core/access_type.py index 7dc2e41239..ff11abed65 100644 --- a/src/psyclone/core/access_type.py +++ b/src/psyclone/core/access_type.py @@ -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 @@ -109,7 +108,7 @@ def all_read_accesses(): :rtype: List of py:class:`psyclone.core.access_type.AccessType`. ''' return [AccessType.READ, AccessType.READWRITE, AccessType.INC, - AccessType.READINC] + AccessType.READINC] + AccessType.get_valid_reduction_modes() @staticmethod def get_valid_reduction_modes(): diff --git a/src/psyclone/core/component_indices.py b/src/psyclone/core/component_indices.py index 4772bebbc2..191537f055 100644 --- a/src/psyclone/core/component_indices.py +++ b/src/psyclone/core/component_indices.py @@ -36,9 +36,7 @@ '''This module provides a class to manage indices in variable accesses.''' -from __future__ import print_function, absolute_import - - +from psyclone.core.symbolic_maths import SymbolicMaths from psyclone.errors import InternalError @@ -190,6 +188,34 @@ def get_subscripts_of(self, set_of_vars): indices.append(unique_vars) return indices + # ------------------------------------------------------------------------ + def equal(self, other): + '''Checks whether `self` has the same indices as `other`. It uses + symbolic maths to compare the indices. + returns: whether self has the same indices as other. + :rtype: bool + ''' + + # We need to make sure the sizes are identical, otherwise: + # 1.) the zip below will stop after the shortest number of elements, + # 2.) we wouldn't be able to distinguish between a%b(i) and a(i)%b + + # Same number of components: + if len(self) != len(other): + return False + # Same number of dimensions for each component + for i in range(len(self)): + if len(self._component_indices[i]) != \ + len(other.indices_lists[i]): + return False + + # Now the number of indices are identical, compare the actual indices: + sym_maths = SymbolicMaths.get() + for i, j in zip(self.iterate(), other.iterate()): + if not sym_maths.equal(self[i], other[j]): + return False + return True + # ---------- Documentation utils -------------------------------------------- # # The list of module members that we wish AutoAPI to generate diff --git a/src/psyclone/core/single_variable_access_info.py b/src/psyclone/core/single_variable_access_info.py index d58efa6edb..d89236de2c 100644 --- a/src/psyclone/core/single_variable_access_info.py +++ b/src/psyclone/core/single_variable_access_info.py @@ -68,12 +68,16 @@ class AccessInfo(): :type component_indices: None, [], a list or a list of lists of \ :py:class:`psyclone.psyir.nodes.Node` objects, or an object of type \ :py:class:`psyclone.core.component_indices.ComponentIndices` + :param bool conditional: if the access is a conditional access. ''' - def __init__(self, access_type, location, node, component_indices=None): + def __init__(self, access_type, location, node, component_indices=None, + conditional=False): + # pylint: disable=too-many-arguments self._location = location self._access_type = access_type self._node = node + self._conditional = conditional if not isinstance(component_indices, ComponentIndices): self.component_indices = ComponentIndices(component_indices) else: @@ -82,7 +86,8 @@ def __init__(self, access_type, location, node, component_indices=None): def __str__(self): '''Returns a string representation showing the access mode and location, e.g.: WRITE(5).''' - return f"{self._access_type}({self._location})" + return (f"{'%' if self._conditional else ''}" + f"{self._access_type}({self._location})") def change_read_to_write(self): '''This changes the access mode from READ to WRITE. @@ -166,6 +171,36 @@ def node(self): :rtype: :py:class:`psyclone.psyir.nodes.Node` ''' return self._node + @property + def is_read(self): + ''':returns: whether this access includes a read access + (e.g. a READWRITE would also be a read access). + :rtype: bool + ''' + return self._access_type in AccessType.all_read_accesses() + + @property + def is_written(self): + ''':returns: whether this access includes a write access + (e.g. a READWRITE would also be a write access). + :rtype: bool + ''' + return self._access_type in AccessType.all_write_accesses() + + @property + def conditional(self): + ''':returns: whether the access is conditional. + :rtype: bool + ''' + return self._conditional + + @conditional.setter + def conditional(self, conditional): + '''Sets whether this access is conditional. + :param bool conditional: whether this access is conditional or not. + ''' + self._conditional = conditional + # ============================================================================= class SingleVariableAccessInfo(): @@ -216,6 +251,22 @@ def is_written(self): AccessType.all_write_accesses() for access_info in self._accesses) + def is_conditional_read(self): + ''':returns: if all read accesses to this variable are conditional, + meaning that this variable is read conditional + :rtype: bool + ''' + return all(access_read.conditional + for access_read in self.all_read_accesses) + + def is_conditional_write(self): + ''':returns: if all write accesses to this variable are conditional, + meaning that this variable is written conditional + :rtype: bool + ''' + return all(access_written.conditional + for access_written in self.all_write_accesses) + def is_written_first(self): ''':returns: True if this variable is written in the first access \ (which indicates that this variable is not an input variable \ @@ -286,7 +337,8 @@ def all_write_accesses(self): if access.access_type in AccessType.all_write_accesses()] def add_access_with_location(self, access_type, location, node, - component_indices): + component_indices, conditional=False): + # pylint: disable=too-many-arguments '''Adds access information to this variable. :param access_type: the type of access (READ, WRITE, ....) @@ -300,9 +352,10 @@ def add_access_with_location(self, access_type, location, node, access. :type component_indices: \ :py:class:`psyclone.core.component_indices.ComponentIndices` + :param bool conditional: if the access is conditional ''' self._accesses.append(AccessInfo(access_type, location, node, - component_indices)) + component_indices, conditional)) def change_read_to_write(self): '''This function is only used when analysing an assignment statement. diff --git a/src/psyclone/core/variables_access_info.py b/src/psyclone/core/variables_access_info.py index 11f3b0188a..92df2aa2d0 100644 --- a/src/psyclone/core/variables_access_info.py +++ b/src/psyclone/core/variables_access_info.py @@ -40,6 +40,7 @@ '''This module provides management of variable access information.''' +from psyclone.core.access_type import AccessType from psyclone.core.component_indices import ComponentIndices from psyclone.core.signature import Signature from psyclone.core.single_variable_access_info import SingleVariableAccessInfo @@ -84,10 +85,18 @@ class VariablesAccessInfo(dict): # COLLECT-ARRAY-SHAPE-READS: controls if access to the shape of an array # (e.g. ``ubound(a)`` are reported as read or not at all. Defaults # to True. + # FLATTEN: by default, accesses in blocks (e.g. if blocks, loops) will + # all be added to the access information, with no indication that an + # accesses might be conditional (i.e. it's up to the calling program + # to check if a node is inside an if block etc). If FLATTEN is set to + # True, only the statements in the original node list will be + # reported, but accesses including in block will be marked as + # conditional if required. Check the manual for additional details. # USE-ORIGINAL-NAMES: if set this will report the original names of any # symbol that is being renamed (``use mod, renamed_a=>a``). Defaults # to False. _DEFAULT_OPTIONS = {"COLLECT-ARRAY-SHAPE-READS": False, + "FLATTEN": False, "USE-ORIGINAL-NAMES": False} def __init__(self, nodes=None, options=None): @@ -159,7 +168,9 @@ def __str__(self): mode = "READ" elif self.is_written(signature): mode = "WRITE" - output_list.append(f"{signature}: {mode}") + all_accesses = self[signature] + cond = any(acc.conditional for acc in all_accesses) + output_list.append(f"{'%' if cond else ''}{signature}: {mode}") return ", ".join(output_list) def options(self, key=None): @@ -203,7 +214,9 @@ def next_location(self): '''Increases the location number.''' self._location = self._location + 1 - def add_access(self, signature, access_type, node, component_indices=None): + def add_access(self, signature, access_type, node, component_indices=None, + conditional=False): + # pylint: disable=too-many-arguments '''Adds access information for the variable with the given signature. If the `component_indices` parameter is not an instance of `ComponentIndices`, it is used to construct an instance. Therefore it @@ -266,11 +279,13 @@ def add_access(self, signature, access_type, node, component_indices=None): if signature in self: self[signature].add_access_with_location(access_type, self._location, node, - component_indices) + component_indices, + conditional=conditional) else: var_info = SingleVariableAccessInfo(signature) var_info.add_access_with_location(access_type, self._location, - node, component_indices) + node, component_indices, + conditional=conditional) self[signature] = var_info @property @@ -314,7 +329,8 @@ def merge(self, other_access_info): new_location, access_info.node, access_info. - component_indices) + component_indices, + access_info.conditional) # Increase the current location of this instance by the amount of # locations just merged in self._location = self._location + max_new_location @@ -367,6 +383,155 @@ def has_read_write(self, signature): var_access_info = self[signature] return var_access_info.has_read_write() + def set_conditional_accesses(self, if_branch, else_branch): + '''This function adds the accesses from `if_branch` and `else_branch`, + marking them as conditional if the accesses are already conditional, + or only happen in one of the two branches. While this function is + at the moment only used for if-statements, it can also be used for + e.g. loops by providing None as `else_branch` object. + + :param if_branch: the first branch. + :type if_branch: :py:class:`psyclone.psyir.nodes.Node` + :param else_branch: the second branch, which can be None. + :type else_branch: :py:class:`psyclone.psyir.nodes.Node` + + ''' + var_if = VariablesAccessInfo(if_branch, self.options()) + # Create an empty access info object in case that we do not have + # a second branch. + if else_branch: + var_else = VariablesAccessInfo(else_branch, self.options()) + else: + var_else = VariablesAccessInfo() + + # Get the list of all signatures in the if and else branch: + all_sigs = set(var_if.keys()) + all_sigs.update(set(var_else.keys())) + + for sig in all_sigs: + if sig not in var_if or sig not in var_else: + # Signature is only in one branch. Mark all existing accesses + # as conditional + var_access = var_if[sig] if sig in var_if else var_else[sig] + for access in var_access.all_accesses: + print("conditional 1", sig.to_language( + component_indices=access.component_indices)) + + access.conditional = True + continue + + # Now we have a signature that is accessed in both + # the if and else block. In case of array variables, we need to + # distinguish between different indices, e.g. a(i) might be + # written to unconditionally, but a(i+1) might be written + # conditionally. Additionally, we should support mathematically + # equivalent statements (e.g. a(i+1), and a(1+i)). + # As a first step, split all the accesses into equivalence + # classes. Each equivalent class stores two lists as a pair: the + # first one with the accesses from the if branch, the second with + # the accesses from the else branch. + equiv = {} + for access in var_if[sig].all_accesses: + for comp_access in equiv.keys(): + if access.component_indices.equal(comp_access): + equiv[comp_access][0].append(access) + break + else: + # New component index: + equiv[access.component_indices] = ([access], []) + # While we know that the signature is used in both branches, the + # accesses for a given equivalence class of indices could still + # be in only in one of them (e.g. + # if () then a(i)=1 else a(i+1)=2 endif). So it is still possible + # that we a new equivalence class in the second branch + for access in var_else[sig].all_accesses: + for comp_access in equiv.keys(): + if access.component_indices.equal(comp_access): + equiv[comp_access][1].append(access) + break + else: + # New component index: + equiv[access.component_indices] = ([], [access]) + + print("===============================") + # Now handle each equivalent set of component indices: + for comp_index in equiv.keys(): + # print("evaluating equivalence", sig.to_language( + # component_indices=comp_index)) + if_accesses, else_accesses = equiv[comp_index] + # If the access is not in both branches, it is conditional: + if not if_accesses or not else_accesses: + # Only accesses in one section, therefore conditional: + var_access = if_accesses if if_accesses else else_accesses + for access in var_access: + access.conditional = True + continue + + # Now we have accesses to the same indices in both branches. + # We still need to distinguish between read and write accesses. + # This can result in incorrect/unexpected results in some rare + # cases: + # if () + # call kernel(a(i)) ! Assume a(i) is READWRITE + # else + # b = a(i) + # endif + # Now the read access to a(i) is unconditional, but the write + # access to a(i) as part of the readwrite is conditional. But + # since there is only one accesses for the readwrite, we can't + # mark it as both conditional and unconditional + conditional_in_if = True + + for mode in [AccessType.READ, AccessType.WRITE]: + for access in if_accesses: + # Ignore read or write accesses depending on mode + if mode is AccessType.READ and not access.is_read: + continue + if mode is AccessType.WRITE and not access.is_written: + continue + if not access.conditional: + conditional_in_if = False + break + + overall_conditional = conditional_in_if + # If there is no conditional access in the if branch, there + # might still be one in the else branch, making the whole + # access conditional: + if not conditional_in_if: + # Assume that there is a conditional access in the else + # branch, unless we find an unconditional one + overall_conditional = True + for access in else_accesses: + # Ignore read or write accesses depending on mode + if mode is AccessType.READ and not access.is_read: + continue + if mode is AccessType.WRITE and \ + not access.is_written: + continue + if not access.conditional: + # We have an unconditional access, so know now + # that the access is unconditional: + overall_conditional = False + break + + # If the access to this equivalence class is conditional, + # mark all accesses as conditional: + for access in if_accesses + else_accesses: + # Ignore read or write accesses depending on mode + if mode is AccessType.READ and not access.is_read: + continue + if mode is AccessType.WRITE and \ + not access.is_written: + continue + access.conditional = overall_conditional + print(f"conditional" if overall_conditional + else "unconditional", + mode, + sig.to_language(component_indices=comp_index)) + print("-----------------------------") + self.merge(var_if) + self.merge(var_else) + # ---------- Documentation utils -------------------------------------------- # # The list of module members that we wish AutoAPI to generate diff --git a/src/psyclone/psyir/nodes/if_block.py b/src/psyclone/psyir/nodes/if_block.py index bba9bd9795..c2add4d5d2 100644 --- a/src/psyclone/psyir/nodes/if_block.py +++ b/src/psyclone/psyir/nodes/if_block.py @@ -195,9 +195,13 @@ def reference_accesses(self, var_accesses): # The first child is the if condition - all variables are read-only self.condition.reference_accesses(var_accesses) var_accesses.next_location() - self.if_body.reference_accesses(var_accesses) - var_accesses.next_location() - if self.else_body: - self.else_body.reference_accesses(var_accesses) + if not var_accesses.options("FLATTEN"): + self.if_body.reference_accesses(var_accesses) var_accesses.next_location() + if self.else_body: + self.else_body.reference_accesses(var_accesses) + var_accesses.next_location() + return + + var_accesses.set_conditional_accesses(self.if_body, self.else_body) diff --git a/src/psyclone/tests/core/access_type_test.py b/src/psyclone/tests/core/access_type_test.py index 4987568c9c..56be9a645a 100644 --- a/src/psyclone/tests/core/access_type_test.py +++ b/src/psyclone/tests/core/access_type_test.py @@ -36,7 +36,6 @@ '''This module tests AccessType.''' -from __future__ import absolute_import import pytest from psyclone.configuration import Config from psyclone.core.access_type import AccessType @@ -76,7 +75,8 @@ def test_api_specific_name(): assert set(AccessType.all_read_accesses()) == set([AccessType.READ, AccessType.READWRITE, AccessType.READINC, - AccessType.INC]) + AccessType.INC, + AccessType.SUM]) # Clean up the Config instance Config._instance = None @@ -116,7 +116,7 @@ def test_all_read_accesses(): all_read_accesses = AccessType.all_read_accesses() assert isinstance(all_read_accesses, list) - assert len(all_read_accesses) == 4 + assert len(all_read_accesses) == 5 assert (len(all_read_accesses) == len(set(all_read_accesses))) assert all(isinstance(read_access, AccessType) diff --git a/src/psyclone/tests/core/component_indices_test.py b/src/psyclone/tests/core/component_indices_test.py index ed8d3355eb..1166d13e67 100644 --- a/src/psyclone/tests/core/component_indices_test.py +++ b/src/psyclone/tests/core/component_indices_test.py @@ -35,11 +35,11 @@ '''This module tests the ComponentIndices class in psyclone/core.''' -from __future__ import absolute_import import pytest -from psyclone.core import ComponentIndices, VariablesAccessInfo +from psyclone.core import ComponentIndices, Signature, VariablesAccessInfo from psyclone.errors import InternalError +from psyclone.psyir.nodes import Assignment def test_component_indices(): @@ -169,3 +169,93 @@ def test_get_subscripts_of(expression, correct, fortran_reader): access = access_info[sig][0] result = access.component_indices.get_subscripts_of(loop_vars) assert result == correct + + +# ----------------------------------------------------------------------------- +@pytest.mark.parametrize("index_1, index_2, result", [("i", "i", True), + ("i", "i-1", False), + ("i", "2*i+1-i-1", True), + ("i", "2*i+1-i", False), + ("i,j", "i,j", True), + ("i,2*j-1", + "j+2*i+1-j-1-i, 2*j-1", + True), + ("i,j", "j,i", False), + ("i,j", "i", False), + ]) +def test_component_indices_equal(index_1, index_2, result, fortran_reader): + '''Tests that comparing component indices works as expected, taking + symbolic equality into account. Also test if some accesses should + have different number of indices, e.g.: a(i,j) vs b(i), since the + component indices are independent of the symbol. In this test, + we use the same variable name (e.g. a(i,j) and a(i)), which is not + semantically correct, but it creates the right component indices + for this test. + ''' + # Check if we need 1D or 2D arrays: + if "," in index_1: + declaration = "n,n" + else: + declaration = "n" + source = f'''program test + use my_mod, only: my_type + type(my_type) :: dv(10) + integer i, j, k, l + integer, parameter :: n=10 + real, dimension({declaration}) :: a1, a2 + a1({index_1}) = a2({index_2}) + end program test''' + psyir = fortran_reader.psyir_from_source(source) + assign = psyir.walk(Assignment) + + # Get all access info for the expression + access_info = VariablesAccessInfo(assign) + comp_index_1 = access_info[Signature("a1")][0].component_indices + comp_index_2 = access_info[Signature("a2")][0].component_indices + assert comp_index_1.equal(comp_index_2) == result + + +# ----------------------------------------------------------------------------- +@pytest.mark.parametrize("var1, var2, result", [("a(i)%b(j)", + "a(2*i-i)%b(1+j-2+1)", True), + ("a(i)%b", + "a(i)%b(j)", False), + ("a(i)%b", + "a(i)%b(i, j)", False), + ("a(i)%b(i)", + "a(i)%b(i, j)", False), + ("a(i)%b(i, j, k)", + "a(i)%b(i, j)", False), + ("a(i)%b(i)%c", + "a(i)%b%c(i)", False), + ("a(i)%b", + "a(i)%b(j)%c(k)", False), + ]) +def test_component_indices_equal_derived(var1, var2, result, fortran_reader): + '''Tests that comparing component indices of derived types work as + expected. Besides using symbolic maths, it tests if different level of + members (a%b vs a%b%c) are used. + ''' + source = f'''program test + use my_mod, only: my_type + type(my_type), dimension(10) :: a, b, c + {var1} = {var2} + end program test''' + psyir = fortran_reader.psyir_from_source(source) + assignment = psyir.walk(Assignment)[0] + + # Get all access info for the expression + access_info_1 = VariablesAccessInfo(assignment.lhs) + access_info_2 = VariablesAccessInfo(assignment.rhs) + if var1.count("%") == 1: + comp_index_1 = access_info_1[Signature("a%b")][0].component_indices + else: + comp_index_1 = access_info_1[Signature("a%b%c")][0].component_indices + if var2.count("%") == 1: + comp_index_2 = access_info_2[Signature("a%b")][0].component_indices + else: + # Support the tests with a%b%c + comp_index_2 = access_info_2[Signature("a%b%c")][0].component_indices + + assert comp_index_1.equal(comp_index_2) == result + assert comp_index_2.equal(comp_index_1) == result diff --git a/src/psyclone/tests/core/single_variable_access_info_test.py b/src/psyclone/tests/core/single_variable_access_info_test.py index 7a45ab524b..f3dc500653 100644 --- a/src/psyclone/tests/core/single_variable_access_info_test.py +++ b/src/psyclone/tests/core/single_variable_access_info_test.py @@ -55,6 +55,9 @@ def test_access_info(): assert access_info.access_type == AccessType.READ assert access_info.location == location assert access_info.component_indices.indices_lists == [[]] + assert access_info.is_read + assert not access_info.is_written + assert not access_info.conditional assert not access_info.is_array() assert str(access_info) == "READ(12)" access_info.change_read_to_write() @@ -89,6 +92,51 @@ def test_access_info(): assert access_info.component_indices.indices_lists == [["i", "j"]] +@pytest.mark.parametrize("mode", [AccessType.READ, + AccessType.READWRITE, + AccessType.INC, + AccessType.READINC, + AccessType.SUM]) +def test_access_info_is_read(mode): + '''Test the convenient functions is_read, esp. if readwrite and + increment accesses are returned as being a read access + ''' + location = 12 + access_info = AccessInfo(mode, location, Node()) + assert access_info.is_read + + +@pytest.mark.parametrize("mode", [AccessType.WRITE, + AccessType.READWRITE, + AccessType.INC, + AccessType.READINC, + AccessType.SUM]) +def test_access_info_is_written(mode): + '''Test the convenient functions is_read/is_written. + ''' + location = 12 + access_info = AccessInfo(mode, location, Node()) + assert access_info.is_written + + +def test_access_info_conditional(): + '''Test the handling of conditional accesses + ''' + location = 12 + access_info = AccessInfo(AccessType.READ, location, Node(), + conditional=True) + assert access_info.conditional + assert str(access_info) == "%READ(12)" + access_info.conditional = False + assert not access_info.conditional + assert str(access_info) == "READ(12)" + + access_info = AccessInfo(AccessType.WRITE, location, Node(), + conditional=True) + assert access_info.conditional + assert str(access_info) == "%WRITE(12)" + + # ----------------------------------------------------------------------------- def test_access_info_exceptions(): '''Test that the right exceptions are raised. diff --git a/src/psyclone/tests/core/variables_access_info_test.py b/src/psyclone/tests/core/variables_access_info_test.py index 6d58effb07..7b251ddf45 100644 --- a/src/psyclone/tests/core/variables_access_info_test.py +++ b/src/psyclone/tests/core/variables_access_info_test.py @@ -41,7 +41,7 @@ from psyclone.core import ComponentIndices, Signature, VariablesAccessInfo from psyclone.core.access_type import AccessType from psyclone.errors import InternalError -from psyclone.psyir.nodes import Assignment, Node +from psyclone.psyir.nodes import Assignment, IfBlock, Node from psyclone.tests.utilities import get_invoke @@ -395,18 +395,20 @@ def test_variables_access_info_options(): assert vai.options("COLLECT-ARRAY-SHAPE-READS") is True assert vai.options("USE-ORIGINAL-NAMES") is False assert vai.options() == {"COLLECT-ARRAY-SHAPE-READS": True, + "FLATTEN": False, "USE-ORIGINAL-NAMES": False} vai = VariablesAccessInfo(options={'USE-ORIGINAL-NAMES': True}) assert vai.options("COLLECT-ARRAY-SHAPE-READS") is False assert vai.options("USE-ORIGINAL-NAMES") is True assert vai.options() == {"COLLECT-ARRAY-SHAPE-READS": False, + "FLATTEN": False, "USE-ORIGINAL-NAMES": True} with pytest.raises(InternalError) as err: vai.options("invalid") assert ("Option key 'invalid' is invalid, it must be one of " - "['COLLECT-ARRAY-SHAPE-READS', 'USE-ORIGINAL-NAMES']." + "['COLLECT-ARRAY-SHAPE-READS', 'FLATTEN', 'USE-ORIGINAL-NAMES']." in str(err.value)) @@ -477,3 +479,88 @@ def test_lfric_access_info(): "READ, np_xy_qr: READ, np_z_qr: READ, undf_w1: READ, undf_w2: " "READ, undf_w3: READ, weights_xy_qr: READ, weights_z_qr: READ" == str(vai)) + + +# ----------------------------------------------------------------------------- +def test_variables_access_info_flatten(fortran_reader): + '''Test that flatten works as expected. + ''' + code = '''module test + contains + subroutine tmp() + integer :: cond_var + integer :: write_if, write_else, write_if_else + integer :: read_if, read_else, read_if_else + if (cond_var .eq. 1) then + write_if = read_if + write_if_else = read_if_else + else + write_else = read_else + write_if_else = read_if_else + endif + + end subroutine tmp + end module test''' + psyir = fortran_reader.psyir_from_source(code) + node1 = psyir.walk(IfBlock)[0] + + # By default, array shape accesses are not reads. + vai = VariablesAccessInfo(node1, options={"FLATTEN": True}) + + print(vai) + + +# ----------------------------------------------------------------------------- +def test_variables_access_info_array_conditional(fortran_reader): + '''Test that flatten works as expected. + ''' + code = '''module test + contains + subroutine tmp(i) + integer :: cond_var, i + integer, dimension(10) :: write_if, write_else, write_if_else + integer, dimension(10) :: read_if, read_else, read_if_else + if (cond_var .ne. 1) then + if (cond_var .eq. 2) then + write_if(i) = read_if(i) + write_if_else(i+1) = read_if_else(i) + endif + write_if_else(i+1) = read_if_else(1+i-1) + else + write_if(i) = read_if(i) + write_else(i) = read_else(i) + write_if_else(i) = read_if_else(i) + write_if_else(i+1) = read_if_else(i) + endif + + end subroutine tmp + end module test''' + psyir = fortran_reader.psyir_from_source(code) + node1 = psyir.walk(IfBlock)[0] + + # By default, array shape accesses are not reads. + vai = VariablesAccessInfo(node1, options={"FLATTEN": True}) + + print(vai) + + code = '''module test + contains + subroutine tmp(i) + integer :: cond_var, i + integer, dimension(10) :: array, my_val + do i = 1, 10 + if (array(i) > 3) then + my_val(1) = 1 + array(i) = my_val(1) + else + array(i) = my_val(1) + endif + end do + + end subroutine tmp + end module test''' + psyir = fortran_reader.psyir_from_source(code) + node1 = psyir.walk(IfBlock)[0] + + # By default, array shape accesses are not reads. + vai = VariablesAccessInfo(node1, options={"FLATTEN": True}) diff --git a/src/psyclone/tests/dynamo0p3_test.py b/src/psyclone/tests/dynamo0p3_test.py index 76721031e2..2b53a673e4 100644 --- a/src/psyclone/tests/dynamo0p3_test.py +++ b/src/psyclone/tests/dynamo0p3_test.py @@ -3399,8 +3399,8 @@ def test_HaloReadAccess_field_not_reader(): _ = HaloReadAccess(argument, None) assert ( "In HaloInfo class, field 'f1' should be one of ['gh_read', " - "'gh_readwrite', 'gh_inc', 'gh_readinc'], but found 'gh_write'" - in str(excinfo.value)) + "'gh_readwrite', 'gh_inc', 'gh_readinc', 'gh_sum'], but found " + "'gh_write'" in str(excinfo.value)) def test_HaloRead_inv_loop_upper(monkeypatch):