From 59d98750235eace4ba45cde199736aefeb820ec0 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:53:04 +0100 Subject: [PATCH 01/62] First code towards #2499 --- .../transformations/scalarization_trans.py | 188 ++++++++++++++++++ .../scalarization_trans_test.py | 79 ++++++++ 2 files changed, 267 insertions(+) create mode 100644 src/psyclone/psyir/transformations/scalarization_trans.py create mode 100644 src/psyclone/tests/psyir/transformations/scalarization_trans_test.py diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py new file mode 100644 index 0000000000..28d207440f --- /dev/null +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -0,0 +1,188 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2024, 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 provides the sclarization transformation class.''' + +import itertools + +from psyclone.core import AccessType, SymbolicMaths, VariablesAccessInfo, \ + Signature +from psyclone.domain.common.psylayer import PSyLoop +from psyclone.psyGen import Kern +from psyclone.psyir.nodes import Assignment, Call, CodeBlock, IfBlock,\ + Reference, Routine +from psyclone.psyir.symbols import DataSymbol +from psyclone.psyir.tools import DependencyTools +from psyclone.psyir.transformations.loop_trans import LoopTrans +from psyclone.psyir.transformations.transformation_error import \ + TransformationError, LazyString + + +class ScalarizationTrans(LoopTrans): + + #def validate(self, node, options=None): + + + def _find_potential_scalarizable_array_symbols(self, node, var_accesses): + + potential_arrays = [] + signatures = var_accesses.all_signatures + for signature in signatures: + # Skip over non-arrays + if not var_accesses[signature].is_array(): + continue + array_indices = None + scalarizable = True + for access in var_accesses[signature].all_accesses: + if array_indices is None: + array_indices = access.component_indices + # For some reason using == on the component_lists doesn't work + elif array_indices[:] != access.component_indices[:]: + scalarizable = False + break + # For each index, we need to check they're not written to in + # the loop. + flattened_indices = list(itertools.chain.from_iterable( + array_indices)) + for index in flattened_indices: + sig, _ = index.get_signature_and_indices() + if var_accesses[sig].is_written(): + scalarizable = False + break + if scalarizable: + potential_arrays.append(signature) + + return potential_arrays + + def _check_first_access_is_write(self, node, var_accesses, potentials): + potential_arrays = [] + + for signature in potentials: + if var_accesses[signature].is_written_first(): + potential_arrays.append(signature) + + return potential_arrays + + def _check_valid_following_access(self, node, var_accesses, potentials): + potential_arrays = [] + + for signature in potentials: + # Find the last access of each signature + last_access = var_accesses[signature].all_accesses[-1].node + # Find the next access to this symbol + next_access = last_access.next_access() + # If we don't use this again then its valid + if next_access is None: + potential_arrays.append(signature) + continue + # If we do and the next_access has an ancestor IfBlock + # that isn't an ancestor of the loop then its not valid since + # we aren't tracking down what the condition-dependent next + # use really is. + if_ancestor = next_access.ancestor(IfBlock) + + # If abs_position of if_ancestor is > node.abs_position + # its not an ancestor of us. + if (if_ancestor is not None and + if_ancestor.abs_position > node.abs_position): + # Not a valid next_access pattern. + continue + + # If next access is the LHS of an assignment, we need to + # check that it doesn't also appear on the RHS. If so its + # not a valid access + if (isinstance(next_access.parent, Assignment) and + next_access.parent.lhs is next_access and + (next_access.next_access().ancestor(Assignment) is + next_access.parent)): + continue + + # If its a read Reference then we can't use it too, or if its + # ancestor is a CodeBlock or Call or Kern? + if (next_access.ancestor((Assignment, CodeBlock, Call, Kern)) + is not None): + continue + + potential_arrays.append(signature) + + + return potential_arrays + + def apply(self, node, options=None): + '''TODO Docs''' + # For each array reference in the Loop: + # Find every access to the same symbol in the loop + # They all have to be accessed with the same index statement, and + # that index needs to not be written to inside the loop body. + # For each symbol that meets this criteria, we then need to check the + # first access is a write + # Then, for each symbol still meeting this criteria, we need to find + # the next access outside of this loop. If its inside an ifblock that + # is not an ancestor of this loop then we refuse to scalarize for + # simplicity. Otherwise if its a read we can't scalarize safely. + # If its a write then this symbol can be scalarized. + + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + + # Find all the ararys that are only accessed by a single index, and + # that index is only read inside the loop. + potential_targets = self._find_potential_scalarizable_array_symbols( + node, var_accesses) + + # Now we need to check the first access is a write and remove those + # that aren't. + potential_targets = self._check_first_access_is_write( + node, var_accesses, potential_targets) + + # Check the values written to these arrays are not used after this loop. + finalised_targets = self._check_valid_following_access( + node, var_accesses, potential_targets) + + routine_table = node.ancestor(Routine).symbol_table + # For each finalised target we can replace them with a scalarized + # symbol + for target in finalised_targets: + target_accesses = var_accesses[target].all_accesses + first_access = target_accesses[0].node + symbol_type = first_access.symbol.datatype.datatype + symbol_name = first_access.symbol.name + scalar_symbol = routine_table.new_symbol( + root_name=f"{symbol_name}_scalar", + symbol_type=DataSymbol, + datatype=symbol_type) + ref_to_copy = Reference(scalar_symbol) + for access in target_accesses: + node = access.node + node.replace_with(ref_to_copy.copy()) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py new file mode 100644 index 0000000000..0b2677c613 --- /dev/null +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -0,0 +1,79 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2022-2024, 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 tests the scalarization transformation. +''' + +import os +import pytest + +from psyclone.configuration import Config +from psyclone.errors import InternalError +from psyclone.psyir.nodes import Call, IntrinsicCall, Reference, Routine, Loop +from psyclone.psyir.symbols import ( + ArgumentInterface, AutomaticInterface, DataSymbol, INTEGER_TYPE, + RoutineSymbol, SymbolTable, UnresolvedType) +from psyclone.psyir.transformations import ( + ScalarizationTrans, TransformationError) +from psyclone.tests.utilities import Compile + + +def test_scal_quick(fortran_reader, fortran_writer): + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + + do i = 1, 100 + arr(i) = i + arr(i) = exp(arr(i)) + k = i + b(i) = arr(i) * 3 + c(k) = i + end do + do i = 1, 100 + b(i) = b(i) + 1 + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.children[0].children[0] + strans.apply(loop) + print(fortran_writer(psyir)) + assert False From ede95c74135c438eb8abc7dc7419cd0dd085f57b Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:55:06 +0100 Subject: [PATCH 02/62] forgotten file --- src/psyclone/psyir/transformations/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/psyclone/psyir/transformations/__init__.py b/src/psyclone/psyir/transformations/__init__.py index 4f757e4581..d6d9808c13 100644 --- a/src/psyclone/psyir/transformations/__init__.py +++ b/src/psyclone/psyir/transformations/__init__.py @@ -103,6 +103,8 @@ ReplaceInductionVariablesTrans from psyclone.psyir.transformations.reference2arrayrange_trans import \ Reference2ArrayRangeTrans +from psyclone.psyir.transformations.scalarization_trans import \ + ScalarizationTrans # For AutoAPI documentation generation @@ -143,4 +145,5 @@ 'Reference2ArrayRangeTrans', 'RegionTrans', 'ReplaceInductionVariablesTrans', + 'ScalarizationTrans', 'TransformationError'] From 6ae3ae31000fe72ec3e284a0ab0e8a84187de810 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:51:15 +0100 Subject: [PATCH 03/62] First implementation of scalarization_trans and unit tests for the private functions --- .../transformations/scalarization_trans.py | 36 +-- .../scalarization_trans_test.py | 258 +++++++++++++++++- 2 files changed, 258 insertions(+), 36 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 28d207440f..f473468adb 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -37,24 +37,16 @@ import itertools -from psyclone.core import AccessType, SymbolicMaths, VariablesAccessInfo, \ - Signature -from psyclone.domain.common.psylayer import PSyLoop +from psyclone.core import VariablesAccessInfo from psyclone.psyGen import Kern -from psyclone.psyir.nodes import Assignment, Call, CodeBlock, IfBlock,\ +from psyclone.psyir.nodes import Assignment, Call, CodeBlock, IfBlock, \ Reference, Routine from psyclone.psyir.symbols import DataSymbol -from psyclone.psyir.tools import DependencyTools from psyclone.psyir.transformations.loop_trans import LoopTrans -from psyclone.psyir.transformations.transformation_error import \ - TransformationError, LazyString class ScalarizationTrans(LoopTrans): - #def validate(self, node, options=None): - - def _find_potential_scalarizable_array_symbols(self, node, var_accesses): potential_arrays = [] @@ -99,6 +91,7 @@ def _check_valid_following_access(self, node, var_accesses, potentials): potential_arrays = [] for signature in potentials: + print(signature) # Find the last access of each signature last_access = var_accesses[signature].all_accesses[-1].node # Find the next access to this symbol @@ -115,31 +108,32 @@ def _check_valid_following_access(self, node, var_accesses, potentials): # If abs_position of if_ancestor is > node.abs_position # its not an ancestor of us. - if (if_ancestor is not None and - if_ancestor.abs_position > node.abs_position): + if (if_ancestor is not None and + if_ancestor.abs_position > node.abs_position): # Not a valid next_access pattern. continue # If next access is the LHS of an assignment, we need to # check that it doesn't also appear on the RHS. If so its # not a valid access + # I'm not sure this code is reachable if (isinstance(next_access.parent, Assignment) and next_access.parent.lhs is next_access and - (next_access.next_access().ancestor(Assignment) is + (next_access.next_access() is not None and + next_access.next_access().ancestor(Assignment) is next_access.parent)): - continue + continue - # If its a read Reference then we can't use it too, or if its - # ancestor is a CodeBlock or Call or Kern? - if (next_access.ancestor((Assignment, CodeBlock, Call, Kern)) + # If it has an ancestor that is a CodeBlock or Call or Kern + # then we can't guarantee anything, so we remove it. + if (next_access.ancestor((CodeBlock, Call, Kern)) is not None): continue - - potential_arrays.append(signature) + potential_arrays.append(signature) return potential_arrays - + def apply(self, node, options=None): '''TODO Docs''' # For each array reference in the Loop: @@ -166,7 +160,7 @@ def apply(self, node, options=None): potential_targets = self._check_first_access_is_write( node, var_accesses, potential_targets) - # Check the values written to these arrays are not used after this loop. + # Check the values written to these arrays are not used after this loop finalised_targets = self._check_valid_following_access( node, var_accesses, potential_targets) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 0b2677c613..806bef5c68 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -39,18 +39,14 @@ import os import pytest -from psyclone.configuration import Config -from psyclone.errors import InternalError -from psyclone.psyir.nodes import Call, IntrinsicCall, Reference, Routine, Loop -from psyclone.psyir.symbols import ( - ArgumentInterface, AutomaticInterface, DataSymbol, INTEGER_TYPE, - RoutineSymbol, SymbolTable, UnresolvedType) -from psyclone.psyir.transformations import ( - ScalarizationTrans, TransformationError) +from psyclone.core import VariablesAccessInfo +from psyclone.psyir.transformations import ScalarizationTrans from psyclone.tests.utilities import Compile -def test_scal_quick(fortran_reader, fortran_writer): +def test_scalarizationtrans_potential_array_symbols(fortran_reader): + ''' Test the possible code paths in the + _find_potential_scalarizable_array_symbols function.''' code = '''subroutine test() integer :: i integer :: k @@ -60,10 +56,119 @@ def test_scal_quick(fortran_reader, fortran_writer): do i = 1, 100 arr(i) = i + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potential_targets = strans._find_potential_scalarizable_array_symbols( + node, var_accesses) + assert len(potential_targets) == 1 + assert potential_targets[0].var_name == "arr" + + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + + do i = 1, 99 + k = i + 1 + arr(k) = i + end do + end subroutine + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potential_targets = strans._find_potential_scalarizable_array_symbols( + node, var_accesses) + assert len(potential_targets) == 0 + + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + k = 3 + do i = 1, 99 + arr(i) = i + 1 + arr(k) = arr(i) + 1 + end do + end subroutine + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[1] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potential_targets = strans._find_potential_scalarizable_array_symbols( + node, var_accesses) + assert len(potential_targets) == 0 + + +def test_scalarization_first_access_is_write(fortran_reader): + ''' Test the scalarization transformation's + _check_first_access_is_write function.''' + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + + do i = 1, 100 + arr(i) = i + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potentials = [var_accesses.all_signatures[0]] + potential_targets = strans._check_first_access_is_write( + node, var_accesses, potentials) + + assert len(potential_targets) == 1 + assert potential_targets[0].var_name == "arr" + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + + do i = 1, 100 + arr(i) = arr(i) + 1 + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potentials = [var_accesses.all_signatures[0]] + potential_targets = strans._check_first_access_is_write( + node, var_accesses, potentials) + + assert len(potential_targets) == 0 + + +def test_scalarization_trans_check_valid_following_access(fortran_reader): + ''' Test the scalarization transformation's + _check_valid_following_access function.''' + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + + do i = 1, 100 arr(i) = exp(arr(i)) - k = i b(i) = arr(i) * 3 - c(k) = i end do do i = 1, 100 b(i) = b(i) + 1 @@ -72,8 +177,131 @@ def test_scal_quick(fortran_reader, fortran_writer): ''' strans = ScalarizationTrans() psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + # Only arr makes it through the 2 prior stages + potentials = [var_accesses.all_signatures[0]] + potential_targets = strans._check_valid_following_access( + node, var_accesses, potentials) + assert len(potential_targets) == 1 + assert potential_targets[0].var_name == "arr" + + # Test we ignore array next_access if they're in an if statement + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + logical :: x = .FALSE. + + do i = 1, 100 + arr(i) = exp(arr(i)) + b(i) = arr(i) * 3 + end do + if(x) then + do i = 1, 100 + b(i) = 1 + end do + end if + end subroutine test + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potentials = [var_accesses.all_signatures[0], + var_accesses.all_signatures[1]] + potential_targets = strans._check_valid_following_access( + node, var_accesses, potentials) + assert len(potential_targets) == 1 + assert potential_targets[0].var_name == "arr" + # Test we don't ignore array next_access if they're in an if statement + # that is an ancestor of the loop we're scalarizing + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + + if(.false.) then + do i = 1, 100 + arr(i) = exp(arr(i)) + b(i) = arr(i) * 3 + end do + do i = 1, 100 + b(i) = 1 + end do + end if + end subroutine test + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0].if_body.children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potentials = [var_accesses.all_signatures[0], + var_accesses.all_signatures[1]] + potential_targets = strans._check_valid_following_access( + node, var_accesses, potentials) + assert len(potential_targets) == 2 + assert potential_targets[0].var_name == "arr" + assert potential_targets[1].var_name == "b" + + # Test we don't ignore array next_access if they have an ancestor + # that is a Call + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + + if(.false.) then + do i = 1, 100 + arr(i) = exp(arr(i)) + b(i) = arr(i) * 3 + end do + do i = 1, 100 + Call some_func(b(i)) + end do + end if + end subroutine test + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0].if_body.children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potentials = [var_accesses.all_signatures[0], + var_accesses.all_signatures[1]] + potential_targets = strans._check_valid_following_access( + node, var_accesses, potentials) + assert len(potential_targets) == 1 + assert potential_targets[0].var_name == "arr" - loop = psyir.children[0].children[0] - strans.apply(loop) - print(fortran_writer(psyir)) - assert False + +#def test_scal_quick(fortran_reader, fortran_writer): +# code = '''subroutine test() +# integer :: i +# integer :: k +# integer, dimension(1:100) :: arr +# integer, dimension(1:100) :: b +# integer, dimension(1:100) :: c +# +# do i = 1, 100 +# arr(i) = i +# arr(i) = exp(arr(i)) +# k = i +# b(i) = arr(i) * 3 +# c(k) = i +# end do +# do i = 1, 100 +# b(i) = b(i) + 1 +# end do +# end subroutine +# ''' +# strans = ScalarizationTrans() +# psyir = fortran_reader.psyir_from_source(code) +# +# loop = psyir.children[0].children[0] +# strans.apply(loop) +# print(fortran_writer(psyir)) +# assert False From c7cf88cae4acf2dfa6f9051c1b4f81074a600fb4 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:56:19 +0100 Subject: [PATCH 04/62] Linting errors --- .../scalarization_trans_test.py | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 806bef5c68..f967079b3a 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -36,16 +36,13 @@ '''This module tests the scalarization transformation. ''' -import os -import pytest - from psyclone.core import VariablesAccessInfo from psyclone.psyir.transformations import ScalarizationTrans -from psyclone.tests.utilities import Compile +# from psyclone.tests.utilities import Compile def test_scalarizationtrans_potential_array_symbols(fortran_reader): - ''' Test the possible code paths in the + ''' Test the possible code paths in the _find_potential_scalarizable_array_symbols function.''' code = '''subroutine test() integer :: i @@ -278,30 +275,30 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): assert potential_targets[0].var_name == "arr" -#def test_scal_quick(fortran_reader, fortran_writer): -# code = '''subroutine test() -# integer :: i -# integer :: k -# integer, dimension(1:100) :: arr -# integer, dimension(1:100) :: b -# integer, dimension(1:100) :: c -# -# do i = 1, 100 -# arr(i) = i -# arr(i) = exp(arr(i)) -# k = i -# b(i) = arr(i) * 3 -# c(k) = i -# end do -# do i = 1, 100 -# b(i) = b(i) + 1 -# end do -# end subroutine -# ''' -# strans = ScalarizationTrans() -# psyir = fortran_reader.psyir_from_source(code) -# -# loop = psyir.children[0].children[0] -# strans.apply(loop) -# print(fortran_writer(psyir)) -# assert False +# def test_scal_quick(fortran_reader, fortran_writer): +# code = '''subroutine test() +# integer :: i +# integer :: k +# integer, dimension(1:100) :: arr +# integer, dimension(1:100) :: b +# integer, dimension(1:100) :: c +# +# do i = 1, 100 +# arr(i) = i +# arr(i) = exp(arr(i)) +# k = i +# b(i) = arr(i) * 3 +# c(k) = i +# end do +# do i = 1, 100 +# b(i) = b(i) + 1 +# end do +# end subroutine +# ''' +# strans = ScalarizationTrans() +# psyir = fortran_reader.psyir_from_source(code) +# +# loop = psyir.children[0].children[0] +# strans.apply(loop) +# print(fortran_writer(psyir)) +# assert False From 577d7029fa2df3c59a6f5b797b094612afdcbd30 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Tue, 30 Apr 2024 15:59:47 +0100 Subject: [PATCH 05/62] Linting errors --- .../tests/psyir/transformations/scalarization_trans_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index f967079b3a..f54783f79a 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -282,7 +282,7 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): # integer, dimension(1:100) :: arr # integer, dimension(1:100) :: b # integer, dimension(1:100) :: c -# +# # do i = 1, 100 # arr(i) = i # arr(i) = exp(arr(i)) @@ -297,7 +297,7 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): # ''' # strans = ScalarizationTrans() # psyir = fortran_reader.psyir_from_source(code) -# +# # loop = psyir.children[0].children[0] # strans.apply(loop) # print(fortran_writer(psyir)) From d76bfb66e502f9d720c1b1cb7fb98b4d168080a6 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 1 May 2024 13:46:58 +0100 Subject: [PATCH 06/62] Final commits ready for a review --- .../transformations/scalarization_trans.py | 44 +++++++-- .../scalarization_trans_test.py | 97 +++++++++++++------ 2 files changed, 107 insertions(+), 34 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index f473468adb..60d15b5c42 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -55,6 +55,10 @@ def _find_potential_scalarizable_array_symbols(self, node, var_accesses): # Skip over non-arrays if not var_accesses[signature].is_array(): continue + # Skip over non-local symbols + base_symbol = var_accesses[signature].all_accesses[0].node.symbol + if not base_symbol.is_automatic: + continue array_indices = None scalarizable = True for access in var_accesses[signature].all_accesses: @@ -91,7 +95,6 @@ def _check_valid_following_access(self, node, var_accesses, potentials): potential_arrays = [] for signature in potentials: - print(signature) # Find the last access of each signature last_access = var_accesses[signature].all_accesses[-1].node # Find the next access to this symbol @@ -117,11 +120,18 @@ def _check_valid_following_access(self, node, var_accesses, potentials): # check that it doesn't also appear on the RHS. If so its # not a valid access # I'm not sure this code is reachable - if (isinstance(next_access.parent, Assignment) and - next_access.parent.lhs is next_access and - (next_access.next_access() is not None and - next_access.next_access().ancestor(Assignment) is - next_access.parent)): +# if (isinstance(next_access.parent, Assignment) and +# next_access.parent.lhs is next_access and +# (next_access.next_access() is not None and +# next_access.next_access().ancestor(Assignment) is +# next_access.parent)): +# continue + + # If next access is the RHS of an assignment then we need to + # skip it + ancestor_assign = next_access.ancestor(Assignment) + if (ancestor_assign is not None and + ancestor_assign.lhs is not next_access): continue # If it has an ancestor that is a CodeBlock or Call or Kern @@ -135,7 +145,27 @@ def _check_valid_following_access(self, node, var_accesses, potentials): return potential_arrays def apply(self, node, options=None): - '''TODO Docs''' + '''Apply the scalarization transformation to a loop. + All of the array accesses that are identified as being able to be + scalarized will be transformed by this transformation. + + An array access will be scalarized if: + 1. All accesses to the array use the same indexing statement. + 2. All References contained in the indexing statement are not modified + inside of the loop (loop variables are ok). + 3. The array symbol is either not accessed again or is written to + as its next access. If the next access is inside a conditional + that is not an ancestor of the input loop, then PSyclone will + assume that we cannot scalarize that value instead of attempting to + understand the control flow. + 4. TODO - The array symbol is a local variable. + + :param node: the supplied loop to apply scalarization to. + :type node: :py:class:`psyclone.psyir.nodes.Loop` + :param options: a dictionary with options for transformations. + :type options: Optional[Dict[str, Any]] + + ''' # For each array reference in the Loop: # Find every access to the same symbol in the loop # They all have to be accessed with the same index statement, and diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index f54783f79a..0e71c8cfaf 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -105,6 +105,30 @@ def test_scalarizationtrans_potential_array_symbols(fortran_reader): node, var_accesses) assert len(potential_targets) == 0 + # Ensure that we don't access imports or arguments or unknowns + # for scalarization + # Not sure if we should expand this for anything else? + code = '''subroutine test(b) + use mymod, only: arr + integer :: i + integer :: k + integer, dimension(1:100) :: b + + do i = 1, 100 + arr(i) = i + b(i) = i + c(i) = i + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + potential_targets = strans._find_potential_scalarizable_array_symbols( + node, var_accesses) + assert len(potential_targets) == 0 + def test_scalarization_first_access_is_write(fortran_reader): ''' Test the scalarization transformation's @@ -275,30 +299,49 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): assert potential_targets[0].var_name == "arr" -# def test_scal_quick(fortran_reader, fortran_writer): -# code = '''subroutine test() -# integer :: i -# integer :: k -# integer, dimension(1:100) :: arr -# integer, dimension(1:100) :: b -# integer, dimension(1:100) :: c -# -# do i = 1, 100 -# arr(i) = i -# arr(i) = exp(arr(i)) -# k = i -# b(i) = arr(i) * 3 -# c(k) = i -# end do -# do i = 1, 100 -# b(i) = b(i) + 1 -# end do -# end subroutine -# ''' -# strans = ScalarizationTrans() -# psyir = fortran_reader.psyir_from_source(code) -# -# loop = psyir.children[0].children[0] -# strans.apply(loop) -# print(fortran_writer(psyir)) -# assert False +def test_scalarization_trans_apply(fortran_reader, fortran_writer): + ''' Test the application of the scalarization transformation.''' + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + + do i = 1, 100 + arr(i) = i + arr(i) = exp(arr(i)) + k = i + b(i) = arr(i) * 3 + c(k) = i + end do + do i = 1, 100 + b(i) = b(i) + 1 + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.children[0].children[0] + strans.apply(loop) + correct = '''subroutine test() + integer :: i + integer :: k + integer, dimension(100) :: arr + integer, dimension(100) :: b + integer, dimension(100) :: c + integer :: arr_scalar + + do i = 1, 100, 1 + arr_scalar = i + arr_scalar = EXP(arr_scalar) + k = i + b(i) = arr_scalar * 3 + c(k) = i + enddo + do i = 1, 100, 1 + b(i) = b(i) + 1 + enddo''' + out = fortran_writer(psyir) + assert correct in out From 2523a6bc8c882385373d0e9dd94b06aadcfcbec0 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 1 May 2024 14:16:44 +0100 Subject: [PATCH 07/62] Added compile test --- .../tests/psyir/transformations/scalarization_trans_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 0e71c8cfaf..2e3fd027c9 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -38,7 +38,7 @@ from psyclone.core import VariablesAccessInfo from psyclone.psyir.transformations import ScalarizationTrans -# from psyclone.tests.utilities import Compile +from psyclone.tests.utilities import Compile def test_scalarizationtrans_potential_array_symbols(fortran_reader): @@ -299,7 +299,7 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): assert potential_targets[0].var_name == "arr" -def test_scalarization_trans_apply(fortran_reader, fortran_writer): +def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' code = '''subroutine test() integer :: i @@ -345,3 +345,5 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer): enddo''' out = fortran_writer(psyir) assert correct in out + assert Compile(tmpdir).string_compiles(out) + From fece2bdaeb10ca1c279b9372c5dd9f30685e0c6a Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 1 May 2024 14:53:49 +0100 Subject: [PATCH 08/62] linting --- .../tests/psyir/transformations/scalarization_trans_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 2e3fd027c9..746dc85f64 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -346,4 +346,3 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): out = fortran_writer(psyir) assert correct in out assert Compile(tmpdir).string_compiles(out) - From ed75417eabfa27b43b39f48f89b78ba12e1623da Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Mon, 20 May 2024 15:47:13 +0100 Subject: [PATCH 09/62] Changes to use filter to make the code easier to understand --- .../transformations/scalarization_trans.py | 201 ++++++------ .../scalarization_trans_test.py | 308 +++++++++--------- 2 files changed, 266 insertions(+), 243 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 60d15b5c42..094257f264 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -47,102 +47,90 @@ class ScalarizationTrans(LoopTrans): - def _find_potential_scalarizable_array_symbols(self, node, var_accesses): - - potential_arrays = [] - signatures = var_accesses.all_signatures - for signature in signatures: - # Skip over non-arrays - if not var_accesses[signature].is_array(): - continue - # Skip over non-local symbols - base_symbol = var_accesses[signature].all_accesses[0].node.symbol - if not base_symbol.is_automatic: - continue - array_indices = None - scalarizable = True - for access in var_accesses[signature].all_accesses: - if array_indices is None: - array_indices = access.component_indices - # For some reason using == on the component_lists doesn't work - elif array_indices[:] != access.component_indices[:]: - scalarizable = False - break + @staticmethod + def _is_local_array(signature, var_accesses): + if not var_accesses[signature].is_array(): + return False + base_symbol = var_accesses[signature].all_accesses[0].node.symbol + if not base_symbol.is_automatic: + return False + + return True + + @staticmethod + def _have_same_unmodified_index(signature, var_accesses): + array_indices = None + scalarizable = True + for access in var_accesses[signature].all_accesses: + if array_indices is None: + array_indices = access.component_indices + # For some reason using == on the component_lists doesn't work + elif array_indices[:] != access.component_indices[:]: + scalarizable = False + break # For each index, we need to check they're not written to in # the loop. flattened_indices = list(itertools.chain.from_iterable( array_indices)) for index in flattened_indices: - sig, _ = index.get_signature_and_indices() - if var_accesses[sig].is_written(): - scalarizable = False - break - if scalarizable: - potential_arrays.append(signature) - - return potential_arrays - - def _check_first_access_is_write(self, node, var_accesses, potentials): - potential_arrays = [] - - for signature in potentials: - if var_accesses[signature].is_written_first(): - potential_arrays.append(signature) - - return potential_arrays - - def _check_valid_following_access(self, node, var_accesses, potentials): - potential_arrays = [] - - for signature in potentials: - # Find the last access of each signature - last_access = var_accesses[signature].all_accesses[-1].node - # Find the next access to this symbol - next_access = last_access.next_access() - # If we don't use this again then its valid - if next_access is None: - potential_arrays.append(signature) - continue - # If we do and the next_access has an ancestor IfBlock - # that isn't an ancestor of the loop then its not valid since - # we aren't tracking down what the condition-dependent next - # use really is. - if_ancestor = next_access.ancestor(IfBlock) - - # If abs_position of if_ancestor is > node.abs_position - # its not an ancestor of us. - if (if_ancestor is not None and - if_ancestor.abs_position > node.abs_position): - # Not a valid next_access pattern. - continue - - # If next access is the LHS of an assignment, we need to - # check that it doesn't also appear on the RHS. If so its - # not a valid access - # I'm not sure this code is reachable -# if (isinstance(next_access.parent, Assignment) and -# next_access.parent.lhs is next_access and -# (next_access.next_access() is not None and -# next_access.next_access().ancestor(Assignment) is -# next_access.parent)): -# continue - - # If next access is the RHS of an assignment then we need to - # skip it - ancestor_assign = next_access.ancestor(Assignment) - if (ancestor_assign is not None and - ancestor_assign.lhs is not next_access): - continue - - # If it has an ancestor that is a CodeBlock or Call or Kern - # then we can't guarantee anything, so we remove it. - if (next_access.ancestor((CodeBlock, Call, Kern)) - is not None): - continue - - potential_arrays.append(signature) - - return potential_arrays + # Index may not be a Reference, so we need to loop over the + # References + for ref in index.walk(Reference): + sig, _ = ref.get_signature_and_indices() + if var_accesses[sig].is_written(): + scalarizable = False + break + + return scalarizable + + @staticmethod + def _check_first_access_is_write(sig, var_accesses): + if var_accesses[sig].is_written_first(): + return True + return False + + @staticmethod + def _value_unused_after_loop(sig, node, var_accesses): + # Find the last access of the signature + last_access = var_accesses[sig].all_accesses[-1].node + # Find the next access to this symbol + next_access = last_access.next_access() + # If we don't use this again then this can be scalarized + if next_access is None: + return True + + # If the next_access has an ancestor IfBlock and + # that isn't an ancestor of the loop then its not valid since + # we aren't tracking down what the condition-dependent next + # use really is. + if_ancestor = next_access.ancestor(IfBlock) + # If abs_position of if_ancestor is > node.abs_position + # its not an ancestor of us. + # Handles: + # if (some_condition) then + # x = next_access[i] + 1 + if (if_ancestor is not None and + if_ancestor.abs_position > node.abs_position): + # Not a valid next_access pattern. + return False + + # If next access is the RHS of an assignment then we need to + # skip it + # Handles: + # a = next_access[i] + 1 + ancestor_assign = next_access.ancestor(Assignment) + if (ancestor_assign is not None and + ancestor_assign.lhs is not next_access): + return False + + # If it has an ancestor that is a CodeBlock or Call or Kern + # then we can't guarantee anything, so we remove it. + # Handles: call my_func(next_access) + if (next_access.ancestor((CodeBlock, Call, Kern)) + is not None): + return False + + return True def apply(self, node, options=None): '''Apply the scalarization transformation to a loop. @@ -182,17 +170,36 @@ def apply(self, node, options=None): # Find all the ararys that are only accessed by a single index, and # that index is only read inside the loop. - potential_targets = self._find_potential_scalarizable_array_symbols( - node, var_accesses) + potential_targets = filter( + lambda sig: + ScalarizationTrans._is_local_array(sig, var_accesses), + var_accesses) + potential_targets = filter( + lambda sig: + ScalarizationTrans._have_same_unmodified_index(sig, + var_accesses), + potential_targets) +# potential_targets = self._find_potential_scalarizable_array_symbols( +# node, var_accesses) # Now we need to check the first access is a write and remove those # that aren't. - potential_targets = self._check_first_access_is_write( - node, var_accesses, potential_targets) + potential_targets = filter( + lambda sig: + ScalarizationTrans._check_first_access_is_write(sig, + var_accesses), + potential_targets) +# potential_targets = self._check_first_access_is_write( +# node, var_accesses, potential_targets) # Check the values written to these arrays are not used after this loop - finalised_targets = self._check_valid_following_access( - node, var_accesses, potential_targets) + finalised_targets = filter( + lambda sig: + ScalarizationTrans._value_unused_after_loop(sig, node, + var_accesses), + potential_targets) +# finalised_targets = self._check_valid_following_access( +# node, var_accesses, potential_targets) routine_table = node.ancestor(Routine).symbol_table # For each finalised target we can replace them with a scalarized diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 746dc85f64..e8668486d2 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -41,146 +41,147 @@ from psyclone.tests.utilities import Compile -def test_scalarizationtrans_potential_array_symbols(fortran_reader): - ''' Test the possible code paths in the - _find_potential_scalarizable_array_symbols function.''' - code = '''subroutine test() - integer :: i - integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c +def test_scalararizationtrans_is_local_array(fortran_reader): + code = '''subroutine test(a) + use mymod, only: arr + integer :: i + integer :: k + integer, dimension(1:100) :: local + integer, dimension(1:100) :: a - do i = 1, 100 - arr(i) = i - end do - end subroutine - ''' - strans = ScalarizationTrans() + do i = 1, 100 + arr(i) = i + a(i) = i + local(i) = i + end do + end subroutine''' psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[0] var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potential_targets = strans._find_potential_scalarizable_array_symbols( - node, var_accesses) - assert len(potential_targets) == 1 - assert potential_targets[0].var_name == "arr" + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._is_local_array(keys[1], + var_accesses) + # Test a + assert var_accesses[keys[2]].var_name == "a" + assert not ScalarizationTrans._is_local_array(keys[2], + var_accesses) + # Test local + assert var_accesses[keys[3]].var_name == "local" + assert ScalarizationTrans._is_local_array(keys[3], + var_accesses) - code = '''subroutine test() - integer :: i - integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c + # Test filter behaviour same as used in the transformation + local_arrays = filter( + lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), + var_accesses) + local_arrays = list(local_arrays) + assert len(local_arrays) == 1 + assert local_arrays[0].var_name == "local" - do i = 1, 99 - k = i + 1 - arr(k) = i - end do - end subroutine - ''' - psyir = fortran_reader.psyir_from_source(code) - node = psyir.children[0].children[0] - var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potential_targets = strans._find_potential_scalarizable_array_symbols( - node, var_accesses) - assert len(potential_targets) == 0 +def test_scalarizationtrans_have_same_unmodified_index(fortran_reader): code = '''subroutine test() - integer :: i - integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c - k = 3 - do i = 1, 99 - arr(i) = i + 1 - arr(k) = arr(i) + 1 - end do - end subroutine - ''' + integer :: i + integer :: k + integer, dimension(1:100) :: a + integer, dimension(1:103) :: b + integer, dimension(1:100) :: c + k = 0 + do i = 1, 100 + a(i) = i + b(i+2) = i + b(i+3) = b(i) + b(i+1) + c(k) = 2 + k = k + 1 + end do + end subroutine''' psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[1] var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potential_targets = strans._find_potential_scalarizable_array_symbols( - node, var_accesses) - assert len(potential_targets) == 0 + keys = list(var_accesses.keys()) + # Test a + assert var_accesses[keys[1]].var_name == "a" + assert ScalarizationTrans._have_same_unmodified_index(keys[1], + var_accesses) + # Test b (differeing indices) + assert var_accesses[keys[2]].var_name == "b" + assert not ScalarizationTrans._have_same_unmodified_index(keys[2], + var_accesses) + # Test c (k is modified) + assert var_accesses[keys[3]].var_name == "c" + assert not ScalarizationTrans._have_same_unmodified_index(keys[3], + var_accesses) + # Test filter behaviour same as used in the transformation + local_arrays = filter( + lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), + var_accesses) + local_arrays = list(local_arrays) + assert len(local_arrays) == 3 - # Ensure that we don't access imports or arguments or unknowns - # for scalarization - # Not sure if we should expand this for anything else? - code = '''subroutine test(b) - use mymod, only: arr - integer :: i - integer :: k - integer, dimension(1:100) :: b + unmodified_indices = filter( + lambda sig: ScalarizationTrans._have_same_unmodified_index( + sig, var_accesses), + local_arrays) + unmodified_indices = list(unmodified_indices) + assert len(unmodified_indices) == 1 + assert unmodified_indices[0].var_name == "a" - do i = 1, 100 - arr(i) = i - b(i) = i - c(i) = i - end do - end subroutine - ''' - strans = ScalarizationTrans() - psyir = fortran_reader.psyir_from_source(code) - node = psyir.children[0].children[0] - var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potential_targets = strans._find_potential_scalarizable_array_symbols( - node, var_accesses) - assert len(potential_targets) == 0 - -def test_scalarization_first_access_is_write(fortran_reader): - ''' Test the scalarization transformation's - _check_first_access_is_write function.''' +def test_scalarizationtrans_check_first_access_is_write(fortran_reader): code = '''subroutine test() - integer :: i - integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c - - do i = 1, 100 - arr(i) = i - end do - end subroutine - ''' - strans = ScalarizationTrans() + integer :: i + integer :: k + integer, dimension(1:100) :: a + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + do i = 1, 100 + a(i) = i + b(i) = b(i) + 1 + c(i) = a(i) + b(i) + end do + end subroutine''' psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[0] var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potentials = [var_accesses.all_signatures[0]] - potential_targets = strans._check_first_access_is_write( - node, var_accesses, potentials) + keys = list(var_accesses.keys()) + # Test a + assert var_accesses[keys[1]].var_name == "a" + assert ScalarizationTrans._check_first_access_is_write(keys[1], + var_accesses) + # Test b (differeing indices) + assert var_accesses[keys[2]].var_name == "b" + assert not ScalarizationTrans._check_first_access_is_write(keys[2], + var_accesses) + # Test c (k is modified) + assert var_accesses[keys[3]].var_name == "c" + assert ScalarizationTrans._check_first_access_is_write(keys[3], + var_accesses) - assert len(potential_targets) == 1 - assert potential_targets[0].var_name == "arr" - code = '''subroutine test() - integer :: i - integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c + # Test filter behaviour same as used in the transformation + local_arrays = filter( + lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), + var_accesses) + local_arrays = list(local_arrays) + assert len(local_arrays) == 3 - do i = 1, 100 - arr(i) = arr(i) + 1 - end do - end subroutine - ''' - strans = ScalarizationTrans() - psyir = fortran_reader.psyir_from_source(code) - node = psyir.children[0].children[0] - var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potentials = [var_accesses.all_signatures[0]] - potential_targets = strans._check_first_access_is_write( - node, var_accesses, potentials) + unmodified_indices = filter( + lambda sig: ScalarizationTrans._have_same_unmodified_index( + sig, var_accesses), + local_arrays) + unmodified_indices = list(unmodified_indices) + assert len(unmodified_indices) == 3 - assert len(potential_targets) == 0 + first_write_arrays = filter( + lambda sig: ScalarizationTrans._check_first_access_is_write( + sig, var_accesses), + unmodified_indices) + first_write_arrays = list(first_write_arrays) + assert len(first_write_arrays) == 2 -def test_scalarization_trans_check_valid_following_access(fortran_reader): - ''' Test the scalarization transformation's - _check_valid_following_access function.''' +def test_scalarizationtrans_value_unused_after_loop(fortran_reader): code = '''subroutine test() integer :: i integer :: k @@ -196,16 +197,20 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): end do end subroutine ''' - strans = ScalarizationTrans() psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[0] var_accesses = VariablesAccessInfo(nodes=node.loop_body) - # Only arr makes it through the 2 prior stages - potentials = [var_accesses.all_signatures[0]] - potential_targets = strans._check_valid_following_access( - node, var_accesses, potentials) - assert len(potential_targets) == 1 - assert potential_targets[0].var_name == "arr" + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert ScalarizationTrans._value_unused_after_loop(keys[1], + node, + var_accesses) + # Test b + assert var_accesses[keys[2]].var_name == "b" + assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node, + var_accesses) # Test we ignore array next_access if they're in an if statement code = '''subroutine test() @@ -226,16 +231,20 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): end if end subroutine test ''' - strans = ScalarizationTrans() psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[0] var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potentials = [var_accesses.all_signatures[0], - var_accesses.all_signatures[1]] - potential_targets = strans._check_valid_following_access( - node, var_accesses, potentials) - assert len(potential_targets) == 1 - assert potential_targets[0].var_name == "arr" + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert ScalarizationTrans._value_unused_after_loop(keys[1], + node, + var_accesses) + # Test b + assert var_accesses[keys[2]].var_name == "b" + assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node, + var_accesses) # Test we don't ignore array next_access if they're in an if statement # that is an ancestor of the loop we're scalarizing code = '''subroutine test() @@ -255,17 +264,20 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): end if end subroutine test ''' - strans = ScalarizationTrans() psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[0].if_body.children[0] var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potentials = [var_accesses.all_signatures[0], - var_accesses.all_signatures[1]] - potential_targets = strans._check_valid_following_access( - node, var_accesses, potentials) - assert len(potential_targets) == 2 - assert potential_targets[0].var_name == "arr" - assert potential_targets[1].var_name == "b" + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert ScalarizationTrans._value_unused_after_loop(keys[1], + node, + var_accesses) + # Test b + assert var_accesses[keys[2]].var_name == "b" + assert ScalarizationTrans._value_unused_after_loop(keys[2], + node, + var_accesses) # Test we don't ignore array next_access if they have an ancestor # that is a Call @@ -287,16 +299,20 @@ def test_scalarization_trans_check_valid_following_access(fortran_reader): end if end subroutine test ''' - strans = ScalarizationTrans() psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[0].if_body.children[0] var_accesses = VariablesAccessInfo(nodes=node.loop_body) - potentials = [var_accesses.all_signatures[0], - var_accesses.all_signatures[1]] - potential_targets = strans._check_valid_following_access( - node, var_accesses, potentials) - assert len(potential_targets) == 1 - assert potential_targets[0].var_name == "arr" + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert ScalarizationTrans._value_unused_after_loop(keys[1], + node, + var_accesses) + # Test b + assert var_accesses[keys[2]].var_name == "b" + assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node, + var_accesses) def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): From bd2144b35585184c2745cbdb2f99b8c85d51b038 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:38:10 +0000 Subject: [PATCH 10/62] Changes to work with master --- .../transformations/scalarization_trans.py | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 094257f264..dffb193445 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -93,42 +93,47 @@ def _check_first_access_is_write(sig, var_accesses): def _value_unused_after_loop(sig, node, var_accesses): # Find the last access of the signature last_access = var_accesses[sig].all_accesses[-1].node - # Find the next access to this symbol - next_access = last_access.next_access() + # Find the next accesses to this symbol + next_accesses = last_access.next_accesses() # If we don't use this again then this can be scalarized - if next_access is None: + if len(next_accesses) == 0: return True # If the next_access has an ancestor IfBlock and # that isn't an ancestor of the loop then its not valid since # we aren't tracking down what the condition-dependent next # use really is. - if_ancestor = next_access.ancestor(IfBlock) - # If abs_position of if_ancestor is > node.abs_position - # its not an ancestor of us. - # Handles: - # if (some_condition) then - # x = next_access[i] + 1 - if (if_ancestor is not None and - if_ancestor.abs_position > node.abs_position): - # Not a valid next_access pattern. - return False - - # If next access is the RHS of an assignment then we need to - # skip it - # Handles: - # a = next_access[i] + 1 - ancestor_assign = next_access.ancestor(Assignment) - if (ancestor_assign is not None and - ancestor_assign.lhs is not next_access): - return False - - # If it has an ancestor that is a CodeBlock or Call or Kern - # then we can't guarantee anything, so we remove it. - # Handles: call my_func(next_access) - if (next_access.ancestor((CodeBlock, Call, Kern)) - is not None): - return False + for next_access in next_accesses: + # next_accesses looks backwards to the start of the loop, + # but we don't care about those accesses here. + if next_access.abs_position <= last_access.abs_position: + continue + if_ancestor = next_access.ancestor(IfBlock) + # If abs_position of if_ancestor is > node.abs_position + # its not an ancestor of us. + # Handles: + # if (some_condition) then + # x = next_access[i] + 1 + if (if_ancestor is not None and + if_ancestor.abs_position > node.abs_position): + # Not a valid next_access pattern. + return False + + # If next access is the RHS of an assignment then we need to + # skip it + # Handles: + # a = next_access[i] + 1 + ancestor_assign = next_access.ancestor(Assignment) + if (ancestor_assign is not None and + ancestor_assign.lhs is not next_access): + return False + + # If it has an ancestor that is a CodeBlock or Call or Kern + # then we can't guarantee anything, so we remove it. + # Handles: call my_func(next_access) + if (next_access.ancestor((CodeBlock, Call, Kern)) + is not None): + return False return True From 047c4a6e6183d70dabca8d7e53e9a10d2e62c7dc Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:48:56 +0000 Subject: [PATCH 11/62] First section of rewriting, coverage issues to fix --- .../transformations/scalarization_trans.py | 54 ++++++++++++------- .../scalarization_trans_test.py | 6 +-- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index dffb193445..22e0d803ae 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -40,7 +40,7 @@ from psyclone.core import VariablesAccessInfo from psyclone.psyGen import Kern from psyclone.psyir.nodes import Assignment, Call, CodeBlock, IfBlock, \ - Reference, Routine + Loop, Node, Reference, Routine, WhileLoop from psyclone.psyir.symbols import DataSymbol from psyclone.psyir.transformations.loop_trans import LoopTrans @@ -95,30 +95,48 @@ def _value_unused_after_loop(sig, node, var_accesses): last_access = var_accesses[sig].all_accesses[-1].node # Find the next accesses to this symbol next_accesses = last_access.next_accesses() - # If we don't use this again then this can be scalarized - if len(next_accesses) == 0: - return True - - # If the next_access has an ancestor IfBlock and - # that isn't an ancestor of the loop then its not valid since - # we aren't tracking down what the condition-dependent next - # use really is. for next_access in next_accesses: # next_accesses looks backwards to the start of the loop, # but we don't care about those accesses here. if next_access.abs_position <= last_access.abs_position: continue - if_ancestor = next_access.ancestor(IfBlock) - # If abs_position of if_ancestor is > node.abs_position - # its not an ancestor of us. - # Handles: - # if (some_condition) then - # x = next_access[i] + 1 - if (if_ancestor is not None and - if_ancestor.abs_position > node.abs_position): - # Not a valid next_access pattern. + + # If next access is a Call or CodeBlock or Kern then + # we have to assume the value is used. + if isinstance(next_access, (CodeBlock, Call, Kern)): return False + # If next access is an IfBlock then it reads the value. + if isinstance(next_access, IfBlock): + return False + + # If next access has an ancestor WhileLoop, and its in the + # condition then it reads the value. + ancestor_while = next_access.ancestor(WhileLoop) + if ancestor_while: + conditions = ancestor_while.condition.walk(Node) + for node in conditions: + if node is next_access: + return False + + # If next access has an ancestor Loop, and its one of the + # start/stop/step values then it reads the value. + ancestor_loop = next_access.ancestor(Loop) + if ancestor_loop: + starts = ancestor_loop.start_expr.walk(Node) + stops = ancestor_loop.stop_expr.walk(Node) + steps = ancestor_loop.step_expr.walk(Node) + for node in starts: + if node is next_access: + return False + for node in stops: + if node is next_access: + return False + for node in steps: + if node is next_access: + return False + + # If next access is the RHS of an assignment then we need to # skip it # Handles: diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index e8668486d2..c18a915ba9 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -242,9 +242,9 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" - assert not ScalarizationTrans._value_unused_after_loop(keys[2], - node, - var_accesses) + assert ScalarizationTrans._value_unused_after_loop(keys[2], + node, + var_accesses) # Test we don't ignore array next_access if they're in an if statement # that is an ancestor of the loop we're scalarizing code = '''subroutine test() From b1d95448d22bfa3bcf5ce3d6cc20c1b5a5c17e3e Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 10 Jan 2025 13:27:50 +0000 Subject: [PATCH 12/62] linting error --- src/psyclone/psyir/transformations/scalarization_trans.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 22e0d803ae..463a0ef705 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -136,7 +136,6 @@ def _value_unused_after_loop(sig, node, var_accesses): if node is next_access: return False - # If next access is the RHS of an assignment then we need to # skip it # Handles: From dc69373b92199c91cea43a155e8ca67b591d3afa Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:02:38 +0000 Subject: [PATCH 13/62] Updated tests for coverage. Now need to do some more complex functionality tests --- .../scalarization_trans_test.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index c18a915ba9..399e619dc0 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -314,6 +314,79 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): node, var_accesses) + # Test being a while condition correctly counts as being used. + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + + do i = 1, 100 + arr(i) = exp(arr(i)) + b(i) = arr(i) * 3 + end do + do i = 1, 100 + do while(b(i) < 256) + b(i) = arr(i) * arr(i) + arr(i) = arr(i) * 2 + end do + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test b + assert var_accesses[keys[2]].var_name == "b" + assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node, + var_accesses) + + # Test being a loop start/stop/step condition correctly counts + # as being used. + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + integer, dimension(1:100, 1:100) :: d + + do i = 1, 100 + arr(i) = exp(arr(i)) + b(i) = arr(i) * 3 + c(i) = i + end do + do i = 1, 100 + do k = arr(i), b(i), c(i) + d(i,k) = i + end do + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node, + var_accesses) + # Test b + assert var_accesses[keys[2]].var_name == "b" + assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node, + var_accesses) + # Test b + assert var_accesses[keys[3]].var_name == "c" + assert not ScalarizationTrans._value_unused_after_loop(keys[3], + node, + var_accesses) + def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' From 1a1531ab2404905cbe54caa5a22192a3dcb9a931 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:45:49 +0000 Subject: [PATCH 14/62] Missing coverage for other types of read --- .../transformations/scalarization_trans.py | 17 +++-- .../scalarization_trans_test.py | 63 ++++++++++++++++++- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 463a0ef705..1e7f6dceee 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -106,9 +106,14 @@ def _value_unused_after_loop(sig, node, var_accesses): if isinstance(next_access, (CodeBlock, Call, Kern)): return False - # If next access is an IfBlock then it reads the value. - if isinstance(next_access, IfBlock): - return False + # If next access is in an IfBlock condition then it reads the + # value. + ancestor_ifblock = next_access.ancestor(IfBlock) + if ancestor_ifblock: + conditions = ancestor_ifblock.condition.walk(Node) + for node in conditions: + if node is next_access: + return False # If next access has an ancestor WhileLoop, and its in the # condition then it reads the value. @@ -201,8 +206,6 @@ def apply(self, node, options=None): ScalarizationTrans._have_same_unmodified_index(sig, var_accesses), potential_targets) -# potential_targets = self._find_potential_scalarizable_array_symbols( -# node, var_accesses) # Now we need to check the first access is a write and remove those # that aren't. @@ -211,8 +214,6 @@ def apply(self, node, options=None): ScalarizationTrans._check_first_access_is_write(sig, var_accesses), potential_targets) -# potential_targets = self._check_first_access_is_write( -# node, var_accesses, potential_targets) # Check the values written to these arrays are not used after this loop finalised_targets = filter( @@ -220,8 +221,6 @@ def apply(self, node, options=None): ScalarizationTrans._value_unused_after_loop(sig, node, var_accesses), potential_targets) -# finalised_targets = self._check_valid_following_access( -# node, var_accesses, potential_targets) routine_table = node.ancestor(Routine).symbol_table # For each finalised target we can replace them with a scalarized diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 399e619dc0..aaaa059b1c 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -381,12 +381,73 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): assert not ScalarizationTrans._value_unused_after_loop(keys[2], node, var_accesses) - # Test b + # Test c assert var_accesses[keys[3]].var_name == "c" assert not ScalarizationTrans._value_unused_after_loop(keys[3], node, var_accesses) + # Test being a symbol in a Codeblock counts as used + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + integer, dimension(1:100, 1:100) :: d + + do i = 1, 100 + arr(i) = exp(arr(i)) + b(i) = arr(i) * 3 + c(i) = i + end do + do i = 1, 100 + print *, arr(i) + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node, + var_accesses) + + # Test being in an IfBlock condition counts as used. + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + integer, dimension(1:100, 1:100) :: d + + do i = 1, 100 + arr(i) = exp(arr(i)) + b(i) = arr(i) * 3 + c(i) = i + end do + do i = 1, 100 + if(arr(i) == 1) then + print *, b(i) + end if + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node, + var_accesses) def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' From a5062446dc06242569d8a1d22eb75049eb955036 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:59:00 +0000 Subject: [PATCH 15/62] linting fix --- .../tests/psyir/transformations/scalarization_trans_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index aaaa059b1c..9df1d4c987 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -449,6 +449,7 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): node, var_accesses) + def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' code = '''subroutine test() From 75b77d7a6a47c5940870e4704606165693a1abaf Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:36:17 +0000 Subject: [PATCH 16/62] Added another test --- .../transformations/scalarization_trans.py | 45 ++++++++++-- .../scalarization_trans_test.py | 71 +++++++++++++++---- 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 1e7f6dceee..290d9fc83f 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -49,6 +49,16 @@ class ScalarizationTrans(LoopTrans): @staticmethod def _is_local_array(signature, var_accesses): + ''' + :param signature: The signature to check if it is a local array symbol + or not. + :type signature: :py:class:`psyclone.core.Signature` + :param var_accesses: The VariableAccessesInfo object containing + signature. + :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` + :returns bool: whether the symbol corresponding to signature is a + local symbol or not. + ''' if not var_accesses[signature].is_array(): return False base_symbol = var_accesses[signature].all_accesses[0].node.symbol @@ -59,6 +69,16 @@ def _is_local_array(signature, var_accesses): @staticmethod def _have_same_unmodified_index(signature, var_accesses): + ''' + :param signature: The signature to check. + :type signature: :py:class:`psyclone.core.Signature` + :param var_accesses: The VariableAccessesInfo object containing + signature. + :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` + :returns bool: whether all the array accesses to signature use the + same index, and whether the index is unmodified in + the code region. + ''' array_indices = None scalarizable = True for access in var_accesses[signature].all_accesses: @@ -84,13 +104,30 @@ def _have_same_unmodified_index(signature, var_accesses): return scalarizable @staticmethod - def _check_first_access_is_write(sig, var_accesses): - if var_accesses[sig].is_written_first(): + def _check_first_access_is_write(signature, var_accesses): + ''' + :param signature: The signature to check. + :type signature: :py:class:`psyclone.core.Signature` + :param var_accesses: The VariableAccessesInfo object containing + signature. + :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` + :returns bool: whether the first access to signature is a write. + ''' + if var_accesses[signature].is_written_first(): return True return False @staticmethod - def _value_unused_after_loop(sig, node, var_accesses): + def _value_unused_after_loop(sig, var_accesses): + ''' + :param sig: The signature to check. + :type sig: :py:class:`psyclone.core.Signature` + :param var_accesses: The VariableAccessesInfo object containing + signature. + :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` + :returns bool: whether the value computed in the loop containing + sig is read from after the loop. + ''' # Find the last access of the signature last_access = var_accesses[sig].all_accesses[-1].node # Find the next accesses to this symbol @@ -218,7 +255,7 @@ def apply(self, node, options=None): # Check the values written to these arrays are not used after this loop finalised_targets = filter( lambda sig: - ScalarizationTrans._value_unused_after_loop(sig, node, + ScalarizationTrans._value_unused_after_loop(sig, var_accesses), potential_targets) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 9df1d4c987..0286695fa7 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -204,12 +204,10 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], - node, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], - node, var_accesses) # Test we ignore array next_access if they're in an if statement @@ -238,12 +236,10 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], - node, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert ScalarizationTrans._value_unused_after_loop(keys[2], - node, var_accesses) # Test we don't ignore array next_access if they're in an if statement # that is an ancestor of the loop we're scalarizing @@ -271,12 +267,10 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], - node, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert ScalarizationTrans._value_unused_after_loop(keys[2], - node, var_accesses) # Test we don't ignore array next_access if they have an ancestor @@ -306,12 +300,10 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], - node, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], - node, var_accesses) # Test being a while condition correctly counts as being used. @@ -341,7 +333,6 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], - node, var_accesses) # Test being a loop start/stop/step condition correctly counts @@ -374,17 +365,14 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert not ScalarizationTrans._value_unused_after_loop(keys[1], - node, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], - node, var_accesses) # Test c assert var_accesses[keys[3]].var_name == "c" assert not ScalarizationTrans._value_unused_after_loop(keys[3], - node, var_accesses) # Test being a symbol in a Codeblock counts as used @@ -414,7 +402,6 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert not ScalarizationTrans._value_unused_after_loop(keys[1], - node, var_accesses) # Test being in an IfBlock condition counts as used. @@ -446,7 +433,6 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert not ScalarizationTrans._value_unused_after_loop(keys[1], - node, var_accesses) @@ -497,3 +483,60 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): out = fortran_writer(psyir) assert correct in out assert Compile(tmpdir).string_compiles(out) + + # Use in if/else where the if has write only followup and + # the else has a read - shouldn't scalarise b. + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + integer, dimension(1:100) :: b + integer, dimension(1:100) :: c + + do i = 1, 100 + arr(i) = i + arr(i) = exp(arr(i)) + k = i + b(i) = arr(i) * 3 + c(k) = i + end do + do i = 1, 100 + if(c(i) > 50) then + b(i) = c(i) + else + b(i) = b(i) + c(i) + end if + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + + loop = psyir.children[0].children[0] + strans.apply(loop) + correct = '''subroutine test() + integer :: i + integer :: k + integer, dimension(100) :: arr + integer, dimension(100) :: b + integer, dimension(100) :: c + integer :: arr_scalar + + do i = 1, 100, 1 + arr_scalar = i + arr_scalar = EXP(arr_scalar) + k = i + b(i) = arr_scalar * 3 + c(k) = i + enddo + do i = 1, 100, 1 + if (c(i) > 50) then + b(i) = c(i) + else + b(i) = b(i) + c(i) + end if + enddo''' + out = fortran_writer(psyir) + print(out) + assert correct in out + assert Compile(tmpdir).string_compiles(out) From 280cdb6e9ee15ec7e2b091253b237be55a203351 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:57:06 +0000 Subject: [PATCH 17/62] Added documentation to scalarization trans including an example --- .../transformations/scalarization_trans.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 290d9fc83f..25479eb847 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -46,6 +46,52 @@ class ScalarizationTrans(LoopTrans): + '''This transformation takes a Loop and converts any array accesses + to scalar if the results of the loop are unused, and the initial value + is unused. For example: + + >>> from psyclone.psyir.backend.fortran import FortranWriter + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.transformations import ScalarizationTrans + >>> from psyclone.psyir.nodes import Loop + >>> code = """program test + ... integer :: i,j + ... real :: a(100), b(100) + ... do i = 1,100 + ... a(i) = i + ... b(i) = a(i) * a(i) + ... end do + ... do j = 1, 100 + ... if(b(i) > 200) then + ... print *, b(i) + ... end if + ... end do + ... end program""" + >>> psyir = FortranReader().psyir_from_source(code) + >>> scalarise = ScalarizationTrans() + >>> scalarise.apply(psyir.walk(Loop)[0]) + >>> print(FortranWriter()(psyir)) + program test + integer :: i + integer :: j + real, dimension(100) :: a + real, dimension(100) :: b + real :: a_scalar + + do i = 1, 100, 1 + a_scalar = i + b(i) = a_scalar * a_scalar + enddo + do j = 1, 100, 1 + if (b(i) > 200) then + ! PSyclone CodeBlock (unsupported code) reason: + ! - Unsupported statement: Print_Stmt + PRINT *, b(i) + end if + enddo + + end program test + ''' @staticmethod def _is_local_array(signature, var_accesses): From 74c708160bb3b5a86cb7c1d7127a82a3f5e3e7dd Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 14 Jan 2025 14:08:11 +0000 Subject: [PATCH 18/62] Fixed doc errors --- .../psyir/transformations/scalarization_trans.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 25479eb847..02e5f1a38b 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -243,20 +243,21 @@ def _value_unused_after_loop(sig, var_accesses): return True def apply(self, node, options=None): - '''Apply the scalarization transformation to a loop. + ''' + Apply the scalarization transformation to a loop. All of the array accesses that are identified as being able to be scalarized will be transformed by this transformation. An array access will be scalarized if: 1. All accesses to the array use the same indexing statement. 2. All References contained in the indexing statement are not modified - inside of the loop (loop variables are ok). + inside of the loop (loop variables are ok). 3. The array symbol is either not accessed again or is written to - as its next access. If the next access is inside a conditional - that is not an ancestor of the input loop, then PSyclone will - assume that we cannot scalarize that value instead of attempting to - understand the control flow. - 4. TODO - The array symbol is a local variable. + as its next access. If the next access is inside a conditional + that is not an ancestor of the input loop, then PSyclone will + assume that we cannot scalarize that value instead of attempting to + understand the control flow. + 4. The array symbol is a local variable. :param node: the supplied loop to apply scalarization to. :type node: :py:class:`psyclone.psyir.nodes.Loop` From 058b3ef3ed97f55b9388e69e97ab128f5a2f9579 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 22 Jan 2025 14:36:47 +0000 Subject: [PATCH 19/62] Changes for the review --- src/psyclone/psyir/nodes/node.py | 18 ++++++++++++++ .../transformations/scalarization_trans.py | 24 ++++++++++++------- src/psyclone/tests/psyir/nodes/node_test.py | 19 +++++++++++++++ .../scalarization_trans_test.py | 3 +-- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/psyclone/psyir/nodes/node.py b/src/psyclone/psyir/nodes/node.py index 3567805131..8358dbed92 100644 --- a/src/psyclone/psyir/nodes/node.py +++ b/src/psyclone/psyir/nodes/node.py @@ -1782,6 +1782,24 @@ def update_parent_symbol_table(self, new_parent): ''' + def is_descendent_of(self, potential_ancestor) -> bool: + ''' + Checks if this node is a descendant of the `potential_ancestor` node. + + :param potential_ancestor: The Node to check whether its an ancestor + of self. + :type node: :py:class:`psyclone.psyir.nodes.Node` + + :returns bool: whether potential_ancestor is an ancestor of this node. + ''' + current_node = self + while (current_node is not potential_ancestor and + current_node.parent is not None): + current_node = current_node.parent + + return current_node is potential_ancestor + + # For automatic documentation generation # TODO #913 the 'colored' routine shouldn't be in this module. diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 02e5f1a38b..69f6e3302c 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2017-2024, Science and Technology Facilities Council. +# Copyright (c) 2024-2025, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,8 +36,9 @@ '''This module provides the sclarization transformation class.''' import itertools +from typing import Optional, Dict, Any -from psyclone.core import VariablesAccessInfo +from psyclone.core import VariablesAccessInfo, Signature from psyclone.psyGen import Kern from psyclone.psyir.nodes import Assignment, Call, CodeBlock, IfBlock, \ Loop, Node, Reference, Routine, WhileLoop @@ -94,7 +95,8 @@ class ScalarizationTrans(LoopTrans): ''' @staticmethod - def _is_local_array(signature, var_accesses): + def _is_local_array(signature: Signature, + var_accesses: VariablesAccessInfo) -> bool: ''' :param signature: The signature to check if it is a local array symbol or not. @@ -103,7 +105,7 @@ def _is_local_array(signature, var_accesses): signature. :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` :returns bool: whether the symbol corresponding to signature is a - local symbol or not. + local array symbol or not. ''' if not var_accesses[signature].is_array(): return False @@ -114,7 +116,9 @@ def _is_local_array(signature, var_accesses): return True @staticmethod - def _have_same_unmodified_index(signature, var_accesses): + def _have_same_unmodified_index(signature: Signature, + var_accesses: VariablesAccessInfo) \ + -> bool: ''' :param signature: The signature to check. :type signature: :py:class:`psyclone.core.Signature` @@ -131,6 +135,7 @@ def _have_same_unmodified_index(signature, var_accesses): if array_indices is None: array_indices = access.component_indices # For some reason using == on the component_lists doesn't work + # so we use [:] notation. elif array_indices[:] != access.component_indices[:]: scalarizable = False break @@ -150,7 +155,9 @@ def _have_same_unmodified_index(signature, var_accesses): return scalarizable @staticmethod - def _check_first_access_is_write(signature, var_accesses): + def _check_first_access_is_write(signature: Signature, + var_accesses: VariablesAccessInfo) \ + -> bool: ''' :param signature: The signature to check. :type signature: :py:class:`psyclone.core.Signature` @@ -164,7 +171,8 @@ def _check_first_access_is_write(signature, var_accesses): return False @staticmethod - def _value_unused_after_loop(sig, var_accesses): + def _value_unused_after_loop(sig: Signature, + var_accesses: VariablesAccessInfo) -> bool: ''' :param sig: The signature to check. :type sig: :py:class:`psyclone.core.Signature` @@ -242,7 +250,7 @@ def _value_unused_after_loop(sig, var_accesses): return True - def apply(self, node, options=None): + def apply(self, node: Loop, options:Optional[Dict[str,Any]]=None) -> None: ''' Apply the scalarization transformation to a loop. All of the array accesses that are identified as being able to be diff --git a/src/psyclone/tests/psyir/nodes/node_test.py b/src/psyclone/tests/psyir/nodes/node_test.py index 8ba43b668f..7d3714df73 100644 --- a/src/psyclone/tests/psyir/nodes/node_test.py +++ b/src/psyclone/tests/psyir/nodes/node_test.py @@ -1904,3 +1904,22 @@ def test_following(fortran_reader): assert routines[1] not in loops[1].following(include_children=False) assert routines[1] in loops[1].following(same_routine_scope=False, include_children=False) + + +def test_is_descendent_of(fortran_reader): + '''Test the is_descendent_of function of the Node class''' + code = ''' + subroutine test() + integer :: i, j, k + do i = 1, 100 + j = i + end do + k = 1 + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + loop = psyir.walk(Loop)[0] + assigns = psyir.walk(Assignment) + + assert assigns[0].is_descendent_of(loop) + assert not assigns[1].is_descendent_of(loop) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 0286695fa7..8d6ff54774 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -1,7 +1,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2022-2024, Science and Technology Facilities Council. +# Copyright (c) 2024-2025, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -537,6 +537,5 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): end if enddo''' out = fortran_writer(psyir) - print(out) assert correct in out assert Compile(tmpdir).string_compiles(out) From 4984dcd12123ff751655308d912c85024aad9c77 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 22 Jan 2025 14:40:18 +0000 Subject: [PATCH 20/62] Fixed the docuemntation --- src/psyclone/psyir/transformations/scalarization_trans.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 69f6e3302c..1bbfa02258 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -78,7 +78,7 @@ class ScalarizationTrans(LoopTrans): real, dimension(100) :: a real, dimension(100) :: b real :: a_scalar - + do i = 1, 100, 1 a_scalar = i b(i) = a_scalar * a_scalar @@ -92,6 +92,7 @@ class ScalarizationTrans(LoopTrans): enddo end program test + ''' @staticmethod From caf0176b3678ed34081a0061eef5382a2c6f0029 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 22 Jan 2025 14:55:15 +0000 Subject: [PATCH 21/62] Fixed linting --- src/psyclone/psyir/nodes/node.py | 3 +-- src/psyclone/psyir/transformations/scalarization_trans.py | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/psyclone/psyir/nodes/node.py b/src/psyclone/psyir/nodes/node.py index 8358dbed92..53283f0e12 100644 --- a/src/psyclone/psyir/nodes/node.py +++ b/src/psyclone/psyir/nodes/node.py @@ -1793,14 +1793,13 @@ def is_descendent_of(self, potential_ancestor) -> bool: :returns bool: whether potential_ancestor is an ancestor of this node. ''' current_node = self - while (current_node is not potential_ancestor and + while (current_node is not potential_ancestor and current_node.parent is not None): current_node = current_node.parent return current_node is potential_ancestor - # For automatic documentation generation # TODO #913 the 'colored' routine shouldn't be in this module. __all__ = ["colored", diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 1bbfa02258..d592866767 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -119,7 +119,7 @@ def _is_local_array(signature: Signature, @staticmethod def _have_same_unmodified_index(signature: Signature, var_accesses: VariablesAccessInfo) \ - -> bool: + -> bool: ''' :param signature: The signature to check. :type signature: :py:class:`psyclone.core.Signature` @@ -158,7 +158,7 @@ def _have_same_unmodified_index(signature: Signature, @staticmethod def _check_first_access_is_write(signature: Signature, var_accesses: VariablesAccessInfo) \ - -> bool: + -> bool: ''' :param signature: The signature to check. :type signature: :py:class:`psyclone.core.Signature` @@ -251,7 +251,8 @@ def _value_unused_after_loop(sig: Signature, return True - def apply(self, node: Loop, options:Optional[Dict[str,Any]]=None) -> None: + def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ + -> None: ''' Apply the scalarization transformation to a loop. All of the array accesses that are identified as being able to be From c4b964195ebce973a2da8230441bc41f9bc06ac3 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Thu, 23 Jan 2025 14:19:57 +0000 Subject: [PATCH 22/62] Changes towards review, blocked by #2870 --- src/psyclone/psyir/nodes/node.py | 2 +- .../transformations/scalarization_trans.py | 99 +++++++++---------- .../scalarization_trans_test.py | 39 ++++++++ 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/src/psyclone/psyir/nodes/node.py b/src/psyclone/psyir/nodes/node.py index 53283f0e12..359e776324 100644 --- a/src/psyclone/psyir/nodes/node.py +++ b/src/psyclone/psyir/nodes/node.py @@ -1790,7 +1790,7 @@ def is_descendent_of(self, potential_ancestor) -> bool: of self. :type node: :py:class:`psyclone.psyir.nodes.Node` - :returns bool: whether potential_ancestor is an ancestor of this node. + :returns: whether potential_ancestor is an ancestor of this node. ''' current_node = self while (current_node is not potential_ancestor and diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index d592866767..a4788e1651 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -101,12 +101,10 @@ def _is_local_array(signature: Signature, ''' :param signature: The signature to check if it is a local array symbol or not. - :type signature: :py:class:`psyclone.core.Signature` :param var_accesses: The VariableAccessesInfo object containing signature. - :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` - :returns bool: whether the symbol corresponding to signature is a - local array symbol or not. + :returns: whether the symbol corresponding to signature is a + local array symbol or not. ''' if not var_accesses[signature].is_array(): return False @@ -122,13 +120,11 @@ def _have_same_unmodified_index(signature: Signature, -> bool: ''' :param signature: The signature to check. - :type signature: :py:class:`psyclone.core.Signature` :param var_accesses: The VariableAccessesInfo object containing signature. - :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` - :returns bool: whether all the array accesses to signature use the - same index, and whether the index is unmodified in - the code region. + :returns: whether all the array accesses to signature use the + same index, and whether the index is unmodified in + the code region. ''' array_indices = None scalarizable = True @@ -161,11 +157,9 @@ def _check_first_access_is_write(signature: Signature, -> bool: ''' :param signature: The signature to check. - :type signature: :py:class:`psyclone.core.Signature` :param var_accesses: The VariableAccessesInfo object containing signature. - :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` - :returns bool: whether the first access to signature is a write. + :returns: whether the first access to signature is a write. ''' if var_accesses[signature].is_written_first(): return True @@ -173,15 +167,15 @@ def _check_first_access_is_write(signature: Signature, @staticmethod def _value_unused_after_loop(sig: Signature, + loop: Loop, var_accesses: VariablesAccessInfo) -> bool: ''' :param sig: The signature to check. - :type sig: :py:class:`psyclone.core.Signature` + :param loop: The loop the transformation is operating on. :param var_accesses: The VariableAccessesInfo object containing signature. - :type var_accesses: :py:class:`psyclone.core.VariablesAccessInfo` - :returns bool: whether the value computed in the loop containing - sig is read from after the loop. + :returns: whether the value computed in the loop containing + sig is read from after the loop. ''' # Find the last access of the signature last_access = var_accesses[sig].all_accesses[-1].node @@ -190,7 +184,7 @@ def _value_unused_after_loop(sig: Signature, for next_access in next_accesses: # next_accesses looks backwards to the start of the loop, # but we don't care about those accesses here. - if next_access.abs_position <= last_access.abs_position: + if next_access.is_descendent_of(loop): continue # If next access is a Call or CodeBlock or Kern then @@ -200,54 +194,56 @@ def _value_unused_after_loop(sig: Signature, # If next access is in an IfBlock condition then it reads the # value. - ancestor_ifblock = next_access.ancestor(IfBlock) - if ancestor_ifblock: - conditions = ancestor_ifblock.condition.walk(Node) - for node in conditions: - if node is next_access: - return False +# ancestor_ifblock = next_access.ancestor(IfBlock) +# if ancestor_ifblock: +# conditions = ancestor_ifblock.condition.walk(Node) +# for node in conditions: +# if node is next_access: +# return False # If next access has an ancestor WhileLoop, and its in the # condition then it reads the value. - ancestor_while = next_access.ancestor(WhileLoop) - if ancestor_while: - conditions = ancestor_while.condition.walk(Node) - for node in conditions: - if node is next_access: - return False +# ancestor_while = next_access.ancestor(WhileLoop) +# if ancestor_while: +# conditions = ancestor_while.condition.walk(Node) +# for node in conditions: +# if node is next_access: +# return False # If next access has an ancestor Loop, and its one of the # start/stop/step values then it reads the value. - ancestor_loop = next_access.ancestor(Loop) - if ancestor_loop: - starts = ancestor_loop.start_expr.walk(Node) - stops = ancestor_loop.stop_expr.walk(Node) - steps = ancestor_loop.step_expr.walk(Node) - for node in starts: - if node is next_access: - return False - for node in stops: - if node is next_access: - return False - for node in steps: - if node is next_access: - return False +# ancestor_loop = next_access.ancestor(Loop) +# if ancestor_loop: +# starts = ancestor_loop.start_expr.walk(Node) +# stops = ancestor_loop.stop_expr.walk(Node) +# steps = ancestor_loop.step_expr.walk(Node) +# for node in starts: +# if node is next_access: +# return False +# for node in stops: +# if node is next_access: +# return False +# for node in steps: +# if node is next_access: +# return False + if next_access.is_read: + return False # If next access is the RHS of an assignment then we need to # skip it # Handles: # a = next_access[i] + 1 - ancestor_assign = next_access.ancestor(Assignment) - if (ancestor_assign is not None and - ancestor_assign.lhs is not next_access): - return False +# ancestor_assign = next_access.ancestor(Assignment) +# if (ancestor_assign is not None and +# ancestor_assign.lhs is not next_access): +# return False # If it has an ancestor that is a CodeBlock or Call or Kern # then we can't guarantee anything, so we remove it. # Handles: call my_func(next_access) - if (next_access.ancestor((CodeBlock, Call, Kern)) - is not None): - return False +# if (next_access.ancestor((CodeBlock, Call, Kern)) +# is not None): +# return False return True @@ -270,9 +266,7 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ 4. The array symbol is a local variable. :param node: the supplied loop to apply scalarization to. - :type node: :py:class:`psyclone.psyir.nodes.Loop` :param options: a dictionary with options for transformations. - :type options: Optional[Dict[str, Any]] ''' # For each array reference in the Loop: @@ -313,6 +307,7 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ finalised_targets = filter( lambda sig: ScalarizationTrans._value_unused_after_loop(sig, + node, var_accesses), potential_targets) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 8d6ff54774..7bfc22ea8b 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -204,10 +204,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node.loop_body, var_accesses) # Test we ignore array next_access if they're in an if statement @@ -236,10 +238,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert ScalarizationTrans._value_unused_after_loop(keys[2], + node.loop_body, var_accesses) # Test we don't ignore array next_access if they're in an if statement # that is an ancestor of the loop we're scalarizing @@ -267,10 +271,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert ScalarizationTrans._value_unused_after_loop(keys[2], + node.loop_body, var_accesses) # Test we don't ignore array next_access if they have an ancestor @@ -300,10 +306,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node.loop_body, var_accesses) # Test being a while condition correctly counts as being used. @@ -333,6 +341,7 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node.loop_body, var_accesses) # Test being a loop start/stop/step condition correctly counts @@ -365,14 +374,17 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._value_unused_after_loop(keys[2], + node.loop_body, var_accesses) # Test c assert var_accesses[keys[3]].var_name == "c" assert not ScalarizationTrans._value_unused_after_loop(keys[3], + node.loop_body, var_accesses) # Test being a symbol in a Codeblock counts as used @@ -402,6 +414,7 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, var_accesses) # Test being in an IfBlock condition counts as used. @@ -433,8 +446,34 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): # Test arr assert var_accesses[keys[1]].var_name == "arr" assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, var_accesses) + # Test being a Loop variable counts as unused. + code = '''subroutine test() + integer :: i + integer :: k + integer, dimension(1:100) :: arr + + do i = 1, 100 + arr(i) = i + end do + do arr(50) = 1, 100 + print *, arr(i) + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, + var_accesses) + assert False + def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' From e6c3072af997d5927ce8e3ef1b8a64b796fca3b9 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Thu, 23 Jan 2025 14:53:21 +0000 Subject: [PATCH 23/62] Remove failing test that didn't meet the Fortran standard --- .../transformations/scalarization_trans.py | 59 ++----------------- .../scalarization_trans_test.py | 25 -------- 2 files changed, 6 insertions(+), 78 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index a4788e1651..b0e9bc002c 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -40,8 +40,8 @@ from psyclone.core import VariablesAccessInfo, Signature from psyclone.psyGen import Kern -from psyclone.psyir.nodes import Assignment, Call, CodeBlock, IfBlock, \ - Loop, Node, Reference, Routine, WhileLoop +from psyclone.psyir.nodes import Call, CodeBlock, \ + Loop, Reference, Routine from psyclone.psyir.symbols import DataSymbol from psyclone.psyir.transformations.loop_trans import LoopTrans @@ -188,62 +188,15 @@ def _value_unused_after_loop(sig: Signature, continue # If next access is a Call or CodeBlock or Kern then - # we have to assume the value is used. + # we have to assume the value is used. These nodes don't + # have the is_read property that Reference has, so we need + # to be explicit. if isinstance(next_access, (CodeBlock, Call, Kern)): return False - # If next access is in an IfBlock condition then it reads the - # value. -# ancestor_ifblock = next_access.ancestor(IfBlock) -# if ancestor_ifblock: -# conditions = ancestor_ifblock.condition.walk(Node) -# for node in conditions: -# if node is next_access: -# return False - - # If next access has an ancestor WhileLoop, and its in the - # condition then it reads the value. -# ancestor_while = next_access.ancestor(WhileLoop) -# if ancestor_while: -# conditions = ancestor_while.condition.walk(Node) -# for node in conditions: -# if node is next_access: -# return False - - # If next access has an ancestor Loop, and its one of the - # start/stop/step values then it reads the value. -# ancestor_loop = next_access.ancestor(Loop) -# if ancestor_loop: -# starts = ancestor_loop.start_expr.walk(Node) -# stops = ancestor_loop.stop_expr.walk(Node) -# steps = ancestor_loop.step_expr.walk(Node) -# for node in starts: -# if node is next_access: -# return False -# for node in stops: -# if node is next_access: -# return False -# for node in steps: -# if node is next_access: -# return False - + # If the access is a read, then return False if next_access.is_read: return False - # If next access is the RHS of an assignment then we need to - # skip it - # Handles: - # a = next_access[i] + 1 -# ancestor_assign = next_access.ancestor(Assignment) -# if (ancestor_assign is not None and -# ancestor_assign.lhs is not next_access): -# return False - - # If it has an ancestor that is a CodeBlock or Call or Kern - # then we can't guarantee anything, so we remove it. - # Handles: call my_func(next_access) -# if (next_access.ancestor((CodeBlock, Call, Kern)) -# is not None): -# return False return True diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 7bfc22ea8b..be7fba94ca 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -449,31 +449,6 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): node.loop_body, var_accesses) - # Test being a Loop variable counts as unused. - code = '''subroutine test() - integer :: i - integer :: k - integer, dimension(1:100) :: arr - - do i = 1, 100 - arr(i) = i - end do - do arr(50) = 1, 100 - print *, arr(i) - end do - end subroutine test - ''' - psyir = fortran_reader.psyir_from_source(code) - node = psyir.children[0].children[0] - var_accesses = VariablesAccessInfo(nodes=node.loop_body) - keys = list(var_accesses.keys()) - # Test arr - assert var_accesses[keys[1]].var_name == "arr" - assert ScalarizationTrans._value_unused_after_loop(keys[1], - node.loop_body, - var_accesses) - assert False - def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' From ef3cd346dba001393ef4b38071f08f6917d002e8 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Thu, 23 Jan 2025 15:01:14 +0000 Subject: [PATCH 24/62] Added the transformation into the User guide --- doc/user_guide/transformations.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/user_guide/transformations.rst b/doc/user_guide/transformations.rst index 635773d51f..609d97d7fa 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -568,6 +568,13 @@ can be found in the API-specific sections). #### +.. autoclass:: psyclone.psyir.transformations.ScalarizationTrans + :members: apply + :noindex: + +#### + + Algorithm-layer --------------- From 6d8bff5d9ab703fcc24eeff8f468ccc1ec640f31 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Sat, 25 Jan 2025 16:58:40 +0000 Subject: [PATCH 25/62] Scalarize every loop to ensure nothing breaks in NEMO - Revert this later --- examples/nemo/scripts/omp_gpu_trans.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py index b34b876d22..d675b14679 100755 --- a/examples/nemo/scripts/omp_gpu_trans.py +++ b/examples/nemo/scripts/omp_gpu_trans.py @@ -45,6 +45,8 @@ Loop, Routine, Directive, Assignment, OMPAtomicDirective) from psyclone.psyir.transformations import OMPTargetTrans from psyclone.transformations import OMPDeclareTargetTrans, TransformationError +# TODO REMOVE +from psyclone.transformations import ScalarizationTrans PROFILING_ENABLED = False @@ -93,6 +95,11 @@ def trans(psyir): hoist_expressions=True ) + #TODO REMOVE + scalartrans = ScalarizationTrans() + for loop in subroutine.walk(Loop): + scalartrans.apply(loop) + # Thes are functions that are called from inside parallel regions, # annotate them with 'omp declare target' if subroutine.name.lower().startswith("sign_"): From 4974a6e3f06a1222465a04b522ef0887fcf2c5ec Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 27 Jan 2025 08:47:59 +0000 Subject: [PATCH 26/62] Fix tests to compile --- .../scalarization_trans_test.py | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index be7fba94ca..f5b340096e 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -46,8 +46,8 @@ def test_scalararizationtrans_is_local_array(fortran_reader): use mymod, only: arr integer :: i integer :: k - integer, dimension(1:100) :: local - integer, dimension(1:100) :: a + real, dimension(1:100) :: local + real, dimension(1:100) :: a do i = 1, 100 arr(i) = i @@ -85,9 +85,9 @@ def test_scalarizationtrans_have_same_unmodified_index(fortran_reader): code = '''subroutine test() integer :: i integer :: k - integer, dimension(1:100) :: a - integer, dimension(1:103) :: b - integer, dimension(1:100) :: c + real, dimension(1:100) :: a + real, dimension(1:103) :: b + real, dimension(1:100) :: c k = 0 do i = 1, 100 a(i) = i @@ -133,9 +133,9 @@ def test_scalarizationtrans_check_first_access_is_write(fortran_reader): code = '''subroutine test() integer :: i integer :: k - integer, dimension(1:100) :: a - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c + real, dimension(1:100) :: a + real, dimension(1:100) :: b + real, dimension(1:100) :: c do i = 1, 100 a(i) = i b(i) = b(i) + 1 @@ -185,8 +185,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): code = '''subroutine test() integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b + real, dimension(1:100) :: arr + real, dimension(1:100) :: b do i = 1, 100 arr(i) = exp(arr(i)) @@ -216,8 +216,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): code = '''subroutine test() integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b + real, dimension(1:100) :: arr + real, dimension(1:100) :: b logical :: x = .FALSE. do i = 1, 100 @@ -250,8 +250,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): code = '''subroutine test() integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b + real, dimension(1:100) :: arr + real, dimension(1:100) :: b if(.false.) then do i = 1, 100 @@ -285,8 +285,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): use my_mod integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b + real, dimension(1:100) :: arr + real, dimension(1:100) :: b if(.false.) then do i = 1, 100 @@ -319,8 +319,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): use my_mod integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b + real, dimension(1:100) :: arr + real, dimension(1:100) :: b do i = 1, 100 arr(i) = exp(arr(i)) @@ -350,10 +350,10 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): use my_mod integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c - integer, dimension(1:100, 1:100) :: d + real, dimension(1:100) :: arr + real, dimension(1:100) :: b + real, dimension(1:100) :: c + real, dimension(1:100, 1:100) :: d do i = 1, 100 arr(i) = exp(arr(i)) @@ -392,10 +392,10 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): use my_mod integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c - integer, dimension(1:100, 1:100) :: d + real, dimension(1:100) :: arr + real, dimension(1:100) :: b + real, dimension(1:100) :: c + real, dimension(1:100, 1:100) :: d do i = 1, 100 arr(i) = exp(arr(i)) @@ -422,10 +422,10 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): use my_mod integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c - integer, dimension(1:100, 1:100) :: d + real, dimension(1:100) :: arr + real, dimension(1:100) :: b + real, dimension(1:100) :: c + real, dimension(1:100, 1:100) :: d do i = 1, 100 arr(i) = exp(arr(i)) @@ -455,9 +455,9 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): code = '''subroutine test() integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c + real, dimension(1:100) :: arr + real, dimension(1:100) :: b + real, dimension(1:100) :: c do i = 1, 100 arr(i) = i @@ -479,10 +479,10 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): correct = '''subroutine test() integer :: i integer :: k - integer, dimension(100) :: arr - integer, dimension(100) :: b - integer, dimension(100) :: c - integer :: arr_scalar + real, dimension(100) :: arr + real, dimension(100) :: b + real, dimension(100) :: c + real :: arr_scalar do i = 1, 100, 1 arr_scalar = i @@ -503,9 +503,9 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): code = '''subroutine test() integer :: i integer :: k - integer, dimension(1:100) :: arr - integer, dimension(1:100) :: b - integer, dimension(1:100) :: c + real, dimension(1:100) :: arr + real, dimension(1:100) :: b + real, dimension(1:100) :: c do i = 1, 100 arr(i) = i @@ -531,10 +531,10 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): correct = '''subroutine test() integer :: i integer :: k - integer, dimension(100) :: arr - integer, dimension(100) :: b - integer, dimension(100) :: c - integer :: arr_scalar + real, dimension(100) :: arr + real, dimension(100) :: b + real, dimension(100) :: c + real :: arr_scalar do i = 1, 100, 1 arr_scalar = i From 409b859ade5b20a842b01bde1a9d81052ad6f84d Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 27 Jan 2025 08:48:50 +0000 Subject: [PATCH 27/62] Linting fix --- examples/nemo/scripts/omp_gpu_trans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py index d675b14679..21bc0abf4b 100755 --- a/examples/nemo/scripts/omp_gpu_trans.py +++ b/examples/nemo/scripts/omp_gpu_trans.py @@ -95,7 +95,7 @@ def trans(psyir): hoist_expressions=True ) - #TODO REMOVE + # TODO REMOVE scalartrans = ScalarizationTrans() for loop in subroutine.walk(Loop): scalartrans.apply(loop) From 8d519362de88105e3ab95b7be7504fcb95526b09 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 27 Jan 2025 08:51:46 +0000 Subject: [PATCH 28/62] Failure to import scalarizationtrans fix --- examples/nemo/scripts/omp_gpu_trans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py index 21bc0abf4b..7e0305afd4 100755 --- a/examples/nemo/scripts/omp_gpu_trans.py +++ b/examples/nemo/scripts/omp_gpu_trans.py @@ -46,7 +46,7 @@ from psyclone.psyir.transformations import OMPTargetTrans from psyclone.transformations import OMPDeclareTargetTrans, TransformationError # TODO REMOVE -from psyclone.transformations import ScalarizationTrans +from psyclone.psyir.transformations import ScalarizationTrans PROFILING_ENABLED = False From dbae47c8d51af36696d94afb963bbf458e35d89a Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 27 Jan 2025 12:07:42 +0000 Subject: [PATCH 29/62] Fix a bug from NEMO4 integration tests where the transformation failed to handle a case where an array access had a RoutineSymbol (i.e. a function call) as an index --- .../transformations/scalarization_trans.py | 7 +++- .../scalarization_trans_test.py | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index b0e9bc002c..c0ad0c4d99 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -42,7 +42,7 @@ from psyclone.psyGen import Kern from psyclone.psyir.nodes import Call, CodeBlock, \ Loop, Reference, Routine -from psyclone.psyir.symbols import DataSymbol +from psyclone.psyir.symbols import DataSymbol, RoutineSymbol from psyclone.psyir.transformations.loop_trans import LoopTrans @@ -144,6 +144,11 @@ def _have_same_unmodified_index(signature: Signature, # Index may not be a Reference, so we need to loop over the # References for ref in index.walk(Reference): + # This Reference could be the symbol for a Call or + # IntrinsicCall, which we don't allow to scalarize + if isinstance(ref.symbol, RoutineSymbol): + scalarizable = False + break sig, _ = ref.get_signature_and_indices() if var_accesses[sig].is_written(): scalarizable = False diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index f5b340096e..79947cf15c 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -553,3 +553,36 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): out = fortran_writer(psyir) assert correct in out assert Compile(tmpdir).string_compiles(out) + + +def test_scalarization_trans_apply_routinesymbol(fortran_reader, + fortran_writer, tmpdir): + ''' Test the application of the scalarization transformation doesn't work + when applied on an array with a RoutineSymbol as an index.''' + code = '''subroutine test + integer, dimension(3) :: j + integer :: i + integer, allocatable, dimension(:,:,:) :: k + do i= 1, 100 + allocate(k(MAXVAL(j(1:3)),1,1)) + deallocate(k) + end do + end subroutine test''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + strans.apply(psyir.children[0].children[0]) + correct = '''subroutine test() + integer, dimension(3) :: j + integer :: i + integer, allocatable, dimension(:,:,:) :: k + + do i = 1, 100, 1 + ALLOCATE(k(1:MAXVAL(j(:)),1:1,1:1)) + DEALLOCATE(k) + enddo + +end subroutine test +''' + out = fortran_writer(psyir) + assert correct == out + assert Compile(tmpdir).string_compiles(out) From a840c8b1587eb679d33c40c9670031e464a5f182 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 28 Jan 2025 10:03:50 +0000 Subject: [PATCH 30/62] Fix another NEMO4 failure case --- .../psyir/transformations/scalarization_trans.py | 12 +++++++++--- .../transformations/scalarization_trans_test.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index c0ad0c4d99..034d83b01c 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -108,6 +108,12 @@ def _is_local_array(signature: Signature, ''' if not var_accesses[signature].is_array(): return False + # If any of the accesses are to a CodeBlock then we stop. This can + # happen if there is a string access inside a string concatenation, + # e.g. NEMO4. + for access in var_accesses[signature].all_accesses: + if isinstance(access.node, CodeBlock): + return False base_symbol = var_accesses[signature].all_accesses[0].node.symbol if not base_symbol.is_automatic: return False @@ -115,9 +121,9 @@ def _is_local_array(signature: Signature, return True @staticmethod - def _have_same_unmodified_index(signature: Signature, - var_accesses: VariablesAccessInfo) \ - -> bool: + def _have_same_unmodified_index( + signature: Signature, + var_accesses: VariablesAccessInfo) -> bool: ''' :param signature: The signature to check. :param var_accesses: The VariableAccessesInfo object containing diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 79947cf15c..0e39fa5a95 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -48,11 +48,13 @@ def test_scalararizationtrans_is_local_array(fortran_reader): integer :: k real, dimension(1:100) :: local real, dimension(1:100) :: a + character(2), dimension(1:100) :: b do i = 1, 100 arr(i) = i a(i) = i local(i) = i + b(i) = b(i) // "c" end do end subroutine''' psyir = fortran_reader.psyir_from_source(code) @@ -72,6 +74,14 @@ def test_scalararizationtrans_is_local_array(fortran_reader): assert ScalarizationTrans._is_local_array(keys[3], var_accesses) + # Test b - the RHS of the assignment is a codeblock so we do not + # count it as a local array and invalidate it, as otherwise the + # local array test can fail. Also we can't safely transform the + # CodeBlock anyway. + assert var_accesses[keys[4]].var_name == "b" + assert not ScalarizationTrans._is_local_array(keys[4], + var_accesses) + # Test filter behaviour same as used in the transformation local_arrays = filter( lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), From 01912bd0f7819be8bc6a4876752137cbcf406704 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 28 Jan 2025 14:58:22 +0000 Subject: [PATCH 31/62] fixed the issue in definition use chains when we find empty scopes --- .../psyir/tools/definition_use_chains.py | 54 ++++++++++++------- ...ion_use_chains_backward_dependence_test.py | 30 +++++++++++ ...tion_use_chains_forward_dependence_test.py | 30 +++++++++++ 3 files changed, 96 insertions(+), 18 deletions(-) diff --git a/src/psyclone/psyir/tools/definition_use_chains.py b/src/psyclone/psyir/tools/definition_use_chains.py index 6a68f29233..ac2ba13c9e 100644 --- a/src/psyclone/psyir/tools/definition_use_chains.py +++ b/src/psyclone/psyir/tools/definition_use_chains.py @@ -250,16 +250,20 @@ def find_forward_accesses(self): .abs_position + 1 ) - # We make a copy of the reference to have a detached - # node to avoid handling the special cases based on - # the parents of the reference. - chain = DefinitionUseChain( - self._reference.copy(), - body, - start_point=ancestor.abs_position, - stop_point=sub_stop_point, - ) - chains.insert(0, chain) + # If we have a basic block with no children then skip it, + # e.g. for an if block with no code before the else + # statement, as is found in NEMO4. + if(len(body) > 0): + # We make a copy of the reference to have a detached + # node to avoid handling the special cases based on + # the parents of the reference. + chain = DefinitionUseChain( + self._reference.copy(), + body, + start_point=ancestor.abs_position, + stop_point=sub_stop_point, + ) + chains.insert(0, chain) # If its a while loop, create a basic block for the while # condition. if isinstance(ancestor, WhileLoop): @@ -300,6 +304,11 @@ def find_forward_accesses(self): # Now add all the other standardly handled basic_blocks to the # list of chains. for block in basic_blocks: + # If we have a basic block with no children then skip it, + # e.g. for an if block with no code before the else + # statement, as is found in NEMO4. + if(len(block) == 0): + continue chain = DefinitionUseChain( self._reference, block, @@ -835,6 +844,11 @@ def find_backward_accesses(self): # Now add all the other standardly handled basic_blocks to the # list of chains. for block in basic_blocks: + # If we have a basic block with no children then skip it, + # e.g. for an if block with no code before the else + # statement, as is found in NEMO4. + if(len(block) == 0): + continue chain = DefinitionUseChain( self._reference, block, @@ -874,14 +888,18 @@ def find_backward_accesses(self): ).abs_position else: sub_start_point = self._reference.abs_position - chain = DefinitionUseChain( - self._reference.copy(), - body, - start_point=sub_start_point, - stop_point=sub_stop_point, - ) - chains.append(chain) - control_flow_nodes.append(ancestor) + # If we have a basic block with no children then skip it, + # e.g. for an if block with no code before the else + # statement, as is found in NEMO4. + if(len(body) > 0): + chain = DefinitionUseChain( + self._reference.copy(), + body, + start_point=sub_start_point, + stop_point=sub_stop_point, + ) + chains.append(chain) + control_flow_nodes.append(ancestor) # If its a while loop, create a basic block for the while # condition. if isinstance(ancestor, WhileLoop): diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py index bafc2c92c8..a2e10c42f8 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py @@ -687,3 +687,33 @@ def test_definition_use_chains_backward_accesses_nonassign_reference_in_loop( assert reaches[0] is routine.children[1].loop_body.children[1].children[1] assert reaches[1] is routine.children[1].loop_body.children[0].lhs assert reaches[2] is routine.children[0].lhs + +def test_definition_use_chains_backward_accesses_empty_schedules( + fortran_reader, +): + '''Coverage to handle the case where we have empty schedules inside + various type of code.''' + code = """ + subroutine x() + integer :: a, i + a = 1 + do i = 1, 100 + end do + if(.TRUE.) then + else + endif + do while(.FALSE.) + end do + a = a + a + end subroutine x + """ + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.walk(Routine)[0] + chains = DefinitionUseChain( + routine.children[4].lhs + ) + reaches = chains.find_backward_accesses() + assert len(reaches) == 3 + assert reaches[0] is routine.children[4].rhs.children[1] + assert reaches[1] is routine.children[4].rhs.children[0] + assert reaches[2] is routine.children[0].lhs diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py index 6cf86a3874..05ce1f1201 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py @@ -937,3 +937,33 @@ def test_definition_use_chains_forward_accesses_multiple_routines( ) reaches = chains.find_forward_accesses() assert len(reaches) == 0 + +def test_definition_use_chains_forward_accesses_empty_schedules( + fortran_reader, +): + '''Coverage to handle the case where we have empty schedules inside + various type of code.''' + code = """ + subroutine x() + integer :: a, i + a = 1 + do i = 1, 100 + end do + if(.TRUE.) then + else + endif + do while(.FALSE.) + end do + a = a + a + end subroutine x + """ + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.walk(Routine)[0] + chains = DefinitionUseChain( + routine.children[0].lhs + ) + reaches = chains.find_forward_accesses() + assert len(reaches) == 3 + assert reaches[0] is routine.children[4].rhs.children[0] + assert reaches[1] is routine.children[4].rhs.children[1] + assert reaches[2] is routine.children[4].lhs From 9e75ce6232421eb210ac35ad90d0b36ff68fd0d6 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 28 Jan 2025 14:59:26 +0000 Subject: [PATCH 32/62] flake issues --- src/psyclone/psyir/tools/definition_use_chains.py | 8 ++++---- .../definition_use_chains_backward_dependence_test.py | 1 + .../definition_use_chains_forward_dependence_test.py | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/psyclone/psyir/tools/definition_use_chains.py b/src/psyclone/psyir/tools/definition_use_chains.py index ac2ba13c9e..c480b625cf 100644 --- a/src/psyclone/psyir/tools/definition_use_chains.py +++ b/src/psyclone/psyir/tools/definition_use_chains.py @@ -253,7 +253,7 @@ def find_forward_accesses(self): # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else # statement, as is found in NEMO4. - if(len(body) > 0): + if len(body) > 0: # We make a copy of the reference to have a detached # node to avoid handling the special cases based on # the parents of the reference. @@ -307,7 +307,7 @@ def find_forward_accesses(self): # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else # statement, as is found in NEMO4. - if(len(block) == 0): + if len(block) == 0: continue chain = DefinitionUseChain( self._reference, @@ -847,7 +847,7 @@ def find_backward_accesses(self): # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else # statement, as is found in NEMO4. - if(len(block) == 0): + if len(block) == 0: continue chain = DefinitionUseChain( self._reference, @@ -891,7 +891,7 @@ def find_backward_accesses(self): # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else # statement, as is found in NEMO4. - if(len(body) > 0): + if len(body) > 0: chain = DefinitionUseChain( self._reference.copy(), body, diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py index a2e10c42f8..85fa66499a 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py @@ -688,6 +688,7 @@ def test_definition_use_chains_backward_accesses_nonassign_reference_in_loop( assert reaches[1] is routine.children[1].loop_body.children[0].lhs assert reaches[2] is routine.children[0].lhs + def test_definition_use_chains_backward_accesses_empty_schedules( fortran_reader, ): diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py index 05ce1f1201..bb122de94a 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py @@ -938,6 +938,7 @@ def test_definition_use_chains_forward_accesses_multiple_routines( reaches = chains.find_forward_accesses() assert len(reaches) == 0 + def test_definition_use_chains_forward_accesses_empty_schedules( fortran_reader, ): From 0b372122c08639f6bd500f0e8fb13db2f192b338 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 28 Jan 2025 18:01:59 +0000 Subject: [PATCH 33/62] Testing scalarization in passthrough instead of on GPU --- examples/nemo/scripts/omp_gpu_trans.py | 7 ------- examples/nemo/scripts/passthrough.py | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py index 7e0305afd4..b34b876d22 100755 --- a/examples/nemo/scripts/omp_gpu_trans.py +++ b/examples/nemo/scripts/omp_gpu_trans.py @@ -45,8 +45,6 @@ Loop, Routine, Directive, Assignment, OMPAtomicDirective) from psyclone.psyir.transformations import OMPTargetTrans from psyclone.transformations import OMPDeclareTargetTrans, TransformationError -# TODO REMOVE -from psyclone.psyir.transformations import ScalarizationTrans PROFILING_ENABLED = False @@ -95,11 +93,6 @@ def trans(psyir): hoist_expressions=True ) - # TODO REMOVE - scalartrans = ScalarizationTrans() - for loop in subroutine.walk(Loop): - scalartrans.apply(loop) - # Thes are functions that are called from inside parallel regions, # annotate them with 'omp declare target' if subroutine.name.lower().startswith("sign_"): diff --git a/examples/nemo/scripts/passthrough.py b/examples/nemo/scripts/passthrough.py index 26eb480722..17e7dab641 100755 --- a/examples/nemo/scripts/passthrough.py +++ b/examples/nemo/scripts/passthrough.py @@ -37,6 +37,7 @@ ''' Process Nemo code with PSyclone but don't do any changes. This file is only needed to provide a FILES_TO_SKIP list. ''' from utils import PASSTHROUGH_ISSUES +from psyclone.psyir.transformations import ScalarizationTrans # List of all files that psyclone will skip processing FILES_TO_SKIP = PASSTHROUGH_ISSUES @@ -44,3 +45,8 @@ def trans(_): ''' Don't do any changes. ''' + # TODO REMOVE + scalartrans = ScalarizationTrans() + for loop in subroutine.walk(Loop): + scalartrans.apply(loop) + From 9818e4b54ca80aff73eecda42cfdf643ea0fffb9 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 29 Jan 2025 09:28:54 +0000 Subject: [PATCH 34/62] Go again --- examples/nemo/scripts/passthrough.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/nemo/scripts/passthrough.py b/examples/nemo/scripts/passthrough.py index 17e7dab641..732618ce65 100755 --- a/examples/nemo/scripts/passthrough.py +++ b/examples/nemo/scripts/passthrough.py @@ -37,16 +37,19 @@ ''' Process Nemo code with PSyclone but don't do any changes. This file is only needed to provide a FILES_TO_SKIP list. ''' from utils import PASSTHROUGH_ISSUES +# TODO Remove from psyclone.psyir.transformations import ScalarizationTrans +from psyclone.psyir.nodes import Routine, Loop # List of all files that psyclone will skip processing FILES_TO_SKIP = PASSTHROUGH_ISSUES -def trans(_): +def trans(psyir): ''' Don't do any changes. ''' # TODO REMOVE - scalartrans = ScalarizationTrans() - for loop in subroutine.walk(Loop): - scalartrans.apply(loop) + for subroutine in psyir.walk(Routine): + scalartrans = ScalarizationTrans() + for loop in subroutine.walk(Loop): + scalartrans.apply(loop) From c795eb1dba238de925d9afd475d956699513f49b Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 29 Jan 2025 10:19:45 +0000 Subject: [PATCH 35/62] another change to test things as we need to skip some files for now with obs_ at the start to compile --- examples/nemo/scripts/passthrough.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/nemo/scripts/passthrough.py b/examples/nemo/scripts/passthrough.py index 732618ce65..01e52324bb 100755 --- a/examples/nemo/scripts/passthrough.py +++ b/examples/nemo/scripts/passthrough.py @@ -47,6 +47,9 @@ def trans(psyir): ''' Don't do any changes. ''' + if psyir.name.startswith("obs_"): + print("Skipping file", psyir.name) + return # TODO REMOVE for subroutine in psyir.walk(Routine): scalartrans = ScalarizationTrans() From 4093d6664439d900b8d625ba11e9b4bfbd62fa13 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 29 Jan 2025 11:12:17 +0000 Subject: [PATCH 36/62] run the passthrough code for nemo4 --- .github/workflows/nemo_tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/nemo_tests.yml b/.github/workflows/nemo_tests.yml index 79aab92ba2..be2d17d5f1 100644 --- a/.github/workflows/nemo_tests.yml +++ b/.github/workflows/nemo_tests.yml @@ -94,6 +94,13 @@ jobs: module load perl/${PERL_VERSION} make -j 4 passthrough make -j 4 compile-passthrough + make run-passthrough |& tee output.txt + # Check the output is as expected for the first 6 digits (if not exit with error message) + tail -n 1 output.txt | grep -q " it : 10" || (echo "Error: 'it : 10' not found!" & false) + tail -n 1 output.txt | grep -q "|ssh|_max: 0.259483" || (echo "Error: '|ssh|_max: 0.259483' not found!" & false) + tail -n 1 output.txt | grep -q "|U|_max: 0.458515" || (echo "Error: '|U|_max: 0.458515' not found!" & false) + tail -n 1 output.txt | grep -q "S_min: 0.482686" || (echo "Error: 'S_min: 0.482686' not found!" & false) + tail -n 1 output.txt | grep -q "S_max: 0.407622" || (echo "Error: 'S_max: 0.407622' not found!" & false) # PSyclone, compile and run MetOffice NEMO with OpenMP for GPUs - name: NEMO MetOffice OpenMP for GPU From 250c259fab17a18dbc4d4b5a949feff02abb4908 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 31 Jan 2025 14:51:34 +0000 Subject: [PATCH 37/62] Fix known bugs scalarizing NEMO --- .../transformations/scalarization_trans.py | 16 +++++++++--- .../scalarization_trans_test.py | 26 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 034d83b01c..0731d0189d 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -40,9 +40,9 @@ from psyclone.core import VariablesAccessInfo, Signature from psyclone.psyGen import Kern -from psyclone.psyir.nodes import Call, CodeBlock, \ - Loop, Reference, Routine -from psyclone.psyir.symbols import DataSymbol, RoutineSymbol +from psyclone.psyir.nodes import Call, CodeBlock, \ + Loop, Reference, Routine, StructureReference +from psyclone.psyir.symbols import DataSymbol, DataTypeSymbol, RoutineSymbol from psyclone.psyir.transformations.loop_trans import LoopTrans @@ -117,6 +117,16 @@ def _is_local_array(signature: Signature, base_symbol = var_accesses[signature].all_accesses[0].node.symbol if not base_symbol.is_automatic: return False + # If its a derived type then we don't scalarize. + if isinstance(var_accesses[signature].all_accesses[0].node, + StructureReference): + return False + # Find the containing routine + rout = var_accesses[signature].all_accesses[0].node.ancestor(Routine) + # If the array is the return symbol then its not a local + # array symbol + if base_symbol is rout.return_symbol: + return False return True diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 0e39fa5a95..367a9aa93f 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -42,21 +42,27 @@ def test_scalararizationtrans_is_local_array(fortran_reader): - code = '''subroutine test(a) - use mymod, only: arr + code = '''function test(a) result(x) + use mymod, only: arr, atype integer :: i integer :: k real, dimension(1:100) :: local real, dimension(1:100) :: a character(2), dimension(1:100) :: b + real, dimension(1:100) :: x + type(atype) :: custom + type(atype), dimension(1:100) :: custom2 do i = 1, 100 arr(i) = i a(i) = i local(i) = i b(i) = b(i) // "c" + x(i) = i + custom%type(i) = i + custom2(i)%typeb(i) = i end do - end subroutine''' + end function''' psyir = fortran_reader.psyir_from_source(code) node = psyir.children[0].children[0] var_accesses = VariablesAccessInfo(nodes=node.loop_body) @@ -82,6 +88,20 @@ def test_scalararizationtrans_is_local_array(fortran_reader): assert not ScalarizationTrans._is_local_array(keys[4], var_accesses) + # Test x - the return value is not classed as a local array. + assert var_accesses[keys[5]].var_name == "x" + assert not ScalarizationTrans._is_local_array(keys[5], + var_accesses) + + # Test custom - we don't scalarize derived types. + assert var_accesses[keys[6]].var_name == "custom%type" + assert not ScalarizationTrans._is_local_array(keys[6], + var_accesses) + # Test custom2 - we don't scalarize derived types. + assert var_accesses[keys[7]].var_name == "custom2%typeb" + assert not ScalarizationTrans._is_local_array(keys[7], + var_accesses) + # Test filter behaviour same as used in the transformation local_arrays = filter( lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), From 51814b5324013bd36e2ca156e2b6388e68ab34f8 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 31 Jan 2025 14:52:02 +0000 Subject: [PATCH 38/62] linting --- src/psyclone/psyir/transformations/scalarization_trans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 0731d0189d..a47c8b9f38 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -42,7 +42,7 @@ from psyclone.psyGen import Kern from psyclone.psyir.nodes import Call, CodeBlock, \ Loop, Reference, Routine, StructureReference -from psyclone.psyir.symbols import DataSymbol, DataTypeSymbol, RoutineSymbol +from psyclone.psyir.symbols import DataSymbol, RoutineSymbol from psyclone.psyir.transformations.loop_trans import LoopTrans From 94f99eb12e89c7741996a86198cf4c47d83817e1 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 31 Jan 2025 15:00:45 +0000 Subject: [PATCH 39/62] Linting --- examples/nemo/scripts/passthrough.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/nemo/scripts/passthrough.py b/examples/nemo/scripts/passthrough.py index 01e52324bb..f998904431 100755 --- a/examples/nemo/scripts/passthrough.py +++ b/examples/nemo/scripts/passthrough.py @@ -55,4 +55,3 @@ def trans(psyir): scalartrans = ScalarizationTrans() for loop in subroutine.walk(Loop): scalartrans.apply(loop) - From 88c1eb3868747652574bf9f4cb2c0c4a6fba8cec Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 4 Feb 2025 10:30:23 +0000 Subject: [PATCH 40/62] Failing test added to try to work out NEMO5 issues --- .../scalarization_trans_test.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 367a9aa93f..17f0c40f10 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -616,3 +616,32 @@ def test_scalarization_trans_apply_routinesymbol(fortran_reader, out = fortran_writer(psyir) assert correct == out assert Compile(tmpdir).string_compiles(out) + + +def test_noscalarize(fortran_reader, fortran_writer): + '''TODO''' + code =''' + subroutine test + integer :: i, j, k + real , dimension(1:1000, 1:1000, 1:5) :: arr + + do i = 1,1000 + do j = 1,1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + arr(:,:,1) = 0.0 + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + from psyclone.psyir.nodes import Loop + loops = psyir.walk(Loop) + for loop in loops: + strans.apply(loop) + out = fortran_writer(psyir) + print(out) + assert "arr_scalar" not in out + assert False From 453a56ba89d40078ef634b62ea481b4f7cd03db7 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 4 Feb 2025 15:35:01 +0000 Subject: [PATCH 41/62] Fix inquiry behaviour for is_read --- src/psyclone/psyir/nodes/reference.py | 7 +++++++ src/psyclone/tests/psyir/nodes/reference_test.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/psyclone/psyir/nodes/reference.py b/src/psyclone/psyir/nodes/reference.py index a2737dba98..b658d9d6e8 100644 --- a/src/psyclone/psyir/nodes/reference.py +++ b/src/psyclone/psyir/nodes/reference.py @@ -102,10 +102,17 @@ def is_read(self): ''' # pylint: disable=import-outside-toplevel from psyclone.psyir.nodes.assignment import Assignment + from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall parent = self.parent if isinstance(parent, Assignment): if parent.lhs is self: return False + + # If we have an intrinsic call parent then we need to check if its + # an inquiry. Inquiry functions don't read from their arguments. + if isinstance(parent, IntrinsicCall) and parent.is_inquiry: + return False + # All references other than LHS of assignments represent a read. This # can be improved in the future by looking at Call intents. return True diff --git a/src/psyclone/tests/psyir/nodes/reference_test.py b/src/psyclone/tests/psyir/nodes/reference_test.py index 7036fce1c9..baf4022949 100644 --- a/src/psyclone/tests/psyir/nodes/reference_test.py +++ b/src/psyclone/tests/psyir/nodes/reference_test.py @@ -583,6 +583,7 @@ def test_reference_is_read(fortran_reader): code = """subroutine my_subroutine() b = a call somecall(c) + b = lbound(d) end subroutine""" psyir = fortran_reader.psyir_from_source(code) @@ -591,6 +592,8 @@ def test_reference_is_read(fortran_reader): assert references[1].is_read assert references[3].symbol.name == "c" assert references[3].is_read + assert references[6].symbol.name == "d" + assert not references[6].is_read def test_reference_is_write(fortran_reader): From c17a22dc52ead9cd14814461c0c0d1a35e7f3d9d Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 4 Feb 2025 15:35:39 +0000 Subject: [PATCH 42/62] Linting [skip-ci] --- .../tests/psyir/transformations/scalarization_trans_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 17f0c40f10..ab7e6ce506 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -620,7 +620,7 @@ def test_scalarization_trans_apply_routinesymbol(fortran_reader, def test_noscalarize(fortran_reader, fortran_writer): '''TODO''' - code =''' + code = ''' subroutine test integer :: i, j, k real , dimension(1:1000, 1:1000, 1:5) :: arr From a0e7f2b9743f9c31d0361c68914df1a8ebd547e6 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 4 Feb 2025 16:01:35 +0000 Subject: [PATCH 43/62] Change to handle inquiry functions in definitionusechains --- .../psyir/tools/definition_use_chains.py | 18 +++++++++----- ...ion_use_chains_backward_dependence_test.py | 24 +++++++++++++++++++ ...tion_use_chains_forward_dependence_test.py | 22 +++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/psyclone/psyir/tools/definition_use_chains.py b/src/psyclone/psyir/tools/definition_use_chains.py index c480b625cf..7bdb89f056 100644 --- a/src/psyclone/psyir/tools/definition_use_chains.py +++ b/src/psyclone/psyir/tools/definition_use_chains.py @@ -458,6 +458,11 @@ def _compute_forward_uses(self, basic_block_list): if defs_out is not None: self._defsout.append(defs_out) return + # If its parent is an inquiry function then its neither + # a read nor write. + if (isinstance(reference.parent, IntrinsicCall) and + reference.parent.is_inquiry): + continue if isinstance(reference, CodeBlock): # CodeBlocks only find symbols, so we can only do as good # as checking the symbol - this means we can get false @@ -534,9 +539,7 @@ def _compute_forward_uses(self, basic_block_list): if defs_out is None: self._uses.append(reference) elif reference.ancestor(Call): - # It has a Call ancestor so assume read/write access - # for now. - # We can do better for IntrinsicCalls realistically. + # Otherwise we assume read/write access for now. if defs_out is not None: self._killed.append(defs_out) defs_out = reference @@ -708,6 +711,11 @@ def _compute_backward_uses(self, basic_block_list): abs_pos = reference.abs_position if abs_pos < self._start_point or abs_pos >= stop_position: continue + # If its parent is an inquiry function then its neither + # a read nor write. + if (isinstance(reference.parent, IntrinsicCall) and + reference.parent.is_inquiry): + continue if isinstance(reference, CodeBlock): # CodeBlocks only find symbols, so we can only do as good # as checking the symbol - this means we can get false @@ -793,9 +801,7 @@ def _compute_backward_uses(self, basic_block_list): if defs_out is None: self._uses.append(reference) elif reference.ancestor(Call): - # It has a Call ancestor so assume read/write access - # for now. - # We can do better for IntrinsicCalls realistically. + # Otherwise we assume read/write access for now. if defs_out is not None: self._killed.append(defs_out) defs_out = reference diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py index 85fa66499a..eb591dd28a 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py @@ -718,3 +718,27 @@ def test_definition_use_chains_backward_accesses_empty_schedules( assert reaches[0] is routine.children[4].rhs.children[1] assert reaches[1] is routine.children[4].rhs.children[0] assert reaches[2] is routine.children[0].lhs + + +def test_definition_use_chains_backward_accesses_inquiry_func( + fortran_reader, +): + '''Coverave to handle the case where we have an inquiry function + accessing the symbol of interest.''' + code = """ + subroutine x() + use some_mod, only: func + integer, dimension(100) :: a + integer :: b + + b = func(lbound(a)) + a = 1 + end subroutine + """ + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.walk(Routine)[0] + chains = DefinitionUseChain( + routine.children[1].lhs + ) + reaches = chains.find_backward_accesses() + assert len(reaches) == 0 diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py index bb122de94a..1213a9f1da 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py @@ -968,3 +968,25 @@ def test_definition_use_chains_forward_accesses_empty_schedules( assert reaches[0] is routine.children[4].rhs.children[0] assert reaches[1] is routine.children[4].rhs.children[1] assert reaches[2] is routine.children[4].lhs + + +def test_definition_use_chains_backward_accesses_inquiry_func( + fortran_reader, +): + '''Coverave to handle the case where we have an inquiry function + accessing the symbol of interest.''' + code = """ + subroutine x() + use some_mod, only: func + integer, dimension(100) :: a + integer :: b + + a = 1 + b = func(lbound(a)) + end subroutine + """ + psyir = fortran_reader.psyir_from_source(code) + routine = psyir.walk(Routine)[0] + chains = DefinitionUseChain(routine.children[0].lhs) + reaches = chains.find_forward_accesses() + assert len(reaches) == 0 From 173093d8364606664c405e6cf978430fe63010a8 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 4 Feb 2025 16:02:23 +0000 Subject: [PATCH 44/62] Linting --- src/psyclone/psyir/tools/definition_use_chains.py | 4 ++-- .../tools/definition_use_chains_forward_dependence_test.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/psyclone/psyir/tools/definition_use_chains.py b/src/psyclone/psyir/tools/definition_use_chains.py index 7bdb89f056..960ebaa1ae 100644 --- a/src/psyclone/psyir/tools/definition_use_chains.py +++ b/src/psyclone/psyir/tools/definition_use_chains.py @@ -461,7 +461,7 @@ def _compute_forward_uses(self, basic_block_list): # If its parent is an inquiry function then its neither # a read nor write. if (isinstance(reference.parent, IntrinsicCall) and - reference.parent.is_inquiry): + reference.parent.is_inquiry): continue if isinstance(reference, CodeBlock): # CodeBlocks only find symbols, so we can only do as good @@ -714,7 +714,7 @@ def _compute_backward_uses(self, basic_block_list): # If its parent is an inquiry function then its neither # a read nor write. if (isinstance(reference.parent, IntrinsicCall) and - reference.parent.is_inquiry): + reference.parent.is_inquiry): continue if isinstance(reference, CodeBlock): # CodeBlocks only find symbols, so we can only do as good diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py index 1213a9f1da..7ff97cc56a 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py @@ -989,4 +989,5 @@ def test_definition_use_chains_backward_accesses_inquiry_func( routine = psyir.walk(Routine)[0] chains = DefinitionUseChain(routine.children[0].lhs) reaches = chains.find_forward_accesses() - assert len(reaches) == 0 + assert len(reaches) == 0 + From 1300fcfd49e315a2da1d057666e4feee6567e9dd Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 4 Feb 2025 16:02:42 +0000 Subject: [PATCH 45/62] Linting --- .../psyir/tools/definition_use_chains_forward_dependence_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py index 7ff97cc56a..a3652a99b8 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py @@ -990,4 +990,3 @@ def test_definition_use_chains_backward_accesses_inquiry_func( chains = DefinitionUseChain(routine.children[0].lhs) reaches = chains.find_forward_accesses() assert len(reaches) == 0 - From 728dd7b71138f1214f09881febc1512f0d7c264c Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Feb 2025 15:00:05 +0000 Subject: [PATCH 46/62] Changes to add less_than to symbolic maths and to handle scalarization cases where the correct behaviour is unclear, so psyclone plays cautious --- src/psyclone/core/symbolic_maths.py | 33 ++++ .../transformations/scalarization_trans.py | 160 +++++++++++++++++- .../tests/core/symbolic_maths_test.py | 20 +++ .../scalarization_trans_test.py | 129 +++++++++++++- 4 files changed, 331 insertions(+), 11 deletions(-) diff --git a/src/psyclone/core/symbolic_maths.py b/src/psyclone/core/symbolic_maths.py index 7d7708281a..fc135414ea 100644 --- a/src/psyclone/core/symbolic_maths.py +++ b/src/psyclone/core/symbolic_maths.py @@ -269,6 +269,39 @@ def greater_than(exp1, exp2, all_variables_positive=None): return SymbolicMaths.Fuzzy.TRUE return SymbolicMaths.Fuzzy.FALSE + @staticmethod + def less_than(exp1, exp2, all_variables_positive=None): + ''' + Determines whether exp1 is, or might be, numerically less than exp2. + + :param exp1: the first expression for the comparison. + :type exp1: :py:class:`psyclone.psyir.nodes.Node` + :param exp1: the second expression for the comparison. + :type exp1: :py:class:`psyclone.psyir.nodes.Node` + :param Optional[bool] all_variables_positive: whether or not to assume + that all variables appearing in either expression are positive + definite. Default is not to make this assumption. + + :returns: whether exp1 is, or might be, numerically less than exp2. + :rtype: :py:class:`psyclone.core.symbolic_maths.Fuzzy` + + ''' + diff_val = SymbolicMaths._subtract( + exp1, exp2, + all_variables_positive=all_variables_positive) + if isinstance(diff_val, core.numbers.Integer): + if diff_val.is_zero or diff_val.is_positive: + return SymbolicMaths.Fuzzy.FALSE + return SymbolicMaths.Fuzzy.TRUE + + # We have some sort of symbolic result + result = diff_val.is_negative + if result is None: + return SymbolicMaths.Fuzzy.MAYBE + if result: + return SymbolicMaths.Fuzzy.TRUE + return SymbolicMaths.Fuzzy.FALSE + # ------------------------------------------------------------------------- @staticmethod def solve_equal_for(exp1, exp2, symbol): diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index a47c8b9f38..d64a704d43 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -36,13 +36,13 @@ '''This module provides the sclarization transformation class.''' import itertools -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, List -from psyclone.core import VariablesAccessInfo, Signature +from psyclone.core import VariablesAccessInfo, Signature, SymbolicMaths from psyclone.psyGen import Kern -from psyclone.psyir.nodes import Call, CodeBlock, \ - Loop, Reference, Routine, StructureReference -from psyclone.psyir.symbols import DataSymbol, RoutineSymbol +from psyclone.psyir.nodes import Call, CodeBlock, Literal, \ + Loop, Node, Range, Reference, Routine, StructureReference +from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, INTEGER_TYPE from psyclone.psyir.transformations.loop_trans import LoopTrans @@ -186,6 +186,57 @@ def _check_first_access_is_write(signature: Signature, return True return False + @staticmethod + def _get_index_values_from_indices( + node: Node, indices: List[Node]) -> tuple[bool, List[Node]]: + ''' + TODO + ''' + index_values = [] + has_complex_index = False + for index in indices: + # If the index is an array or structure and there are any more + # accesses to the signature we're trying to scalarize, then we + # should not scalarize. + if (type(index) is not Range and type(index) is not Reference and + type(index) is not Literal): + has_complex_index = True + index_values.append(None) + + one_literal = Literal("1", INTEGER_TYPE) + ancestor_loop = node.ancestor(Loop) + # For Range or Literal array indices this is easy. + for i, index in enumerate(indices): + if isinstance(index, (Range, Literal)): + index_values[i] = index + + while ancestor_loop is not None and not has_complex_index: + for i, index in enumerate(indices): + # Skip over indices we already set. + if index_values[i] is not None: + continue + if ancestor_loop.variable == index.symbol: + start_val = ancestor_loop.start_expr + stop_val = ancestor_loop.stop_expr + step_val = ancestor_loop.step_expr + # If the step value is not exactly 1 then we treat + # this as a complex index, as we can't currently + # do precise comparisons on non-unit stride accesses. + if step_val != one_literal: + has_complex_index = True + # Create a range for this and add it to the index values. + index_range = Range.create(start_val.copy(), stop_val.copy()) + index_values[i] = index_range + ancestor_loop = ancestor_loop.ancestor(Loop) + + # If we couldn't work out any of the index_values, then we treat this + # as a complex index + for index in index_values: + if index is None: + has_complex_index = True + + return has_complex_index, index_values + @staticmethod def _value_unused_after_loop(sig: Signature, loop: Loop, @@ -198,16 +249,33 @@ def _value_unused_after_loop(sig: Signature, :returns: whether the value computed in the loop containing sig is read from after the loop. ''' + routine_var_accesses = None # Find the last access of the signature last_access = var_accesses[sig].all_accesses[-1].node + # Compute the indices used in this loop. We know that all of the + # indices used in this loop must be the same. + indices = last_access.indices + # Find the next accesses to this symbol next_accesses = last_access.next_accesses() + + # Compute the indices ranges. + has_complex_index, index_values = \ + ScalarizationTrans._get_index_values_from_indices( + last_access, indices + ) + for next_access in next_accesses: # next_accesses looks backwards to the start of the loop, # but we don't care about those accesses here. if next_access.is_descendent_of(loop): continue + # If we have a next_access outside of the loop and have a complex + # index then we do not scalarize this at the moment. + if has_complex_index: + return False + # If next access is a Call or CodeBlock or Kern then # we have to assume the value is used. These nodes don't # have the is_read property that Reference has, so we need @@ -219,6 +287,86 @@ def _value_unused_after_loop(sig: Signature, if next_access.is_read: return False + # We need to ensure that the following write accesses the same + # or more of the array. + next_indices = next_access.indices + next_complex_index, next_values = \ + ScalarizationTrans._get_index_values_from_indices( + next_access, next_indices + ) + # If we can't compute the indices of the next access then we + # cannot scalarize + if next_complex_index: + return False + # If the two accesses don't have the same number of indices then + # we won't scalarize - FIXME Can this happen? + if len(next_values) != len(index_values): + return False + # Check the indices of next_access are greater than or equal to + # that of the potential scalarization. + valid_indexes = True + for i in range(len(next_values)): + # If the next index is a full range we can skip it as it must + # cover the previous access + if next_access.is_full_range(i): + continue + # Convert both to ranges if either was a literal + next_index = next_values[i] + orig_index = index_values[i] + if not isinstance(next_index, Range): + next_index = Range.create(next_index.copy(), + next_index.copy()) + if not isinstance(orig_index, Range): + orig_index = Range.create(orig_index.copy(), + orig_index.copy()) + sm = SymbolicMaths.get() + # Need to check that next_index stop point is >= orig_index. + # If its not then this can't cover the full range so we can + # return False to not Scalarize this. + if not (sm.greater_than(next_index.stop, orig_index.stop) + == SymbolicMaths.Fuzzy.TRUE or + sm.equal(next_index.stop, orig_index.stop)): + return False + # Need to check the next_index start point is <= orig_index + if not (sm.less_than(next_index.start, orig_index.start) + == SymbolicMaths.Fuzzy.TRUE or + sm.equal(next_index.start, orig_index.start)): + return False + # If either of the start of stop points of the original + # access range are a reference, we need to make sure that + # reference has not been written to between the locations. + if (isinstance(next_index.stop, Reference)): + # Find the containing Routine + if(routine_var_accesses is None): + routine = loop.ancestor(Routine) + routine_var_accesses = VariablesAccessInfo( + nodes=routine + ) + stop_sig = Signature(next_index.stop.symbol.name) + if not routine_var_accesses[stop_sig].is_read_only(): + stop_savi = routine_var_accesses[stop_sig] + print(type(stop_savi)) + for access in stop_savi.all_write_accesses: + pos = access.node.abs_position + if (pos > loop.abs_position and + pos < next_access.abs_position): + return False + if (isinstance(next_index.start, Reference)): + # Find the containing Routine + if(routine_var_accesses is None): + routine = loop.ancestor(Routine) + routine_var_accesses = VariablesAccessInfo( + nodes=routine + ) + start_sig = Signature(next_index.start.symbol.name) + if not routine_var_accesses[start_sig].is_read_only(): + start_savi = routine_var_accesses[start_sig] + for access in start_savi.all_write_accesses: + pos = access.node.abs_position + if (pos > loop.abs_position and + pos < next_access.abs_position): + return False + return True def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ @@ -257,7 +405,7 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ var_accesses = VariablesAccessInfo(nodes=node.loop_body) - # Find all the ararys that are only accessed by a single index, and + # Find all the arrays that are only accessed by a single index, and # that index is only read inside the loop. potential_targets = filter( lambda sig: diff --git a/src/psyclone/tests/core/symbolic_maths_test.py b/src/psyclone/tests/core/symbolic_maths_test.py index b38e8570d8..8d20aca804 100644 --- a/src/psyclone/tests/core/symbolic_maths_test.py +++ b/src/psyclone/tests/core/symbolic_maths_test.py @@ -279,6 +279,26 @@ def test_symbolic_maths_greater_than( all_variables_positive=positive) == result +@pytest.mark.parametrize("exp1, exp2, positive, result", + [("i", "j", False, SymbolicMaths.Fuzzy.MAYBE), + ("i+1", "i", False, SymbolicMaths.Fuzzy.FALSE), + ("i+j", "i", False, SymbolicMaths.Fuzzy.MAYBE), + ("i+j", "i", True, SymbolicMaths.Fuzzy.FALSE), + ("2*i", "i", True, SymbolicMaths.Fuzzy.FALSE), + ("i", "2*i", True, SymbolicMaths.Fuzzy.TRUE), + ("i", "i+1", False, SymbolicMaths.Fuzzy.TRUE)]) +def test_symbolic_maths_less_than( + fortran_reader, exp1, exp2, positive, result): + ''' + Tests for the greater_than() method. + ''' + sym_maths = SymbolicMaths.get() + ir1 = fortran_reader.psyir_from_expression(exp1) + ir2 = fortran_reader.psyir_from_expression(exp2) + assert sym_maths.less_than(ir1, ir2, + all_variables_positive=positive) == result + + @pytest.mark.parametrize("exp1, exp2, result", [("i", "2*i+1", set([-1])), # Infinite solutions (i is any # integer) are returned as diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index ab7e6ce506..b156b137fd 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -37,6 +37,7 @@ ''' from psyclone.core import VariablesAccessInfo +from psyclone.psyir.nodes import Loop from psyclone.psyir.transformations import ScalarizationTrans from psyclone.tests.utilities import Compile @@ -618,8 +619,11 @@ def test_scalarization_trans_apply_routinesymbol(fortran_reader, assert Compile(tmpdir).string_compiles(out) -def test_noscalarize(fortran_reader, fortran_writer): - '''TODO''' +def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): + ''' + Test that the scalarization transformation won't scalarize some patterns + we expect to not be scalarized. + ''' code = ''' subroutine test integer :: i, j, k @@ -637,11 +641,126 @@ def test_noscalarize(fortran_reader, fortran_writer): ''' strans = ScalarizationTrans() psyir = fortran_reader.psyir_from_source(code) - from psyclone.psyir.nodes import Loop loops = psyir.walk(Loop) for loop in loops: strans.apply(loop) out = fortran_writer(psyir) - print(out) assert "arr_scalar" not in out - assert False + + code = ''' + subroutine test + integer :: i, j, k, l + real, dimension(1:1000, 1:1000, 1:5) :: arr + l = 100 + + do i = 1, l + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + l = 50 + do i = 1, l + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + strans.apply(loops[0]) + out = fortran_writer(psyir) + assert "arr_scalar" not in out + + code = ''' + subroutine test + integer :: i, j, k, l + real, dimension(1:1000, 1:1000, 1:5) :: arr + l = 1 + + do i = l, 1000 + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + l = 50 + do i = l, 1000 + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + strans.apply(loops[0]) + out = fortran_writer(psyir) + assert "arr_scalar" not in out + + code = ''' + subroutine test + integer :: i, j, k, l + real, dimension(1:1000, 1:1000, 1:5) :: arr + l = 50 + + do i = 51, 1000 + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + do i = l, 1000 + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + strans.apply(loops[0]) + out = fortran_writer(psyir) + assert "arr_scalar" not in out + + code = ''' + subroutine test + integer :: i, j, k, l + real, dimension(1:1000, 1:1000, 1:5) :: arr + l = 950 + + do i = 51, 1000 + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + do i = 1, l + do j = 1, 1000 + do k = 1,5 + arr(i,j,k) = 0.0 + end do + end do + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + strans.apply(loops[0]) + out = fortran_writer(psyir) + assert "arr_scalar" not in out From 4d4fbc940751697c4af40fba70f421ea7a5c6312 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Feb 2025 15:03:18 +0000 Subject: [PATCH 47/62] linting --- .../transformations/scalarization_trans.py | 49 ++++++++++--------- .../tests/core/symbolic_maths_test.py | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index d64a704d43..7d68f3c701 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -225,7 +225,10 @@ def _get_index_values_from_indices( if step_val != one_literal: has_complex_index = True # Create a range for this and add it to the index values. - index_range = Range.create(start_val.copy(), stop_val.copy()) + index_range = Range.create( + start_val.copy(), + stop_val.copy() + ) index_values[i] = index_range ancestor_loop = ancestor_loop.ancestor(Loop) @@ -261,9 +264,9 @@ def _value_unused_after_loop(sig: Signature, # Compute the indices ranges. has_complex_index, index_values = \ - ScalarizationTrans._get_index_values_from_indices( + ScalarizationTrans._get_index_values_from_indices( last_access, indices - ) + ) for next_access in next_accesses: # next_accesses looks backwards to the start of the loop, @@ -291,9 +294,9 @@ def _value_unused_after_loop(sig: Signature, # or more of the array. next_indices = next_access.indices next_complex_index, next_values = \ - ScalarizationTrans._get_index_values_from_indices( + ScalarizationTrans._get_index_values_from_indices( next_access, next_indices - ) + ) # If we can't compute the indices of the next access then we # cannot scalarize if next_complex_index: @@ -304,7 +307,6 @@ def _value_unused_after_loop(sig: Signature, return False # Check the indices of next_access are greater than or equal to # that of the potential scalarization. - valid_indexes = True for i in range(len(next_values)): # If the next index is a full range we can skip it as it must # cover the previous access @@ -324,7 +326,7 @@ def _value_unused_after_loop(sig: Signature, # If its not then this can't cover the full range so we can # return False to not Scalarize this. if not (sm.greater_than(next_index.stop, orig_index.stop) - == SymbolicMaths.Fuzzy.TRUE or + == SymbolicMaths.Fuzzy.TRUE or sm.equal(next_index.stop, orig_index.stop)): return False # Need to check the next_index start point is <= orig_index @@ -335,37 +337,36 @@ def _value_unused_after_loop(sig: Signature, # If either of the start of stop points of the original # access range are a reference, we need to make sure that # reference has not been written to between the locations. - if (isinstance(next_index.stop, Reference)): + if isinstance(next_index.stop, Reference): # Find the containing Routine - if(routine_var_accesses is None): + if routine_var_accesses is None: routine = loop.ancestor(Routine) routine_var_accesses = VariablesAccessInfo( nodes=routine ) stop_sig = Signature(next_index.stop.symbol.name) if not routine_var_accesses[stop_sig].is_read_only(): - stop_savi = routine_var_accesses[stop_sig] - print(type(stop_savi)) - for access in stop_savi.all_write_accesses: - pos = access.node.abs_position - if (pos > loop.abs_position and - pos < next_access.abs_position): - return False - if (isinstance(next_index.start, Reference)): + stop_savi = routine_var_accesses[stop_sig] + for access in stop_savi.all_write_accesses: + pos = access.node.abs_position + if (pos > loop.abs_position and + pos < next_access.abs_position): + return False + if isinstance(next_index.start, Reference): # Find the containing Routine - if(routine_var_accesses is None): + if routine_var_accesses is None: routine = loop.ancestor(Routine) routine_var_accesses = VariablesAccessInfo( nodes=routine ) start_sig = Signature(next_index.start.symbol.name) if not routine_var_accesses[start_sig].is_read_only(): - start_savi = routine_var_accesses[start_sig] - for access in start_savi.all_write_accesses: - pos = access.node.abs_position - if (pos > loop.abs_position and - pos < next_access.abs_position): - return False + start_savi = routine_var_accesses[start_sig] + for access in start_savi.all_write_accesses: + pos = access.node.abs_position + if (pos > loop.abs_position and + pos < next_access.abs_position): + return False return True diff --git a/src/psyclone/tests/core/symbolic_maths_test.py b/src/psyclone/tests/core/symbolic_maths_test.py index 8d20aca804..00d96a3c3d 100644 --- a/src/psyclone/tests/core/symbolic_maths_test.py +++ b/src/psyclone/tests/core/symbolic_maths_test.py @@ -296,7 +296,7 @@ def test_symbolic_maths_less_than( ir1 = fortran_reader.psyir_from_expression(exp1) ir2 = fortran_reader.psyir_from_expression(exp2) assert sym_maths.less_than(ir1, ir2, - all_variables_positive=positive) == result + all_variables_positive=positive) == result @pytest.mark.parametrize("exp1, exp2, result", [("i", "2*i+1", set([-1])), From 02de511de8075a25f87ade1a57de39dc9335ace9 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Feb 2025 15:08:17 +0000 Subject: [PATCH 48/62] Swapping non-3.8 compatible type hint --- src/psyclone/psyir/transformations/scalarization_trans.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 7d68f3c701..54b8332258 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -36,7 +36,7 @@ '''This module provides the sclarization transformation class.''' import itertools -from typing import Optional, Dict, Any, List +from typing import Optional, Dict, Any, List, Tuple from psyclone.core import VariablesAccessInfo, Signature, SymbolicMaths from psyclone.psyGen import Kern @@ -188,7 +188,7 @@ def _check_first_access_is_write(signature: Signature, @staticmethod def _get_index_values_from_indices( - node: Node, indices: List[Node]) -> tuple[bool, List[Node]]: + node: Node, indices: List[Node]) -> Tuple[bool, List[Node]]: ''' TODO ''' From 37d92842ebe7d618b229466abec951dee205084f Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Feb 2025 15:44:53 +0000 Subject: [PATCH 49/62] Fixing coverage --- .../transformations/scalarization_trans.py | 61 ++++----- .../scalarization_trans_test.py | 125 ++++++++++++++++++ 2 files changed, 156 insertions(+), 30 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 54b8332258..c73329f7d1 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -337,36 +337,37 @@ def _value_unused_after_loop(sig: Signature, # If either of the start of stop points of the original # access range are a reference, we need to make sure that # reference has not been written to between the locations. - if isinstance(next_index.stop, Reference): - # Find the containing Routine - if routine_var_accesses is None: - routine = loop.ancestor(Routine) - routine_var_accesses = VariablesAccessInfo( - nodes=routine - ) - stop_sig = Signature(next_index.stop.symbol.name) - if not routine_var_accesses[stop_sig].is_read_only(): - stop_savi = routine_var_accesses[stop_sig] - for access in stop_savi.all_write_accesses: - pos = access.node.abs_position - if (pos > loop.abs_position and - pos < next_access.abs_position): - return False - if isinstance(next_index.start, Reference): - # Find the containing Routine - if routine_var_accesses is None: - routine = loop.ancestor(Routine) - routine_var_accesses = VariablesAccessInfo( - nodes=routine - ) - start_sig = Signature(next_index.start.symbol.name) - if not routine_var_accesses[start_sig].is_read_only(): - start_savi = routine_var_accesses[start_sig] - for access in start_savi.all_write_accesses: - pos = access.node.abs_position - if (pos > loop.abs_position and - pos < next_access.abs_position): - return False + # FIXME Commented these out at they don't seem needed. + #if isinstance(next_index.stop, Reference): + # # Find the containing Routine + # if routine_var_accesses is None: + # routine = loop.ancestor(Routine) + # routine_var_accesses = VariablesAccessInfo( + # nodes=routine + # ) + # stop_sig = Signature(next_index.stop.symbol.name) + # if not routine_var_accesses[stop_sig].is_read_only(): + # stop_savi = routine_var_accesses[stop_sig] + # for access in stop_savi.all_write_accesses: + # pos = access.node.abs_position + # if (pos > loop.abs_position and + # pos < next_access.abs_position): + # return False + #if isinstance(next_index.start, Reference): + # # Find the containing Routine + # if routine_var_accesses is None: + # routine = loop.ancestor(Routine) + # routine_var_accesses = VariablesAccessInfo( + # nodes=routine + # ) + # start_sig = Signature(next_index.start.symbol.name) + # if not routine_var_accesses[start_sig].is_read_only(): + # start_savi = routine_var_accesses[start_sig] + # for access in start_savi.all_write_accesses: + # pos = access.node.abs_position + # if (pos > loop.abs_position and + # pos < next_access.abs_position): + # return False return True diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index b156b137fd..353accc238 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -480,6 +480,108 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): node.loop_body, var_accesses) + # Test having a non-unit stride prevents scalarization + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + real, dimension(1:100) :: arr + + do i = 1, 100, 2 + arr(i) = exp(arr(i)) + end do + do i = 1, 100 + arr(i) = 1 + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, + var_accesses) + + # Test having a loop bound as a structure element prevents scalarization + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + real, dimension(1:100) :: arr + + do i = 1, ele%stop + arr(i) = exp(arr(i)) + end do + do i = 1, 100 + arr(i) = 1 + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, + var_accesses) + + # Test having an index as a reference to a non-loop variable prevents + # scalarization + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + real, dimension(1:100) :: arr + + do i = 1, 100 + arr(k) = arr(k) + exp(arr(i)) + end do + do i = 1, 100 + arr(i) = 1 + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, + var_accesses) + + # Test that the next access having a non-unit stride prevents + # scalarization + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + real, dimension(1:100) :: arr + + do i = 1, 100 + arr(i) = exp(arr(i)) + end do + do i = 1, 100, 2 + arr(i) = 1 + end do + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert not ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, + var_accesses) + def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' @@ -647,6 +749,29 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): out = fortran_writer(psyir) assert "arr_scalar" not in out + code = ''' + subroutine test + integer :: i, j, k + real , dimension(1:1000, 1:1000, 1:5) :: arr + + do i = 1,1000 + do j = 1,1000 + do k = 2,5 + arr(i,j,k) = 0.0 + end do + end do + end do + arr(:,:,3:5) = 0.0 + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + for loop in loops: + strans.apply(loop) + out = fortran_writer(psyir) + assert "arr_scalar" not in out + code = ''' subroutine test integer :: i, j, k, l From 9e7c9b89aa1464714410f07c62f6ec1ae3dc4c12 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Feb 2025 15:46:10 +0000 Subject: [PATCH 50/62] linting --- .../transformations/scalarization_trans.py | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index c73329f7d1..a0706fad7b 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -252,7 +252,8 @@ def _value_unused_after_loop(sig: Signature, :returns: whether the value computed in the loop containing sig is read from after the loop. ''' - routine_var_accesses = None + # FIXME remove + # routine_var_accesses = None # Find the last access of the signature last_access = var_accesses[sig].all_accesses[-1].node # Compute the indices used in this loop. We know that all of the @@ -338,36 +339,36 @@ def _value_unused_after_loop(sig: Signature, # access range are a reference, we need to make sure that # reference has not been written to between the locations. # FIXME Commented these out at they don't seem needed. - #if isinstance(next_index.stop, Reference): - # # Find the containing Routine - # if routine_var_accesses is None: - # routine = loop.ancestor(Routine) - # routine_var_accesses = VariablesAccessInfo( - # nodes=routine - # ) - # stop_sig = Signature(next_index.stop.symbol.name) - # if not routine_var_accesses[stop_sig].is_read_only(): - # stop_savi = routine_var_accesses[stop_sig] - # for access in stop_savi.all_write_accesses: - # pos = access.node.abs_position - # if (pos > loop.abs_position and - # pos < next_access.abs_position): - # return False - #if isinstance(next_index.start, Reference): - # # Find the containing Routine - # if routine_var_accesses is None: - # routine = loop.ancestor(Routine) - # routine_var_accesses = VariablesAccessInfo( - # nodes=routine - # ) - # start_sig = Signature(next_index.start.symbol.name) - # if not routine_var_accesses[start_sig].is_read_only(): - # start_savi = routine_var_accesses[start_sig] - # for access in start_savi.all_write_accesses: - # pos = access.node.abs_position - # if (pos > loop.abs_position and - # pos < next_access.abs_position): - # return False + # if isinstance(next_index.stop, Reference): + # # Find the containing Routine + # if routine_var_accesses is None: + # routine = loop.ancestor(Routine) + # routine_var_accesses = VariablesAccessInfo( + # nodes=routine + # ) + # stop_sig = Signature(next_index.stop.symbol.name) + # if not routine_var_accesses[stop_sig].is_read_only(): + # stop_savi = routine_var_accesses[stop_sig] + # for access in stop_savi.all_write_accesses: + # pos = access.node.abs_position + # if (pos > loop.abs_position and + # pos < next_access.abs_position): + # return False + # if isinstance(next_index.start, Reference): + # # Find the containing Routine + # if routine_var_accesses is None: + # routine = loop.ancestor(Routine) + # routine_var_accesses = VariablesAccessInfo( + # nodes=routine + # ) + # start_sig = Signature(next_index.start.symbol.name) + # if not routine_var_accesses[start_sig].is_read_only(): + # start_savi = routine_var_accesses[start_sig] + # for access in start_savi.all_write_accesses: + # pos = access.node.abs_position + # if (pos > loop.abs_position and + # pos < next_access.abs_position): + # return False return True From e02b91493bf0c4f100d58abe2bb7633221e916a4 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 17 Feb 2025 18:17:45 +0000 Subject: [PATCH 51/62] Changes to fix coverage! --- .../transformations/scalarization_trans.py | 10 ++-- .../scalarization_trans_test.py | 50 ++++++++++++++++++- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index a0706fad7b..76bb2d02dd 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -291,6 +291,12 @@ def _value_unused_after_loop(sig: Signature, if next_access.is_read: return False + # If the next access is a Reference then we had a full range + # access described without any range, which means a full + # range access so we can skip the followup checks. + if type(next_access) is Reference: + continue + # We need to ensure that the following write accesses the same # or more of the array. next_indices = next_access.indices @@ -302,10 +308,6 @@ def _value_unused_after_loop(sig: Signature, # cannot scalarize if next_complex_index: return False - # If the two accesses don't have the same number of indices then - # we won't scalarize - FIXME Can this happen? - if len(next_values) != len(index_values): - return False # Check the indices of next_access are greater than or equal to # that of the potential scalarization. for i in range(len(next_values)): diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 353accc238..cdda34528b 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -512,8 +512,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): integer :: k real, dimension(1:100) :: arr - do i = 1, ele%stop - arr(i) = exp(arr(i)) + do i = 1, 100 + arr(ele%stop) = arr(ele%stop) + exp(arr(i)) end do do i = 1, 100 arr(i) = 1 @@ -582,6 +582,52 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): node.loop_body, var_accesses) + # Test that the next access being a Reference allows scalarization + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + real, dimension(1:100) :: arr + + do i = 1, 100 + arr(i) = exp(arr(i)) + end do + arr = 1 + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, + var_accesses) + + # Test that having a scalar array index doesn't prevent scalarization + code = '''subroutine test() + use my_mod + integer :: i + integer :: k + real, dimension(1:100, 1:5) :: arr + + do i = 1, 100 + arr(i, 1) = exp(arr(i, 1)) + end do + arr(1:100, 1:5) = 1 + end subroutine test + ''' + psyir = fortran_reader.psyir_from_source(code) + node = psyir.children[0].children[0] + var_accesses = VariablesAccessInfo(nodes=node.loop_body) + keys = list(var_accesses.keys()) + # Test arr + assert var_accesses[keys[1]].var_name == "arr" + assert ScalarizationTrans._value_unused_after_loop(keys[1], + node.loop_body, + var_accesses) + def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' From 056f577ff0cec6162966e016384346fa80fec8d0 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 18 Feb 2025 10:37:37 +0000 Subject: [PATCH 52/62] Test fix to ensure it tests the expected part of the code --- .../tests/psyir/transformations/scalarization_trans_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index cdda34528b..a0b92fc5ce 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -615,7 +615,7 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): do i = 1, 100 arr(i, 1) = exp(arr(i, 1)) end do - arr(1:100, 1:5) = 1 + arr(1:100, 1:4) = 1 end subroutine test ''' psyir = fortran_reader.psyir_from_source(code) @@ -627,8 +627,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): assert ScalarizationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) - - + + def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' code = '''subroutine test() From b766dfb1e9e2bb024237c2ec2d37f9974a63be69 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 18 Feb 2025 10:42:02 +0000 Subject: [PATCH 53/62] linting --- .../tests/psyir/transformations/scalarization_trans_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index a0b92fc5ce..2462fd1ed1 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -627,8 +627,8 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): assert ScalarizationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) - - + + def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): ''' Test the application of the scalarization transformation.''' code = '''subroutine test() From 756e152c64c627679a1db7416e7e3b4e207b848e Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 24 Feb 2025 09:55:12 +0000 Subject: [PATCH 54/62] Moving scalarization test into the cpu transformation script --- examples/nemo/scripts/omp_cpu_trans.py | 7 +++++++ examples/nemo/scripts/passthrough.py | 12 ------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index bca7690b5d..73a9ed8e14 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -44,6 +44,8 @@ PRIVATISATION_ISSUES) from psyclone.psyir.nodes import Routine from psyclone.transformations import OMPLoopTrans +# TODO Remove +from psyclone.psyir.transformations import ScalarizationTrans # Enable the insertion of profiling hooks during the transformation script PROFILING_ENABLED = False @@ -72,6 +74,11 @@ def trans(psyir): :type psyir: :py:class:`psyclone.psyir.nodes.FileContainer` ''' + # TODO Remove + scalartrans = ScalarizationTrans() + for subroutine in psyir.walk(Routine): + for loop in subroutine.walk(Loop): + scalartrans.apply(loop) # If the environemnt has ONLY_FILE defined, only process that one file and # nothing else. This is useful for file-by-file exhaustive tests. diff --git a/examples/nemo/scripts/passthrough.py b/examples/nemo/scripts/passthrough.py index 7d2987c597..4b0782c0b9 100755 --- a/examples/nemo/scripts/passthrough.py +++ b/examples/nemo/scripts/passthrough.py @@ -37,21 +37,9 @@ ''' Process Nemo code with PSyclone but don't do any changes. This file is only needed to provide a FILES_TO_SKIP list. ''' -# TODO Remove -from psyclone.psyir.transformations import ScalarizationTrans -from psyclone.psyir.nodes import Routine, Loop - # List of all files that psyclone will skip processing FILES_TO_SKIP = [] def trans(psyir): ''' Don't do any changes. ''' - if psyir.name.startswith("obs_"): - print("Skipping file", psyir.name) - return - # TODO REMOVE - for subroutine in psyir.walk(Routine): - scalartrans = ScalarizationTrans() - for loop in subroutine.walk(Loop): - scalartrans.apply(loop) From a6d2faee58de91dfb09fefe75cd3db3105fdcc2c Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 24 Feb 2025 10:10:49 +0000 Subject: [PATCH 55/62] linting error --- examples/nemo/scripts/omp_cpu_trans.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index 73a9ed8e14..b632d16b62 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -46,6 +46,7 @@ from psyclone.transformations import OMPLoopTrans # TODO Remove from psyclone.psyir.transformations import ScalarizationTrans +from psyclone.psyir.nodes import Loop # Enable the insertion of profiling hooks during the transformation script PROFILING_ENABLED = False From bf3eb0b0f284ee48d4104ddafaff90be53ee632e Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 25 Feb 2025 14:20:59 +0000 Subject: [PATCH 56/62] Fix bug when there is a write inside a conditional statement --- .../scalarization_trans_test.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index 2462fd1ed1..f012aa281b 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -180,14 +180,17 @@ def test_scalarizationtrans_check_first_access_is_write(fortran_reader): # Test a assert var_accesses[keys[1]].var_name == "a" assert ScalarizationTrans._check_first_access_is_write(keys[1], + node, var_accesses) # Test b (differeing indices) assert var_accesses[keys[2]].var_name == "b" assert not ScalarizationTrans._check_first_access_is_write(keys[2], + node, var_accesses) # Test c (k is modified) assert var_accesses[keys[3]].var_name == "c" assert ScalarizationTrans._check_first_access_is_write(keys[3], + node, var_accesses) # Test filter behaviour same as used in the transformation @@ -206,7 +209,7 @@ def test_scalarizationtrans_check_first_access_is_write(fortran_reader): first_write_arrays = filter( lambda sig: ScalarizationTrans._check_first_access_is_write( - sig, var_accesses), + sig, node, var_accesses), unmodified_indices) first_write_arrays = list(first_write_arrays) assert len(first_write_arrays) == 2 @@ -935,3 +938,30 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): strans.apply(loops[0]) out = fortran_writer(psyir) assert "arr_scalar" not in out + + code = ''' + subroutine test + use mod + integer :: i + real, dimension(1:1000) :: arr + do i = 1, 1000 + arr(i) = 0.0 + end do + + do i = 1, 1000 + if(zeqn > 0.0) then + arr(i) = prev + else + arr2(i) = prev + endif + val = sqrt(arr(i)) + prev = val + end do + end subroutine + ''' + strans = ScalarizationTrans() + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + strans.apply(loops[1]) + out = fortran_writer(psyir) + assert "arr_scalar" not in out From 75b7ca663d76ad79b5bd36a3c0da42318c96b6a1 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 25 Feb 2025 14:37:17 +0000 Subject: [PATCH 57/62] Missed a file --- .../transformations/scalarization_trans.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 76bb2d02dd..417bab774b 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -41,7 +41,7 @@ from psyclone.core import VariablesAccessInfo, Signature, SymbolicMaths from psyclone.psyGen import Kern from psyclone.psyir.nodes import Call, CodeBlock, Literal, \ - Loop, Node, Range, Reference, Routine, StructureReference + IfBlock, Loop, Node, Range, Reference, Routine, StructureReference from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, INTEGER_TYPE from psyclone.psyir.transformations.loop_trans import LoopTrans @@ -174,17 +174,27 @@ def _have_same_unmodified_index( @staticmethod def _check_first_access_is_write(signature: Signature, + loop: Loop, var_accesses: VariablesAccessInfo) \ -> bool: ''' :param signature: The signature to check. + :param loop: The Loop object being transformed. :param var_accesses: The VariableAccessesInfo object containing signature. :returns: whether the first access to signature is a write. ''' - if var_accesses[signature].is_written_first(): - return True - return False + if not var_accesses[signature].is_written_first(): + return False + # Need to find the first access and check if its in a conditional. + accesses = var_accesses[signature].all_accesses + first_node = accesses[0].node + ifblock = first_node.ancestor(IfBlock) + # If the depth of the ifblock is larger than loop then the write + # is in a conditional + if ifblock and ifblock.depth > loop.depth: + return False + return True @staticmethod def _get_index_values_from_indices( @@ -427,6 +437,7 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ potential_targets = filter( lambda sig: ScalarizationTrans._check_first_access_is_write(sig, + node, var_accesses), potential_targets) From 80dba445483ff5e3c90599b8aa3989391dfaab04 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 26 Feb 2025 11:06:09 +0000 Subject: [PATCH 58/62] Cleaned up the code ready for review --- examples/nemo/scripts/omp_cpu_trans.py | 4 +- src/psyclone/psyir/nodes/reference.py | 7 ++- .../psyir/tools/definition_use_chains.py | 10 ++-- .../transformations/scalarization_trans.py | 60 +++++++------------ .../tests/psyir/nodes/reference_test.py | 6 +- 5 files changed, 40 insertions(+), 47 deletions(-) diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index b632d16b62..8f3c8bc704 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -77,8 +77,10 @@ def trans(psyir): ''' # TODO Remove scalartrans = ScalarizationTrans() + loops = subroutine.walk(Loop) + loops.reverse() for subroutine in psyir.walk(Routine): - for loop in subroutine.walk(Loop): + for loop in loops: scalartrans.apply(loop) # If the environemnt has ONLY_FILE defined, only process that one file and diff --git a/src/psyclone/psyir/nodes/reference.py b/src/psyclone/psyir/nodes/reference.py index b658d9d6e8..3a7960fdd5 100644 --- a/src/psyclone/psyir/nodes/reference.py +++ b/src/psyclone/psyir/nodes/reference.py @@ -109,9 +109,10 @@ def is_read(self): return False # If we have an intrinsic call parent then we need to check if its - # an inquiry. Inquiry functions don't read from their arguments. - if isinstance(parent, IntrinsicCall) and parent.is_inquiry: - return False + # an inquiry. Inquiry functions don't read from their first argument. + if isinstance(parent, IntrinsicCall): + if parent.arguments[0] is self and parent.is_inquiry: + return False # All references other than LHS of assignments represent a read. This # can be improved in the future by looking at Call intents. diff --git a/src/psyclone/psyir/tools/definition_use_chains.py b/src/psyclone/psyir/tools/definition_use_chains.py index 960ebaa1ae..99fd3a9684 100644 --- a/src/psyclone/psyir/tools/definition_use_chains.py +++ b/src/psyclone/psyir/tools/definition_use_chains.py @@ -459,9 +459,10 @@ def _compute_forward_uses(self, basic_block_list): self._defsout.append(defs_out) return # If its parent is an inquiry function then its neither - # a read nor write. + # a read nor write if its the first argument. if (isinstance(reference.parent, IntrinsicCall) and - reference.parent.is_inquiry): + reference.parent.is_inquiry and + reference.parent.arguments[0] is reference): continue if isinstance(reference, CodeBlock): # CodeBlocks only find symbols, so we can only do as good @@ -712,9 +713,10 @@ def _compute_backward_uses(self, basic_block_list): if abs_pos < self._start_point or abs_pos >= stop_position: continue # If its parent is an inquiry function then its neither - # a read nor write. + # a read nor write if its the first argument. if (isinstance(reference.parent, IntrinsicCall) and - reference.parent.is_inquiry): + reference.parent.is_inquiry and + reference.parent.arguments[0] is reference): continue if isinstance(reference, CodeBlock): # CodeBlocks only find symbols, so we can only do as good diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index 417bab774b..a2b18934ab 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -42,6 +42,7 @@ from psyclone.psyGen import Kern from psyclone.psyir.nodes import Call, CodeBlock, Literal, \ IfBlock, Loop, Node, Range, Reference, Routine, StructureReference +from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.symbols import DataSymbol, RoutineSymbol, INTEGER_TYPE from psyclone.psyir.transformations.loop_trans import LoopTrans @@ -198,9 +199,28 @@ def _check_first_access_is_write(signature: Signature, @staticmethod def _get_index_values_from_indices( - node: Node, indices: List[Node]) -> Tuple[bool, List[Node]]: + node: ArrayMixin, indices: List[Node]) -> Tuple[bool, List[Node]]: ''' - TODO + Compute a list of index values for a given node. Looks at loop bounds + and range declarations to attempt to convert loop variables to an + explicit range, i.e. an access like + >>> do i = 1, 100 + array(i) = ... + end do + + the returned list would contain a range object for [1:100]. + + If the computed indexes contains a non-unit stride, or an index is + not a Range, Reference or Literal then this function will return + True as the first element of the returned tuple, and the list of + indices will be incomplete. + + :param node: The node to compute index values for. + :param indices: the list of indexes to have values computed. + + :returns: a tuple containing a bool value set to True if any of the + index values are not computed, and a list of the computed + index values. ''' index_values = [] has_complex_index = False @@ -262,8 +282,6 @@ def _value_unused_after_loop(sig: Signature, :returns: whether the value computed in the loop containing sig is read from after the loop. ''' - # FIXME remove - # routine_var_accesses = None # Find the last access of the signature last_access = var_accesses[sig].all_accesses[-1].node # Compute the indices used in this loop. We know that all of the @@ -347,40 +365,6 @@ def _value_unused_after_loop(sig: Signature, == SymbolicMaths.Fuzzy.TRUE or sm.equal(next_index.start, orig_index.start)): return False - # If either of the start of stop points of the original - # access range are a reference, we need to make sure that - # reference has not been written to between the locations. - # FIXME Commented these out at they don't seem needed. - # if isinstance(next_index.stop, Reference): - # # Find the containing Routine - # if routine_var_accesses is None: - # routine = loop.ancestor(Routine) - # routine_var_accesses = VariablesAccessInfo( - # nodes=routine - # ) - # stop_sig = Signature(next_index.stop.symbol.name) - # if not routine_var_accesses[stop_sig].is_read_only(): - # stop_savi = routine_var_accesses[stop_sig] - # for access in stop_savi.all_write_accesses: - # pos = access.node.abs_position - # if (pos > loop.abs_position and - # pos < next_access.abs_position): - # return False - # if isinstance(next_index.start, Reference): - # # Find the containing Routine - # if routine_var_accesses is None: - # routine = loop.ancestor(Routine) - # routine_var_accesses = VariablesAccessInfo( - # nodes=routine - # ) - # start_sig = Signature(next_index.start.symbol.name) - # if not routine_var_accesses[start_sig].is_read_only(): - # start_savi = routine_var_accesses[start_sig] - # for access in start_savi.all_write_accesses: - # pos = access.node.abs_position - # if (pos > loop.abs_position and - # pos < next_access.abs_position): - # return False return True diff --git a/src/psyclone/tests/psyir/nodes/reference_test.py b/src/psyclone/tests/psyir/nodes/reference_test.py index baf4022949..c8f74d7dae 100644 --- a/src/psyclone/tests/psyir/nodes/reference_test.py +++ b/src/psyclone/tests/psyir/nodes/reference_test.py @@ -583,7 +583,7 @@ def test_reference_is_read(fortran_reader): code = """subroutine my_subroutine() b = a call somecall(c) - b = lbound(d) + b = lbound(d, dim=x) end subroutine""" psyir = fortran_reader.psyir_from_source(code) @@ -592,8 +592,12 @@ def test_reference_is_read(fortran_reader): assert references[1].is_read assert references[3].symbol.name == "c" assert references[3].is_read + # For the lbound, d should be an inquiry (so not a read) but + # x should be a read assert references[6].symbol.name == "d" assert not references[6].is_read + assert references[7].symbol.name == "x" + assert references[7].is_read def test_reference_is_write(fortran_reader): From 5c0557fdc7e580876667abcc35e7f2a5663acbde Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 26 Feb 2025 11:14:07 +0000 Subject: [PATCH 59/62] Error in transform script --- examples/nemo/scripts/omp_cpu_trans.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index 8f3c8bc704..6f8ab6db9b 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -77,9 +77,9 @@ def trans(psyir): ''' # TODO Remove scalartrans = ScalarizationTrans() - loops = subroutine.walk(Loop) - loops.reverse() for subroutine in psyir.walk(Routine): + loops = subroutine.walk(Loop) + loops.reverse() for loop in loops: scalartrans.apply(loop) From 12835bf9fef31858a6cea35e220787145c4348db Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Thu, 27 Feb 2025 11:00:52 +0000 Subject: [PATCH 60/62] Changes for review --- .github/workflows/nemo_tests.yml | 8 ++------ examples/nemo/scripts/omp_cpu_trans.py | 14 ++------------ examples/nemo/scripts/passthrough.py | 2 +- examples/nemo/scripts/utils.py | 15 ++++++++++++++- src/psyclone/psyir/tools/definition_use_chains.py | 8 ++++---- .../psyir/transformations/scalarization_trans.py | 11 +++++++---- ...inition_use_chains_backward_dependence_test.py | 4 ++-- ...finition_use_chains_forward_dependence_test.py | 4 ++-- .../transformations/scalarization_trans_test.py | 5 +++++ 9 files changed, 39 insertions(+), 32 deletions(-) diff --git a/.github/workflows/nemo_tests.yml b/.github/workflows/nemo_tests.yml index 60b484f5e0..3ad147b8ba 100644 --- a/.github/workflows/nemo_tests.yml +++ b/.github/workflows/nemo_tests.yml @@ -97,12 +97,8 @@ jobs: make -j 4 passthrough make -j 4 compile-passthrough make run-passthrough |& tee output.txt - # Check the output is as expected for the first 6 digits (if not exit with error message) - tail -n 1 output.txt | grep -q " it : 10" || (echo "Error: 'it : 10' not found!" & false) - tail -n 1 output.txt | grep -q "|ssh|_max: 0.259483" || (echo "Error: '|ssh|_max: 0.259483' not found!" & false) - tail -n 1 output.txt | grep -q "|U|_max: 0.458515" || (echo "Error: '|U|_max: 0.458515' not found!" & false) - tail -n 1 output.txt | grep -q "S_min: 0.482686" || (echo "Error: 'S_min: 0.482686' not found!" & false) - tail -n 1 output.txt | grep -q "S_max: 0.407622" || (echo "Error: 'S_max: 0.407622' not found!" & false) + # Check for full numerical reproducibility with KGO results + diff <(make -s output-passthrough) KGOs/run.stat.nemo4.splitz12.nvhpc.10steps # PSyclone, compile and run MetOffice NEMO with OpenMP for GPUs - name: NEMO MetOffice OpenMP for GPU diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index 6f8ab6db9b..9654eda057 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -44,9 +44,6 @@ PRIVATISATION_ISSUES) from psyclone.psyir.nodes import Routine from psyclone.transformations import OMPLoopTrans -# TODO Remove -from psyclone.psyir.transformations import ScalarizationTrans -from psyclone.psyir.nodes import Loop # Enable the insertion of profiling hooks during the transformation script PROFILING_ENABLED = False @@ -75,14 +72,6 @@ def trans(psyir): :type psyir: :py:class:`psyclone.psyir.nodes.FileContainer` ''' - # TODO Remove - scalartrans = ScalarizationTrans() - for subroutine in psyir.walk(Routine): - loops = subroutine.walk(Loop) - loops.reverse() - for loop in loops: - scalartrans.apply(loop) - # If the environemnt has ONLY_FILE defined, only process that one file and # nothing else. This is useful for file-by-file exhaustive tests. only_do_file = os.environ.get('ONLY_FILE', False) @@ -106,7 +95,8 @@ def trans(psyir): hoist_local_arrays=False, convert_array_notation=True, convert_range_loops=True, - hoist_expressions=False + hoist_expressions=False, + scalarize_loops=False ) if psyir.name not in PARALLELISATION_ISSUES: diff --git a/examples/nemo/scripts/passthrough.py b/examples/nemo/scripts/passthrough.py index 4b0782c0b9..17344c23b5 100755 --- a/examples/nemo/scripts/passthrough.py +++ b/examples/nemo/scripts/passthrough.py @@ -41,5 +41,5 @@ FILES_TO_SKIP = [] -def trans(psyir): +def trans(): ''' Don't do any changes. ''' diff --git a/examples/nemo/scripts/utils.py b/examples/nemo/scripts/utils.py index 07dfb1e83e..e7c779f557 100755 --- a/examples/nemo/scripts/utils.py +++ b/examples/nemo/scripts/utils.py @@ -44,7 +44,7 @@ from psyclone.psyir.transformations import ( ArrayAssignment2LoopsTrans, HoistLoopBoundExprTrans, HoistLocalArraysTrans, HoistTrans, InlineTrans, Maxval2LoopTrans, ProfileTrans, - Reference2ArrayRangeTrans) + Reference2ArrayRangeTrans, ScalarizationTrans) from psyclone.transformations import TransformationError @@ -283,6 +283,7 @@ def normalise_loops( loopify_array_intrinsics: bool = True, convert_range_loops: bool = True, hoist_expressions: bool = True, + scalarize_loops: bool = False, ): ''' Normalise all loops in the given schedule so that they are in an appropriate form for the Parallelisation transformations to analyse @@ -299,6 +300,8 @@ def normalise_loops( loops. :param bool hoist_expressions: whether to hoist bounds and loop invariant statements out of the loop nest. + :param scalarize_loops: whether to attempt to convert arrays to scalars + where possible, default is False. ''' if hoist_local_arrays and schedule.name not in CONTAINS_STMT_FUNCTIONS: # Apply the HoistLocalArraysTrans when possible, it cannot be applied @@ -339,6 +342,16 @@ def normalise_loops( except TransformationError: pass + if scalarize_loops: + # Apply scalarization to every loop. Execute this in reverse order + # as sometimes we can scalarize earlier loops if following loops + # have already been scalarized. + loops = schedule.walk(Loop) + loops.reverse() + scalartrans = ScalarizationTrans() + for loop in loops: + scalartrans.apply(loop) + if hoist_expressions: # First hoist all possible expressions for loop in schedule.walk(Loop): diff --git a/src/psyclone/psyir/tools/definition_use_chains.py b/src/psyclone/psyir/tools/definition_use_chains.py index 99fd3a9684..cf89d50af5 100644 --- a/src/psyclone/psyir/tools/definition_use_chains.py +++ b/src/psyclone/psyir/tools/definition_use_chains.py @@ -252,7 +252,7 @@ def find_forward_accesses(self): ) # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else - # statement, as is found in NEMO4. + # statement. if len(body) > 0: # We make a copy of the reference to have a detached # node to avoid handling the special cases based on @@ -306,7 +306,7 @@ def find_forward_accesses(self): for block in basic_blocks: # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else - # statement, as is found in NEMO4. + # statement. if len(block) == 0: continue chain = DefinitionUseChain( @@ -854,7 +854,7 @@ def find_backward_accesses(self): for block in basic_blocks: # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else - # statement, as is found in NEMO4. + # statement. if len(block) == 0: continue chain = DefinitionUseChain( @@ -898,7 +898,7 @@ def find_backward_accesses(self): sub_start_point = self._reference.abs_position # If we have a basic block with no children then skip it, # e.g. for an if block with no code before the else - # statement, as is found in NEMO4. + # statement. if len(body) > 0: chain = DefinitionUseChain( self._reference.copy(), diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarization_trans.py index a2b18934ab..e8a006f836 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarization_trans.py @@ -50,7 +50,9 @@ class ScalarizationTrans(LoopTrans): '''This transformation takes a Loop and converts any array accesses to scalar if the results of the loop are unused, and the initial value - is unused. For example: + is unused. For example in the following snippet the value of a(i) + is only used inside the loop, so can be turned into a scalar, wheras + the values of b(i) are used in the following loop so are kept as an array: >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader @@ -204,9 +206,10 @@ def _get_index_values_from_indices( Compute a list of index values for a given node. Looks at loop bounds and range declarations to attempt to convert loop variables to an explicit range, i.e. an access like - >>> do i = 1, 100 - array(i) = ... - end do + .. code-block:: fortran + do i = 1, 100 + array(i) = ... + end do the returned list would contain a range object for [1:100]. diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py index eb591dd28a..e8353ffb27 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_backward_dependence_test.py @@ -692,7 +692,7 @@ def test_definition_use_chains_backward_accesses_nonassign_reference_in_loop( def test_definition_use_chains_backward_accesses_empty_schedules( fortran_reader, ): - '''Coverage to handle the case where we have empty schedules inside + '''Test the case where we have empty schedules inside various type of code.''' code = """ subroutine x() @@ -723,7 +723,7 @@ def test_definition_use_chains_backward_accesses_empty_schedules( def test_definition_use_chains_backward_accesses_inquiry_func( fortran_reader, ): - '''Coverave to handle the case where we have an inquiry function + '''Test the case where we have an inquiry function accessing the symbol of interest.''' code = """ subroutine x() diff --git a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py index a3652a99b8..d65f5e10b7 100644 --- a/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py +++ b/src/psyclone/tests/psyir/tools/definition_use_chains_forward_dependence_test.py @@ -942,7 +942,7 @@ def test_definition_use_chains_forward_accesses_multiple_routines( def test_definition_use_chains_forward_accesses_empty_schedules( fortran_reader, ): - '''Coverage to handle the case where we have empty schedules inside + '''Test the case where we have empty schedules inside various type of code.''' code = """ subroutine x() @@ -973,7 +973,7 @@ def test_definition_use_chains_forward_accesses_empty_schedules( def test_definition_use_chains_backward_accesses_inquiry_func( fortran_reader, ): - '''Coverave to handle the case where we have an inquiry function + '''Test the case where we have an inquiry function accessing the symbol of interest.''' code = """ subroutine x() diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py index f012aa281b..5198bddb63 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py @@ -43,6 +43,7 @@ def test_scalararizationtrans_is_local_array(fortran_reader): + '''Test the _is_local_array function in the ScalarizationTrans.''' code = '''function test(a) result(x) use mymod, only: arr, atype integer :: i @@ -113,6 +114,7 @@ def test_scalararizationtrans_is_local_array(fortran_reader): def test_scalarizationtrans_have_same_unmodified_index(fortran_reader): + '''Test the _have_same_unmodified_index function of ScalarizationTrans.''' code = '''subroutine test() integer :: i integer :: k @@ -161,6 +163,8 @@ def test_scalarizationtrans_have_same_unmodified_index(fortran_reader): def test_scalarizationtrans_check_first_access_is_write(fortran_reader): + '''Test the _check_first_access_is_write function of + ScalarizationTrans.''' code = '''subroutine test() integer :: i integer :: k @@ -216,6 +220,7 @@ def test_scalarizationtrans_check_first_access_is_write(fortran_reader): def test_scalarizationtrans_value_unused_after_loop(fortran_reader): + '''Test the _value_unused_after_loop function of ScalarizationTrans.''' code = '''subroutine test() integer :: i integer :: k From c28fea61f8a5d54a0489cd9059b93ee97ea628aa Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Thu, 27 Feb 2025 11:44:58 +0000 Subject: [PATCH 61/62] #2499 Update changelog, increase version, and fix nemo passthrough --- .github/workflows/nemo_tests.yml | 2 +- changelog | 4 ++++ examples/nemo/scripts/passthrough.py | 2 +- src/psyclone/version.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nemo_tests.yml b/.github/workflows/nemo_tests.yml index 3ad147b8ba..2dcd375d0d 100644 --- a/.github/workflows/nemo_tests.yml +++ b/.github/workflows/nemo_tests.yml @@ -96,7 +96,7 @@ jobs: module load perl/${PERL_VERSION} make -j 4 passthrough make -j 4 compile-passthrough - make run-passthrough |& tee output.txt + make run-passthrough # Check for full numerical reproducibility with KGO results diff <(make -s output-passthrough) KGOs/run.stat.nemo4.splitz12.nvhpc.10steps diff --git a/changelog b/changelog index e432c872c6..0a46c2aa54 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,7 @@ + 1) PR #2563 for #2499. Adds ScalarizationTrans. + +release 3.1.0 26th of February 2025 + 1) PR #2827. Update Zenodo with release 3.0.0 and update link in README.md. diff --git a/examples/nemo/scripts/passthrough.py b/examples/nemo/scripts/passthrough.py index 17344c23b5..6325e56368 100755 --- a/examples/nemo/scripts/passthrough.py +++ b/examples/nemo/scripts/passthrough.py @@ -41,5 +41,5 @@ FILES_TO_SKIP = [] -def trans(): +def trans(_): ''' Don't do any changes. ''' diff --git a/src/psyclone/version.py b/src/psyclone/version.py index 39b1b567c8..a05f8e4246 100644 --- a/src/psyclone/version.py +++ b/src/psyclone/version.py @@ -41,7 +41,7 @@ __MAJOR__ = 3 __MINOR__ = 1 -__MICRO__ = 0 +__MICRO__ = 1 __SHORT_VERSION__ = f"{__MAJOR__:d}.{__MINOR__:d}-dev" __VERSION__ = f"{__MAJOR__:d}.{__MINOR__:d}.{__MICRO__:d}-dev" From 612f264e077d2e31f44a78f82e52a85e8ee31965 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Thu, 27 Feb 2025 14:00:26 +0000 Subject: [PATCH 62/62] #2499 Rename scalarization to scalarisation --- changelog | 2 +- doc/user_guide/transformations.rst | 2 +- examples/nemo/scripts/omp_cpu_trans.py | 2 +- examples/nemo/scripts/utils.py | 16 +- .../psyir/transformations/__init__.py | 6 +- ...zation_trans.py => scalarisation_trans.py} | 62 ++++---- ...ns_test.py => scalarisation_trans_test.py} | 148 +++++++++--------- 7 files changed, 119 insertions(+), 119 deletions(-) rename src/psyclone/psyir/transformations/{scalarization_trans.py => scalarisation_trans.py} (92%) rename src/psyclone/tests/psyir/transformations/{scalarization_trans_test.py => scalarisation_trans_test.py} (87%) diff --git a/changelog b/changelog index 0a46c2aa54..5d24117fa9 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,4 @@ - 1) PR #2563 for #2499. Adds ScalarizationTrans. + 1) PR #2563 for #2499. Adds ScalarisationTrans. release 3.1.0 26th of February 2025 diff --git a/doc/user_guide/transformations.rst b/doc/user_guide/transformations.rst index ccdbff350a..88df0dee8f 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -569,7 +569,7 @@ can be found in the API-specific sections). #### -.. autoclass:: psyclone.psyir.transformations.ScalarizationTrans +.. autoclass:: psyclone.psyir.transformations.ScalarisationTrans :members: apply :noindex: diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py index 9654eda057..403f023e14 100755 --- a/examples/nemo/scripts/omp_cpu_trans.py +++ b/examples/nemo/scripts/omp_cpu_trans.py @@ -96,7 +96,7 @@ def trans(psyir): convert_array_notation=True, convert_range_loops=True, hoist_expressions=False, - scalarize_loops=False + scalarise_loops=False ) if psyir.name not in PARALLELISATION_ISSUES: diff --git a/examples/nemo/scripts/utils.py b/examples/nemo/scripts/utils.py index e7c779f557..2208ddc3c4 100755 --- a/examples/nemo/scripts/utils.py +++ b/examples/nemo/scripts/utils.py @@ -44,7 +44,7 @@ from psyclone.psyir.transformations import ( ArrayAssignment2LoopsTrans, HoistLoopBoundExprTrans, HoistLocalArraysTrans, HoistTrans, InlineTrans, Maxval2LoopTrans, ProfileTrans, - Reference2ArrayRangeTrans, ScalarizationTrans) + Reference2ArrayRangeTrans, ScalarisationTrans) from psyclone.transformations import TransformationError @@ -283,7 +283,7 @@ def normalise_loops( loopify_array_intrinsics: bool = True, convert_range_loops: bool = True, hoist_expressions: bool = True, - scalarize_loops: bool = False, + scalarise_loops: bool = False, ): ''' Normalise all loops in the given schedule so that they are in an appropriate form for the Parallelisation transformations to analyse @@ -300,7 +300,7 @@ def normalise_loops( loops. :param bool hoist_expressions: whether to hoist bounds and loop invariant statements out of the loop nest. - :param scalarize_loops: whether to attempt to convert arrays to scalars + :param scalarise_loops: whether to attempt to convert arrays to scalars where possible, default is False. ''' if hoist_local_arrays and schedule.name not in CONTAINS_STMT_FUNCTIONS: @@ -342,13 +342,13 @@ def normalise_loops( except TransformationError: pass - if scalarize_loops: - # Apply scalarization to every loop. Execute this in reverse order - # as sometimes we can scalarize earlier loops if following loops - # have already been scalarized. + if scalarise_loops: + # Apply scalarisation to every loop. Execute this in reverse order + # as sometimes we can scalarise earlier loops if following loops + # have already been scalarised. loops = schedule.walk(Loop) loops.reverse() - scalartrans = ScalarizationTrans() + scalartrans = ScalarisationTrans() for loop in loops: scalartrans.apply(loop) diff --git a/src/psyclone/psyir/transformations/__init__.py b/src/psyclone/psyir/transformations/__init__.py index 40f31bd2a2..5a71449aed 100644 --- a/src/psyclone/psyir/transformations/__init__.py +++ b/src/psyclone/psyir/transformations/__init__.py @@ -105,8 +105,8 @@ ReplaceInductionVariablesTrans from psyclone.psyir.transformations.reference2arrayrange_trans import \ Reference2ArrayRangeTrans -from psyclone.psyir.transformations.scalarization_trans import \ - ScalarizationTrans +from psyclone.psyir.transformations.scalarisation_trans import \ + ScalarisationTrans # For AutoAPI documentation generation @@ -147,6 +147,6 @@ 'Reference2ArrayRangeTrans', 'RegionTrans', 'ReplaceInductionVariablesTrans', - 'ScalarizationTrans', + 'ScalarisationTrans', 'TransformationError', 'ValueRangeCheckTrans'] diff --git a/src/psyclone/psyir/transformations/scalarization_trans.py b/src/psyclone/psyir/transformations/scalarisation_trans.py similarity index 92% rename from src/psyclone/psyir/transformations/scalarization_trans.py rename to src/psyclone/psyir/transformations/scalarisation_trans.py index e8a006f836..fa9b99fbae 100644 --- a/src/psyclone/psyir/transformations/scalarization_trans.py +++ b/src/psyclone/psyir/transformations/scalarisation_trans.py @@ -47,7 +47,7 @@ from psyclone.psyir.transformations.loop_trans import LoopTrans -class ScalarizationTrans(LoopTrans): +class ScalarisationTrans(LoopTrans): '''This transformation takes a Loop and converts any array accesses to scalar if the results of the loop are unused, and the initial value is unused. For example in the following snippet the value of a(i) @@ -56,7 +56,7 @@ class ScalarizationTrans(LoopTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.psyir.transformations import ScalarizationTrans + >>> from psyclone.psyir.transformations import ScalarisationTrans >>> from psyclone.psyir.nodes import Loop >>> code = """program test ... integer :: i,j @@ -72,7 +72,7 @@ class ScalarizationTrans(LoopTrans): ... end do ... end program""" >>> psyir = FortranReader().psyir_from_source(code) - >>> scalarise = ScalarizationTrans() + >>> scalarise = ScalarisationTrans() >>> scalarise.apply(psyir.walk(Loop)[0]) >>> print(FortranWriter()(psyir)) program test @@ -120,7 +120,7 @@ def _is_local_array(signature: Signature, base_symbol = var_accesses[signature].all_accesses[0].node.symbol if not base_symbol.is_automatic: return False - # If its a derived type then we don't scalarize. + # If its a derived type then we don't scalarise. if isinstance(var_accesses[signature].all_accesses[0].node, StructureReference): return False @@ -146,14 +146,14 @@ def _have_same_unmodified_index( the code region. ''' array_indices = None - scalarizable = True + scalarisable = True for access in var_accesses[signature].all_accesses: if array_indices is None: array_indices = access.component_indices # For some reason using == on the component_lists doesn't work # so we use [:] notation. elif array_indices[:] != access.component_indices[:]: - scalarizable = False + scalarisable = False break # For each index, we need to check they're not written to in # the loop. @@ -164,16 +164,16 @@ def _have_same_unmodified_index( # References for ref in index.walk(Reference): # This Reference could be the symbol for a Call or - # IntrinsicCall, which we don't allow to scalarize + # IntrinsicCall, which we don't allow to scalarise if isinstance(ref.symbol, RoutineSymbol): - scalarizable = False + scalarisable = False break sig, _ = ref.get_signature_and_indices() if var_accesses[sig].is_written(): - scalarizable = False + scalarisable = False break - return scalarizable + return scalarisable @staticmethod def _check_first_access_is_write(signature: Signature, @@ -229,8 +229,8 @@ def _get_index_values_from_indices( has_complex_index = False for index in indices: # If the index is an array or structure and there are any more - # accesses to the signature we're trying to scalarize, then we - # should not scalarize. + # accesses to the signature we're trying to scalarise, then we + # should not scalarise. if (type(index) is not Range and type(index) is not Reference and type(index) is not Literal): has_complex_index = True @@ -296,7 +296,7 @@ def _value_unused_after_loop(sig: Signature, # Compute the indices ranges. has_complex_index, index_values = \ - ScalarizationTrans._get_index_values_from_indices( + ScalarisationTrans._get_index_values_from_indices( last_access, indices ) @@ -307,7 +307,7 @@ def _value_unused_after_loop(sig: Signature, continue # If we have a next_access outside of the loop and have a complex - # index then we do not scalarize this at the moment. + # index then we do not scalarise this at the moment. if has_complex_index: return False @@ -332,15 +332,15 @@ def _value_unused_after_loop(sig: Signature, # or more of the array. next_indices = next_access.indices next_complex_index, next_values = \ - ScalarizationTrans._get_index_values_from_indices( + ScalarisationTrans._get_index_values_from_indices( next_access, next_indices ) # If we can't compute the indices of the next access then we - # cannot scalarize + # cannot scalarise if next_complex_index: return False # Check the indices of next_access are greater than or equal to - # that of the potential scalarization. + # that of the potential scalarisation. for i in range(len(next_values)): # If the next index is a full range we can skip it as it must # cover the previous access @@ -358,7 +358,7 @@ def _value_unused_after_loop(sig: Signature, sm = SymbolicMaths.get() # Need to check that next_index stop point is >= orig_index. # If its not then this can't cover the full range so we can - # return False to not Scalarize this. + # return False to not Scalarise this. if not (sm.greater_than(next_index.stop, orig_index.stop) == SymbolicMaths.Fuzzy.TRUE or sm.equal(next_index.stop, orig_index.stop)): @@ -374,22 +374,22 @@ def _value_unused_after_loop(sig: Signature, def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ -> None: ''' - Apply the scalarization transformation to a loop. + Apply the scalarisation transformation to a loop. All of the array accesses that are identified as being able to be - scalarized will be transformed by this transformation. + scalarised will be transformed by this transformation. - An array access will be scalarized if: + An array access will be scalarised if: 1. All accesses to the array use the same indexing statement. 2. All References contained in the indexing statement are not modified inside of the loop (loop variables are ok). 3. The array symbol is either not accessed again or is written to as its next access. If the next access is inside a conditional that is not an ancestor of the input loop, then PSyclone will - assume that we cannot scalarize that value instead of attempting to + assume that we cannot scalarise that value instead of attempting to understand the control flow. 4. The array symbol is a local variable. - :param node: the supplied loop to apply scalarization to. + :param node: the supplied loop to apply scalarisation to. :param options: a dictionary with options for transformations. ''' @@ -401,9 +401,9 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ # first access is a write # Then, for each symbol still meeting this criteria, we need to find # the next access outside of this loop. If its inside an ifblock that - # is not an ancestor of this loop then we refuse to scalarize for - # simplicity. Otherwise if its a read we can't scalarize safely. - # If its a write then this symbol can be scalarized. + # is not an ancestor of this loop then we refuse to scalarise for + # simplicity. Otherwise if its a read we can't scalarise safely. + # If its a write then this symbol can be scalarised. var_accesses = VariablesAccessInfo(nodes=node.loop_body) @@ -411,11 +411,11 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ # that index is only read inside the loop. potential_targets = filter( lambda sig: - ScalarizationTrans._is_local_array(sig, var_accesses), + ScalarisationTrans._is_local_array(sig, var_accesses), var_accesses) potential_targets = filter( lambda sig: - ScalarizationTrans._have_same_unmodified_index(sig, + ScalarisationTrans._have_same_unmodified_index(sig, var_accesses), potential_targets) @@ -423,7 +423,7 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ # that aren't. potential_targets = filter( lambda sig: - ScalarizationTrans._check_first_access_is_write(sig, + ScalarisationTrans._check_first_access_is_write(sig, node, var_accesses), potential_targets) @@ -431,13 +431,13 @@ def apply(self, node: Loop, options: Optional[Dict[str, Any]] = None) \ # Check the values written to these arrays are not used after this loop finalised_targets = filter( lambda sig: - ScalarizationTrans._value_unused_after_loop(sig, + ScalarisationTrans._value_unused_after_loop(sig, node, var_accesses), potential_targets) routine_table = node.ancestor(Routine).symbol_table - # For each finalised target we can replace them with a scalarized + # For each finalised target we can replace them with a scalarised # symbol for target in finalised_targets: target_accesses = var_accesses[target].all_accesses diff --git a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py b/src/psyclone/tests/psyir/transformations/scalarisation_trans_test.py similarity index 87% rename from src/psyclone/tests/psyir/transformations/scalarization_trans_test.py rename to src/psyclone/tests/psyir/transformations/scalarisation_trans_test.py index 5198bddb63..0695125621 100644 --- a/src/psyclone/tests/psyir/transformations/scalarization_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/scalarisation_trans_test.py @@ -33,17 +33,17 @@ # ---------------------------------------------------------------------------- # Author: A. B. G. Chalk, STFC Daresbury Lab -'''This module tests the scalarization transformation. +'''This module tests the scalarisation transformation. ''' from psyclone.core import VariablesAccessInfo from psyclone.psyir.nodes import Loop -from psyclone.psyir.transformations import ScalarizationTrans +from psyclone.psyir.transformations import ScalarisationTrans from psyclone.tests.utilities import Compile def test_scalararizationtrans_is_local_array(fortran_reader): - '''Test the _is_local_array function in the ScalarizationTrans.''' + '''Test the _is_local_array function in the ScalarisationTrans.''' code = '''function test(a) result(x) use mymod, only: arr, atype integer :: i @@ -71,15 +71,15 @@ def test_scalararizationtrans_is_local_array(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._is_local_array(keys[1], + assert not ScalarisationTrans._is_local_array(keys[1], var_accesses) # Test a assert var_accesses[keys[2]].var_name == "a" - assert not ScalarizationTrans._is_local_array(keys[2], + assert not ScalarisationTrans._is_local_array(keys[2], var_accesses) # Test local assert var_accesses[keys[3]].var_name == "local" - assert ScalarizationTrans._is_local_array(keys[3], + assert ScalarisationTrans._is_local_array(keys[3], var_accesses) # Test b - the RHS of the assignment is a codeblock so we do not @@ -87,34 +87,34 @@ def test_scalararizationtrans_is_local_array(fortran_reader): # local array test can fail. Also we can't safely transform the # CodeBlock anyway. assert var_accesses[keys[4]].var_name == "b" - assert not ScalarizationTrans._is_local_array(keys[4], + assert not ScalarisationTrans._is_local_array(keys[4], var_accesses) # Test x - the return value is not classed as a local array. assert var_accesses[keys[5]].var_name == "x" - assert not ScalarizationTrans._is_local_array(keys[5], + assert not ScalarisationTrans._is_local_array(keys[5], var_accesses) - # Test custom - we don't scalarize derived types. + # Test custom - we don't scalarise derived types. assert var_accesses[keys[6]].var_name == "custom%type" - assert not ScalarizationTrans._is_local_array(keys[6], + assert not ScalarisationTrans._is_local_array(keys[6], var_accesses) - # Test custom2 - we don't scalarize derived types. + # Test custom2 - we don't scalarise derived types. assert var_accesses[keys[7]].var_name == "custom2%typeb" - assert not ScalarizationTrans._is_local_array(keys[7], + assert not ScalarisationTrans._is_local_array(keys[7], var_accesses) # Test filter behaviour same as used in the transformation local_arrays = filter( - lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), + lambda sig: ScalarisationTrans._is_local_array(sig, var_accesses), var_accesses) local_arrays = list(local_arrays) assert len(local_arrays) == 1 assert local_arrays[0].var_name == "local" -def test_scalarizationtrans_have_same_unmodified_index(fortran_reader): - '''Test the _have_same_unmodified_index function of ScalarizationTrans.''' +def test_scalarisationtrans_have_same_unmodified_index(fortran_reader): + '''Test the _have_same_unmodified_index function of ScalarisationTrans.''' code = '''subroutine test() integer :: i integer :: k @@ -136,25 +136,25 @@ def test_scalarizationtrans_have_same_unmodified_index(fortran_reader): keys = list(var_accesses.keys()) # Test a assert var_accesses[keys[1]].var_name == "a" - assert ScalarizationTrans._have_same_unmodified_index(keys[1], + assert ScalarisationTrans._have_same_unmodified_index(keys[1], var_accesses) # Test b (differeing indices) assert var_accesses[keys[2]].var_name == "b" - assert not ScalarizationTrans._have_same_unmodified_index(keys[2], + assert not ScalarisationTrans._have_same_unmodified_index(keys[2], var_accesses) # Test c (k is modified) assert var_accesses[keys[3]].var_name == "c" - assert not ScalarizationTrans._have_same_unmodified_index(keys[3], + assert not ScalarisationTrans._have_same_unmodified_index(keys[3], var_accesses) # Test filter behaviour same as used in the transformation local_arrays = filter( - lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), + lambda sig: ScalarisationTrans._is_local_array(sig, var_accesses), var_accesses) local_arrays = list(local_arrays) assert len(local_arrays) == 3 unmodified_indices = filter( - lambda sig: ScalarizationTrans._have_same_unmodified_index( + lambda sig: ScalarisationTrans._have_same_unmodified_index( sig, var_accesses), local_arrays) unmodified_indices = list(unmodified_indices) @@ -162,9 +162,9 @@ def test_scalarizationtrans_have_same_unmodified_index(fortran_reader): assert unmodified_indices[0].var_name == "a" -def test_scalarizationtrans_check_first_access_is_write(fortran_reader): +def test_scalarisationtrans_check_first_access_is_write(fortran_reader): '''Test the _check_first_access_is_write function of - ScalarizationTrans.''' + ScalarisationTrans.''' code = '''subroutine test() integer :: i integer :: k @@ -183,44 +183,44 @@ def test_scalarizationtrans_check_first_access_is_write(fortran_reader): keys = list(var_accesses.keys()) # Test a assert var_accesses[keys[1]].var_name == "a" - assert ScalarizationTrans._check_first_access_is_write(keys[1], + assert ScalarisationTrans._check_first_access_is_write(keys[1], node, var_accesses) # Test b (differeing indices) assert var_accesses[keys[2]].var_name == "b" - assert not ScalarizationTrans._check_first_access_is_write(keys[2], + assert not ScalarisationTrans._check_first_access_is_write(keys[2], node, var_accesses) # Test c (k is modified) assert var_accesses[keys[3]].var_name == "c" - assert ScalarizationTrans._check_first_access_is_write(keys[3], + assert ScalarisationTrans._check_first_access_is_write(keys[3], node, var_accesses) # Test filter behaviour same as used in the transformation local_arrays = filter( - lambda sig: ScalarizationTrans._is_local_array(sig, var_accesses), + lambda sig: ScalarisationTrans._is_local_array(sig, var_accesses), var_accesses) local_arrays = list(local_arrays) assert len(local_arrays) == 3 unmodified_indices = filter( - lambda sig: ScalarizationTrans._have_same_unmodified_index( + lambda sig: ScalarisationTrans._have_same_unmodified_index( sig, var_accesses), local_arrays) unmodified_indices = list(unmodified_indices) assert len(unmodified_indices) == 3 first_write_arrays = filter( - lambda sig: ScalarizationTrans._check_first_access_is_write( + lambda sig: ScalarisationTrans._check_first_access_is_write( sig, node, var_accesses), unmodified_indices) first_write_arrays = list(first_write_arrays) assert len(first_write_arrays) == 2 -def test_scalarizationtrans_value_unused_after_loop(fortran_reader): - '''Test the _value_unused_after_loop function of ScalarizationTrans.''' +def test_scalarisationtrans_value_unused_after_loop(fortran_reader): + '''Test the _value_unused_after_loop function of ScalarisationTrans.''' code = '''subroutine test() integer :: i integer :: k @@ -242,12 +242,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert ScalarizationTrans._value_unused_after_loop(keys[1], + assert ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" - assert not ScalarizationTrans._value_unused_after_loop(keys[2], + assert not ScalarisationTrans._value_unused_after_loop(keys[2], node.loop_body, var_accesses) @@ -276,16 +276,16 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert ScalarizationTrans._value_unused_after_loop(keys[1], + assert ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" - assert ScalarizationTrans._value_unused_after_loop(keys[2], + assert ScalarisationTrans._value_unused_after_loop(keys[2], node.loop_body, var_accesses) # Test we don't ignore array next_access if they're in an if statement - # that is an ancestor of the loop we're scalarizing + # that is an ancestor of the loop we're scalarising code = '''subroutine test() integer :: i integer :: k @@ -309,12 +309,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert ScalarizationTrans._value_unused_after_loop(keys[1], + assert ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" - assert ScalarizationTrans._value_unused_after_loop(keys[2], + assert ScalarisationTrans._value_unused_after_loop(keys[2], node.loop_body, var_accesses) @@ -344,12 +344,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert ScalarizationTrans._value_unused_after_loop(keys[1], + assert ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" - assert not ScalarizationTrans._value_unused_after_loop(keys[2], + assert not ScalarisationTrans._value_unused_after_loop(keys[2], node.loop_body, var_accesses) @@ -379,7 +379,7 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test b assert var_accesses[keys[2]].var_name == "b" - assert not ScalarizationTrans._value_unused_after_loop(keys[2], + assert not ScalarisationTrans._value_unused_after_loop(keys[2], node.loop_body, var_accesses) @@ -412,17 +412,17 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._value_unused_after_loop(keys[1], + assert not ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) # Test b assert var_accesses[keys[2]].var_name == "b" - assert not ScalarizationTrans._value_unused_after_loop(keys[2], + assert not ScalarisationTrans._value_unused_after_loop(keys[2], node.loop_body, var_accesses) # Test c assert var_accesses[keys[3]].var_name == "c" - assert not ScalarizationTrans._value_unused_after_loop(keys[3], + assert not ScalarisationTrans._value_unused_after_loop(keys[3], node.loop_body, var_accesses) @@ -452,7 +452,7 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._value_unused_after_loop(keys[1], + assert not ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) @@ -484,11 +484,11 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._value_unused_after_loop(keys[1], + assert not ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) - # Test having a non-unit stride prevents scalarization + # Test having a non-unit stride prevents scalarisation code = '''subroutine test() use my_mod integer :: i @@ -509,11 +509,11 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._value_unused_after_loop(keys[1], + assert not ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) - # Test having a loop bound as a structure element prevents scalarization + # Test having a loop bound as a structure element prevents scalarisation code = '''subroutine test() use my_mod integer :: i @@ -534,12 +534,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._value_unused_after_loop(keys[1], + assert not ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) # Test having an index as a reference to a non-loop variable prevents - # scalarization + # scalarisation code = '''subroutine test() use my_mod integer :: i @@ -560,12 +560,12 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._value_unused_after_loop(keys[1], + assert not ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) # Test that the next access having a non-unit stride prevents - # scalarization + # scalarisation code = '''subroutine test() use my_mod integer :: i @@ -586,11 +586,11 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert not ScalarizationTrans._value_unused_after_loop(keys[1], + assert not ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) - # Test that the next access being a Reference allows scalarization + # Test that the next access being a Reference allows scalarisation code = '''subroutine test() use my_mod integer :: i @@ -609,11 +609,11 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert ScalarizationTrans._value_unused_after_loop(keys[1], + assert ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) - # Test that having a scalar array index doesn't prevent scalarization + # Test that having a scalar array index doesn't prevent scalarisation code = '''subroutine test() use my_mod integer :: i @@ -632,13 +632,13 @@ def test_scalarizationtrans_value_unused_after_loop(fortran_reader): keys = list(var_accesses.keys()) # Test arr assert var_accesses[keys[1]].var_name == "arr" - assert ScalarizationTrans._value_unused_after_loop(keys[1], + assert ScalarisationTrans._value_unused_after_loop(keys[1], node.loop_body, var_accesses) -def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): - ''' Test the application of the scalarization transformation.''' +def test_scalarisation_trans_apply(fortran_reader, fortran_writer, tmpdir): + ''' Test the application of the scalarisation transformation.''' code = '''subroutine test() integer :: i integer :: k @@ -658,7 +658,7 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): end do end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loop = psyir.children[0].children[0] @@ -710,7 +710,7 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): end do end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loop = psyir.children[0].children[0] @@ -742,9 +742,9 @@ def test_scalarization_trans_apply(fortran_reader, fortran_writer, tmpdir): assert Compile(tmpdir).string_compiles(out) -def test_scalarization_trans_apply_routinesymbol(fortran_reader, +def test_scalarisation_trans_apply_routinesymbol(fortran_reader, fortran_writer, tmpdir): - ''' Test the application of the scalarization transformation doesn't work + ''' Test the application of the scalarisation transformation doesn't work when applied on an array with a RoutineSymbol as an index.''' code = '''subroutine test integer, dimension(3) :: j @@ -755,7 +755,7 @@ def test_scalarization_trans_apply_routinesymbol(fortran_reader, deallocate(k) end do end subroutine test''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) strans.apply(psyir.children[0].children[0]) correct = '''subroutine test() @@ -775,10 +775,10 @@ def test_scalarization_trans_apply_routinesymbol(fortran_reader, assert Compile(tmpdir).string_compiles(out) -def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): +def test_scalarisation_trans_noscalarise(fortran_reader, fortran_writer): ''' - Test that the scalarization transformation won't scalarize some patterns - we expect to not be scalarized. + Test that the scalarisation transformation won't scalarise some patterns + we expect to not be scalarised. ''' code = ''' subroutine test @@ -795,7 +795,7 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): arr(:,:,1) = 0.0 end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) for loop in loops: @@ -818,7 +818,7 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): arr(:,:,3:5) = 0.0 end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) for loop in loops: @@ -849,7 +849,7 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): end do end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) strans.apply(loops[0]) @@ -879,7 +879,7 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): end do end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) strans.apply(loops[0]) @@ -908,7 +908,7 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): end do end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) strans.apply(loops[0]) @@ -937,7 +937,7 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): end do end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) strans.apply(loops[0]) @@ -964,7 +964,7 @@ def test_scalarization_trans_noscalarize(fortran_reader, fortran_writer): end do end subroutine ''' - strans = ScalarizationTrans() + strans = ScalarisationTrans() psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) strans.apply(loops[1])