From e8dd4c79dae970045b9db9c91d63d03af40c041b Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Mon, 3 Feb 2025 11:18:40 +1100 Subject: [PATCH] #2878 Add some support for dependency analysis involving array ranges. --- src/psyclone/psyir/tools/dependency_tools.py | 49 +++++++++++++- .../psyir/tools/dependency_tools_test.py | 66 ++++++++++++++++++- 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyir/tools/dependency_tools.py b/src/psyclone/psyir/tools/dependency_tools.py index e29e4b87a0..c0380d2814 100644 --- a/src/psyclone/psyir/tools/dependency_tools.py +++ b/src/psyclone/psyir/tools/dependency_tools.py @@ -48,7 +48,7 @@ from psyclone.errors import InternalError, LazyString from psyclone.psyir.backend.sympy_writer import SymPyWriter from psyclone.psyir.backend.visitor import VisitorError -from psyclone.psyir.nodes import Loop +from psyclone.psyir.nodes import Loop, Node, Range # pylint: disable=too-many-lines @@ -281,6 +281,50 @@ def _partition(comp_ind1, comp_ind2, loop_variables): return partition_infos + # ------------------------------------------------------------------------- + @staticmethod + def _ranges_overlap(range1: Node, + range2: Node) -> bool: + '''This function tests if two ranges overlap. It also accepts a simple + index as 'range' (e.g. just `i`), which will be converted into `i:i:1` + before comparing. At this stage, this function simple checks if one of + the ranges starts after the other (e.g. 1:3, and 5:7). It will handle + unspecified ranges (":"), and will report an overlap. + Additional tests e.g. using the step value are not yet implemented + (e.g. 1:10:2 and 2:10:2 will not overlap, but this will not be + detected atm). + + :param range1: The first range or expression. + :param range2: The second range or expression. + + :returns: whether the ranges (or an index expression with a range) + overlap or not + + ''' + if not isinstance(range1, Range): + # Not a range, must be some index `i`. Create a range `i:i:1` + range1 = Range.create(range1.copy(), range1.copy()) + if not isinstance(range2, Range): + # Not a range, must be some index `i`. Create a range `i:i:1` + range2 = Range.create(range2.copy(), range2.copy()) + + sm = SymbolicMaths.get() + + # Check if the first range is smaller than the second one, e.g.: + # 1:3:1 and 4:6:1 + if sm.greater_than(range2.start, range1.stop) == sm.Fuzzy.TRUE: + # The first range is before the second range, so no overlap + return False + # Check if the second range is smaller than the first one, e.g.: + # 4:6:1 and 1:3:1 + if sm.greater_than(range1.start, range2.stop) == sm.Fuzzy.TRUE: + # The second range is before the first range, so no overlap + return False + + # We could do additional tests here, e.g. including step to determine + # that 1:10:2 does not overlap with 2:10:2 + return True + # ------------------------------------------------------------------------- @staticmethod def _independent_0_var(index_exp1, index_exp2): @@ -296,6 +340,9 @@ def _independent_0_var(index_exp1, index_exp2): :type index_exp2: :py:class:`psyclone.psyir.nodes.Node` ''' + if isinstance(index_exp1, Range) or isinstance(index_exp2, Range): + return not DependencyTools._ranges_overlap(index_exp1, index_exp2) + sym_maths = SymbolicMaths.get() # If the indices can be shown to be never equal, the accesses diff --git a/src/psyclone/tests/psyir/tools/dependency_tools_test.py b/src/psyclone/tests/psyir/tools/dependency_tools_test.py index 8c4e28995f..a0eff77f40 100644 --- a/src/psyclone/tests/psyir/tools/dependency_tools_test.py +++ b/src/psyclone/tests/psyir/tools/dependency_tools_test.py @@ -41,7 +41,7 @@ from psyclone.configuration import Config from psyclone.core import AccessType, Signature, VariablesAccessInfo from psyclone.errors import InternalError -from psyclone.psyir.nodes import Loop +from psyclone.psyir.nodes import Assignment, Loop from psyclone.psyir.tools import DependencyTools, DTCode from psyclone.tests.utilities import get_invoke @@ -1107,3 +1107,67 @@ def test_fuse_dimension_change(fortran_reader): "in different index locations: s%comp1(jj)%comp2(ji) and " "s%comp1(ji)%comp2(jj)." in str(msg)) + + +# ---------------------------------------------------------------------------- +@pytest.mark.parametrize("range1, range2, overlap", + [("1:3", "4:6", False), + ("3:9", "-1:-3", False), + ("1:3", "4", False), + ("5", "-1:-3", False), + ("i:i+3", "i+5:i+7", False), + ("i:i+3", "i+2", True), + ("i:i+3", "i+5", False), + ("i:i+3", "i-1", False), + (":", "1", True), + (":", "i", True), + ("::", "1", True), + ("::", "i", True), + ("1", ":", True), + ("i", ":", True), + ]) +def test_ranges_overlap(range1, range2, overlap, fortran_reader): + '''Test the detection of overlapping ranges. + ''' + source = f'''program test + integer i, ji, inbj + integer, parameter :: jpi=5, jpj=10 + real, dimension(jpi,jpi) :: ldisoce + + ldisoce({range1},{range2}) = 1.0 + end program test''' + + psyir = fortran_reader.psyir_from_source(source) + dep_tools = DependencyTools() + assign = psyir.walk(Assignment)[0] + r1 = assign.lhs.children[0] + r2 = assign.lhs.children[1] + assert dep_tools._ranges_overlap(r1, r2) == overlap + # Also make sure that _independent_0_var handles this correctly: + assert dep_tools._independent_0_var(r1, r2) is not overlap + + +# ---------------------------------------------------------------------------- +def test_nemo_example_ranges(fortran_reader): + '''Tests an actual NEMO example + ''' + source = '''program test + integer ji, inbj + integer, parameter :: jpi=5, jpj=10 + real, dimension(jpi,jpi) :: ldisoce + do jj = 1, inbj, 1 + if (COUNT(ldisoce(:,jj)) == 0) then + ldisoce(1,jj) = .true. + end if + enddo + end program test''' + + psyir = fortran_reader.psyir_from_source(source) + loops = psyir.children[0].children[0] + dep_tools = DependencyTools() + + # This loop can be parallelised because all instances of ldisoce use + # the index jj in position 2 (the overlap between ":" and "1" + # is tested in test_ranges_overlap above, here we check that this + # overlap is indeed ignored because of the jj index). + assert dep_tools.can_loop_be_parallelised(loops)