Skip to content
2 changes: 1 addition & 1 deletion src/psyclone/core/access_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,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():
Expand Down
32 changes: 29 additions & 3 deletions src/psyclone/core/component_indices.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down
61 changes: 57 additions & 4 deletions src/psyclone/core/single_variable_access_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -174,6 +179,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():
Expand Down Expand Up @@ -232,6 +267,22 @@ def is_written(self) -> bool:
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) -> bool:
'''
:returns: True if this variable is written in the first data access
Expand Down Expand Up @@ -316,7 +367,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, ....)
Expand All @@ -330,9 +382,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.
Expand Down
171 changes: 165 additions & 6 deletions src/psyclone/core/variables_access_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

from typing import List

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
Expand Down Expand Up @@ -82,7 +83,9 @@ class VariablesAccessInfo(dict):
# 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 = {"USE-ORIGINAL-NAMES": False}

_DEFAULT_OPTIONS = {"USE-ORIGINAL-NAMES": False,
"FLATTEN": False}

def __init__(self, nodes=None, options=None):
# This dictionary stores the mapping of signatures to the
Expand Down Expand Up @@ -156,7 +159,9 @@ def __str__(self):
else:
# The data associated with this signature is not accessed.
mode = "NO_DATA_ACCESS"
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):
Expand Down Expand Up @@ -200,7 +205,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
Expand Down Expand Up @@ -263,11 +270,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
Expand Down Expand Up @@ -323,7 +332,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
Expand Down Expand Up @@ -383,6 +393,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("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
Expand Down
Loading