Skip to content

Commit

Permalink
#2878 Add some support for dependency analysis involving array ranges.
Browse files Browse the repository at this point in the history
  • Loading branch information
hiker committed Feb 3, 2025
1 parent 06e0257 commit e8dd4c7
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 2 deletions.
49 changes: 48 additions & 1 deletion src/psyclone/psyir/tools/dependency_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down
66 changes: 65 additions & 1 deletion src/psyclone/tests/psyir/tools/dependency_tools_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

0 comments on commit e8dd4c7

Please sign in to comment.