From 628d732765f1b9ce87d209e94cb9cdfa7c044e2b Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:30:28 +0100 Subject: [PATCH 01/27] Initial implementation to handle issue #2684 --- src/psyclone/psyir/frontend/fparser2.py | 135 ++++------- .../frontend/fparser2_where_handler_test.py | 221 +++++++++++------- 2 files changed, 175 insertions(+), 181 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index e03a85fa4d..1958cb706c 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -59,8 +59,6 @@ Reference, Return, Routine, Schedule, StructureReference, UnaryOperation, WhileLoop) from psyclone.psyir.nodes.array_mixin import ArrayMixin -from psyclone.psyir.nodes.array_of_structures_mixin import ( - ArrayOfStructuresMixin) from psyclone.psyir.symbols import ( ArgumentInterface, ArrayType, AutomaticInterface, CHARACTER_TYPE, CommonBlockInterface, ContainerSymbol, DataSymbol, DataTypeSymbol, @@ -4102,75 +4100,6 @@ def _process_case_value(self, selector, node, parent): parent.addchild(call) call.children.extend(fake_parent.pop_all_children()) - @staticmethod - def _array_notation_rank(node): - '''Check that the supplied candidate array reference uses supported - array notation syntax and return the rank of the sub-section - of the array that uses array notation. e.g. for a reference - "a(:, 2, :)" the rank of the sub-section is 2. - - :param node: the reference to check. - :type node: :py:class:`psyclone.psyir.nodes.ArrayReference` or \ - :py:class:`psyclone.psyir.nodes.ArrayMember` or \ - :py:class:`psyclone.psyir.nodes.StructureReference` - - :returns: rank of the sub-section of the array. - :rtype: int - - :raises InternalError: if no ArrayMixin node with at least one \ - Range in its indices is found. - :raises InternalError: if two or more part references in a \ - structure reference contain ranges. - :raises NotImplementedError: if the supplied node is not of a \ - supported type. - :raises NotImplementedError: if any ranges are encountered that are \ - not for the full extent of the dimension. - ''' - if isinstance(node, (ArrayReference, ArrayMember)): - array = node - elif isinstance(node, StructureReference): - array = None - arrays = node.walk((ArrayMember, ArrayOfStructuresMixin)) - for part_ref in arrays: - if any(isinstance(idx, Range) for idx in part_ref.indices): - if array: - # Cannot have two or more part references that contain - # ranges - this is not valid Fortran. - raise InternalError( - f"Found a structure reference containing two or " - f"more part references that have ranges: " - f"'{node.debug_string()}'. This is not valid " - f"within a WHERE in Fortran.") - array = part_ref - if not array: - raise InternalError( - f"No array access found in node '{node.name}'") - else: - # This will result in a CodeBlock. - raise NotImplementedError( - f"Expected either an ArrayReference, ArrayMember or a " - f"StructureReference but got '{type(node).__name__}'") - - # Only array refs using basic colon syntax are currently - # supported e.g. (a(:,:)). Each colon is represented in the - # PSyIR as a Range node with first argument being an lbound - # binary operator, the second argument being a ubound operator - # and the third argument being an integer Literal node with - # value 1 i.e. a(:,:) is represented as - # a(lbound(a,1):ubound(a,1):1,lbound(a,2):ubound(a,2):1) in - # the PSyIR. - num_colons = 0 - for idx_node in array.indices: - if isinstance(idx_node, Range): - # Found array syntax notation. Check that it is the - # simple ":" format. - if not array.is_full_range(array.index_of(idx_node)): - raise NotImplementedError( - "Only array notation of the form my_array(:, :, ...) " - "is supported.") - num_colons += 1 - return num_colons - def _array_syntax_to_indexed(self, parent, loop_vars): ''' Utility function that modifies each ArrayReference object in the @@ -4188,23 +4117,38 @@ def _array_syntax_to_indexed(self, parent, loop_vars): :raises NotImplementedError: if array sections of differing ranks are found. ''' - assigns = parent.walk(Assignment) - # Check that the LHS of any assignment uses array notation. - # Note that this will prevent any WHERE blocks that contain scalar - # assignments from being handled but is a necessary limitation until - # #717 is done and we interrogate the type of each symbol. - for assign in assigns: - _ = self._array_notation_rank(assign.lhs) - - # TODO #717 if the supplied code accidentally omits array + # If the supplied code accidentally omits array # notation for an array reference on the RHS then we will - # identify it as a scalar and the code produced from the - # PSyIR (using e.g. the Fortran backend) will not - # compile. We need to implement robust identification of the - # types of all symbols in the PSyIR fragment. + # identify it as a scalar and the produce array notated code + # in the PSyIR if possible. table = parent.scope.symbol_table one = Literal("1", INTEGER_TYPE) + refs = parent.walk(Reference) arrays = parent.walk(ArrayMixin) + for ref in refs: + add_op = BinaryOperation.Operator.ADD + sub_op = BinaryOperation.Operator.SUB + if (type(ref) is Reference and type(ref.symbol) is not Symbol and + isinstance(ref.symbol.datatype, ArrayType)): + # Skip if the shape doesn't match the loop_vars length. + if len(ref.datatype.shape) != len(loop_vars): + continue + # Have an implicit full range reference to an array which + # we need to convert ourselves. + temp_vals = [":" for i in range(len(ref.datatype.shape))] + array = ArrayReference.create(ref.symbol, temp_vals) + for dim in range(len(ref.datatype.shape)): + # Create the index expression. + symbol = table.lookup(loop_vars[dim]) + # We don't know what the lower bound is so have to + # have an expression: + # idx-expr = array-lower-bound + loop-idx - 1 + lbound = array.get_lbound_expression(dim) + expr = BinaryOperation.create( + add_op, lbound, Reference(symbol)) + expr2 = BinaryOperation.create(sub_op, expr, one.copy()) + array.children[dim] = expr2 + first_rank = None for array in arrays: # Check that this is a supported array reference and that @@ -4272,7 +4216,11 @@ def _array_syntax_to_indexed(self, parent, loop_vars): expr = BinaryOperation.create( add_op, lbound, Reference(symbol)) expr2 = BinaryOperation.create(sub_op, expr, one.copy()) - array.children[idx] = expr2 + # Indices of an AoSReference start at index 1 + if isinstance(array, ArrayOfStructuresReference): + array.children[idx+1] = expr2 + else: + array.children[idx] = expr2 range_idx += 1 def _where_construct_handler(self, node, parent): @@ -4405,10 +4353,17 @@ def _contains_intrinsic_reduction(pnodes): # variable in the logical-array expression must be an array for # this to be a valid WHERE(). # TODO #1799. Look-up the shape of the array in the SymbolTable. - raise NotImplementedError( - f"Only WHERE constructs using explicit array notation (e.g. " - f"my_array(:,:)) are supported but found '{logical_expr}'.") - + for ref in fake_parent.walk(Reference): + if isinstance(ref.datatype, ArrayType): + ranges = [] + for dim in range(len(ref.datatype.shape)): + ranges.append(":") + if not isinstance(ref.datatype.datatype, ScalarType): + print("Hello") + raise NotImplementedError("TODO") + replace_ref = ArrayReference.create(ref.symbol, ranges) + ref.replace_with(replace_ref) + arrays.append(replace_ref) for array in arrays: if any(isinstance(idx, Range) for idx in array.indices): first_array = array @@ -4491,7 +4446,6 @@ def _contains_intrinsic_reduction(pnodes): # handler returns root_loop = loop new_parent = sched - # Now we have the loop nest, add an IF block to the innermost # schedule ifblock = IfBlock(parent=new_parent, annotations=annotations) @@ -4514,7 +4468,6 @@ def _contains_intrinsic_reduction(pnodes): # Now construct the body of the IF using the body of the WHERE sched = Schedule(parent=ifblock) ifblock.addchild(sched) - if was_single_stmt: # We only had a single-statement WHERE self.process_nodes(sched, node.items[1:]) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index a6ff7ca51a..9f284683ff 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -46,10 +46,10 @@ from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.nodes import ( ArrayMember, ArrayReference, Assignment, BinaryOperation, Call, CodeBlock, - Container, IfBlock, IntrinsicCall, Literal, Loop, Range, Routine, Schedule, - UnaryOperation) + Container, IfBlock, IntrinsicCall, Literal, Loop, Range, Reference, + Routine, Schedule, UnaryOperation) from psyclone.psyir.symbols import ( - DataSymbol, ArrayType, ScalarType, REAL_TYPE, INTEGER_TYPE, + DataSymbol, ScalarType, INTEGER_TYPE, UnresolvedInterface) @@ -197,56 +197,6 @@ def test_labelled_where(): assert isinstance(fake_parent.children[0], CodeBlock) -@pytest.mark.usefixtures("parser") -def test_missing_array_notation_lhs(): - ''' Check that we get a code block if the WHERE does not use explicit - array syntax on the LHS of an assignment within the body. - - ''' - fake_parent, _ = process_where("WHERE (ptsu(:,:,:) /= 0._wp)\n" - " z1_st = 1._wp / ptsu(:, :, :)\n" - "END WHERE\n", Fortran2003.Where_Construct, - ["ptsu", "z1_st"]) - assert isinstance(fake_parent.children[0], CodeBlock) - - -@pytest.mark.usefixtures("parser") -def test_missing_array_notation_in_assign(): - ''' Check that we get a code block if the WHERE contains an assignment - where no array notation appears. TODO #717 - extend this test so that - `z1_st` is of array type with rank 3. ''' - fake_parent, _ = process_where("WHERE (ptsu(:,:,:) /= 0._wp)\n" - " z1_st = 1._wp\n" - "END WHERE\n", Fortran2003.Where_Construct, - ["ptsu", "z1_st"]) - assert isinstance(fake_parent.children[0], CodeBlock) - - -@pytest.mark.usefixtures("parser") -def test_where_array_notation_rank(): - ''' Test that the _array_notation_rank() utility raises the expected - errors when passed an unsupported Array object. - ''' - array_type = ArrayType(REAL_TYPE, [10]) - symbol = DataSymbol("my_array", array_type) - my_array = ArrayReference(symbol) - processor = Fparser2Reader() - with pytest.raises(InternalError) as err: - processor._array_notation_rank(my_array) - assert ("ArrayReference malformed or incomplete: must have one or more " - "children representing array-index expressions but array " - "'my_array' has none." in str(err.value)) - array_type = ArrayType(REAL_TYPE, [10]) - my_array = ArrayReference.create( - DataSymbol("my_array", array_type), - [Range.create(Literal("2", INTEGER_TYPE), - Literal("10", INTEGER_TYPE))]) - with pytest.raises(NotImplementedError) as err: - processor._array_notation_rank(my_array) - assert ("Only array notation of the form my_array(:, :, ...) is " - "supported." in str(err.value)) - - @pytest.mark.usefixtures("parser") def test_different_ranks_error(): ''' Check that a WHERE construct containing array references of different @@ -260,35 +210,6 @@ def test_different_ranks_error(): assert isinstance(fake_parent.children[0], CodeBlock) -@pytest.mark.usefixtures("parser") -def test_array_notation_rank(): - ''' Check that the _array_notation_rank() utility handles various examples - of array notation. - - ''' - fake_parent = Schedule() - fake_parent.symbol_table.new_symbol("z1_st") - fake_parent.symbol_table.new_symbol("ptsu") - fake_parent.symbol_table.new_symbol("n") - processor = Fparser2Reader() - reader = FortranStringReader(" z1_st(:, 2, :) = ptsu(:, :, 3)") - fparser2spec = Fortran2003.Assignment_Stmt(reader) - processor.process_nodes(fake_parent, [fparser2spec]) - assert processor._array_notation_rank(fake_parent[0].lhs) == 2 - reader = FortranStringReader(" z1_st(:, :, 2, :) = ptsu(:, :, :, 3)") - fparser2spec = Fortran2003.Assignment_Stmt(reader) - processor.process_nodes(fake_parent, [fparser2spec]) - assert processor._array_notation_rank(fake_parent[1].lhs) == 3 - # We don't support bounds on slices - reader = FortranStringReader(" z1_st(:, 1:n, 2, :) = ptsu(:, :, :, 3)") - fparser2spec = Fortran2003.Assignment_Stmt(reader) - processor.process_nodes(fake_parent, [fparser2spec]) - with pytest.raises(NotImplementedError) as err: - processor._array_notation_rank(fake_parent[2].lhs) - assert ("Only array notation of the form my_array(:, :, ...) is " - "supported." in str(err.value)) - - def test_where_symbol_clash(fortran_reader): ''' Check that we handle the case where the code we are processing already contains a symbol with the same name as one of the loop variables @@ -604,19 +525,36 @@ def test_where_mask_containing_sum_with_dim(fortran_reader): @pytest.mark.usefixtures("parser") -@pytest.mark.parametrize("rhs", ["depth", "maxval(depth(:))"]) -@pytest.mark.xfail(reason="#717 need to distinguish scalar and array " - "assignments") def test_where_with_scalar_assignment(rhs): ''' Test that a WHERE containing a scalar assignment is handled correctly. Currently it is not as we do not distinguish between a scalar and an array reference that is missing its colons. This will be fixed in #717. ''' fake_parent, _ = process_where( - f"WHERE (dry(1, :, :))\n" - f" var1 = {rhs}\n" - f" z1_st(:, 2, :) = var1 / ptsu(:, :, 3)\n" - f"END WHERE\n", Fortran2003.Where_Construct, + "WHERE (dry(1, :, :))\n" + " var1 = depth\n" + " z1_st(:, 2, :) = var1 / ptsu(:, :, 3)\n" + "END WHERE\n", Fortran2003.Where_Construct, + ["dry", "z1_st", "depth", "ptsu", "var1"]) + # We should have a doubly-nested loop with an IfBlock inside + loops = fake_parent.walk(Loop) + assert len(loops) == 2 + for loop in loops: + assert "was_where" in loop.annotations + assert isinstance(loops[1].loop_body[0], IfBlock) + + +@pytest.mark.usefixtures("parser") +@pytest.mark.xfail(reason="#1960 Can't handle array-reduction intrinsics") +def test_where_with_array_reduction_intrinsic(): + ''' Test that a WHERE containing an array-reduction intrinsic is handled + correctly. Currently it is not supported. This will be fixed in #1960. + ''' + fake_parent, _ = process_where( + "WHERE (dry(1, :, :))\n" + " var1 = maxval(depth(:))\n" + " z1_st(:, 2, :) = var1 / ptsu(:, :, 3)\n" + "END WHERE\n", Fortran2003.Where_Construct, ["dry", "z1_st", "depth", "ptsu", "var1"]) # We should have a doubly-nested loop with an IfBlock inside loops = fake_parent.walk(Loop) @@ -804,7 +742,9 @@ def test_where_ordering(parser): ("where (my_type%block(jl)%var(:) > epsi20)\n" "my_type%block%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), ("where (my_type%block(jl)%var(:) > epsi20)\n" - "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var")]) + "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), + ("where (my_type(:)%var > epsi20)\n" + "my_type%array(:,jl) = 3.0\n", "my_type")]) def test_where_derived_type(fortran_reader, fortran_writer, code, size_arg): ''' Test that we handle the case where array members of a derived type are accessed within a WHERE. ''' @@ -831,3 +771,104 @@ def test_where_derived_type(fortran_reader, fortran_writer, code, size_arg): array_members = loops[0].walk(ArrayMember) for member in array_members: assert "+ widx1 - 1" in member.indices[0].debug_string() + + +@pytest.mark.parametrize( + "code", + [("where (my_type%var > epsi20)\n" + "my_type%array(:,jl) = 3.0\n", "my_type%var"), + ("where (my_type%var > epsi20)\n" + "my_type(jl)%array(:,jl) = 3.0\n", "my_type%var"), + ("where (my_type%block(jl)%var > epsi20)\n" + "my_type(jl%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), + ("where (my_type%block(jl)%var > epsi20)\n" + "my_type%block(jl)%var = 3.0\n", "my_type%block(jl)%var")]) +@pytest.mark.xfail(reason="#1960 Can't handle WHERE constructs without " + "explicit array notation inside derived types.") +def test_where_noarray_syntax_derived_types(fortran_reader, fortran_writer, + code): + '''Xfailing test for when a derived type access in a where condition + doesn't use range syntax.''' + code = (f"module my_mod\n" + f" use some_mod\n" + f"contains\n" + f"subroutine my_sub()\n" + f" integer :: jl\n" + f" do jl = 1, 10\n" + f"{code}" + f" end where\n" + f" end do\n" + f"end subroutine my_sub\n" + f"end module my_mod\n") + psyir = fortran_reader.psyir_from_source(code) + cbs = psyir.walk(CodeBlock) + assert len(cbs) == 0 + + +@pytest.mark.parametrize( + "code", + [("where (var(:) > 1.0)\n" + "var(:) = 3.0\n"), + ("where (var(:) > var2(1:20))\n" + "var(:) = 3.0\n"), + ("where (var > 1.0)\n" + "var = 3.0\n"), + ("where (var > var2)\n" + "var = 3.0\n"), + ("where (var(:) > var2)\n" + "var(:) = 3.0\n")]) +def test_where_scalar_var(fortran_reader, fortran_writer, code): + '''Test where we have a scalar variable in a WHERE clause with no + array index clause.''' + code = ( + f"program where_test\n" + f"implicit none\n" + f"integer, parameter :: nc=20\n" + f"integer :: ii\n" + f"real :: var(nc)\n" + f"real:: var2(nc)\n" + f"var = 1.5\n" + f"var2 = 1.6\n" + f"{code}" + f"end where\n" + f"end program where_test") + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + assert len(loops) == 1 + assert isinstance(loops[0].stop_expr, Reference) + assert loops[0].stop_expr.debug_string() == "nc" + assert isinstance(loops[0].loop_body[0], IfBlock) + # All Range nodes should have been replaced + assert not loops[0].walk(Range) + # All ArrayMember accesses should now use the `widx1` loop variable + array_members = loops[0].walk(ArrayMember) + for member in array_members: + assert "+ widx1 - 1" in member.indices[0].debug_string() + + +def test_where_other_cases(fortran_reader, fortran_writer): + code = ''' + program where_test + integer, parameter :: nc=20 + integer :: ii + real :: var(nc) + real :: var2(nc) + var = 1.5 + var2 = 1.6 + where (var > var2(1:20)) + var = 3.0 + end where + end program''' + psyir = fortran_reader.psyir_from_source(code) + loops = psyir.walk(Loop) + assert len(loops) == 1 + assert isinstance(loops[0].stop_expr, Literal) + assert loops[0].stop_expr.debug_string() == "20" + assert loops[0].start_expr.debug_string() == "1" + assert isinstance(loops[0].loop_body[0], IfBlock) + # All Range nodes should have been replaced + assert not loops[0].walk(Range) + # All ArrayMember accesses should now use the `widx1` loop variable + array_members = loops[0].walk(ArrayMember) + for member in array_members: + assert "+ widx1 - 1" in member.indices[0].debug_string() From 4719328e12f2a76ef43f1336314aa49a0b63243b Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:57:02 +0100 Subject: [PATCH 02/27] Further changes --- .../tests/psyir/frontend/fparser2_test.py | 94 ------------------- .../frontend/fparser2_where_handler_test.py | 2 +- 2 files changed, 1 insertion(+), 95 deletions(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 9780181c50..e4a1fda056 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -340,100 +340,6 @@ def test_get_arg_names(parser): # Class Fparser2Reader -def test_array_notation_rank(): - '''Test the static method _array_notation_rank in the fparser2reader - class. - - ''' - int_one = Literal("1", INTEGER_TYPE) - # Wrong type of argument - with pytest.raises(NotImplementedError) as err: - Fparser2Reader._array_notation_rank(int_one) - assert ("Expected either an ArrayReference, ArrayMember or a " - "StructureReference but got 'Literal'" in str(err.value)) - - # Structure reference containing no array access - symbol = DataSymbol("field", UnresolvedType()) - with pytest.raises(InternalError) as err: - Fparser2Reader._array_notation_rank( - StructureReference.create(symbol, ["first", "second"])) - assert "No array access found in node 'field'" in str(err.value) - - # Structure reference with ranges in more than one part reference. - lbound = IntrinsicCall.create( - IntrinsicCall.Intrinsic.LBOUND, - [StructureReference.create(symbol, ["first"]), - ("dim", int_one.copy())]) - ubound = IntrinsicCall.create( - IntrinsicCall.Intrinsic.UBOUND, - [StructureReference.create(symbol, ["first"]), - ("dim", int_one.copy())]) - my_range = Range.create(lbound, ubound) - with pytest.raises(InternalError) as err: - Fparser2Reader._array_notation_rank( - StructureReference.create(symbol, [("first", [my_range]), - ("second", [my_range.copy()])])) - assert ("Found a structure reference containing two or more part " - "references that have ranges: 'field%first(:)%second(" - "LBOUND(field%first, dim=1):UBOUND(field%first, dim=1))'. This is " - "not valid within a WHERE in Fortran." in str(err.value)) - # Repeat but this time for an ArrayOfStructuresReference. - with pytest.raises(InternalError) as err: - Fparser2Reader._array_notation_rank( - ArrayOfStructuresReference.create(symbol, [my_range.copy()], - ["first", - ("second", [my_range.copy()])])) - assert ("Found a structure reference containing two or more part " - "references that have ranges: 'field(LBOUND(field%first, dim=1):" - "UBOUND(field%first, dim=1))%first%second(" - "LBOUND(field%first, dim=1):UBOUND(field%first, dim=1))'. This is " - "not valid within a WHERE in Fortran." in str(err.value)) - - # An array with no dimensions raises an exception - array_type = ArrayType(REAL_TYPE, [10]) - symbol = DataSymbol("a", array_type) - array = ArrayReference(symbol) - with pytest.raises(InternalError) as excinfo: - Fparser2Reader._array_notation_rank(array) - assert ("ArrayReference malformed or incomplete: must have one or more " - "children representing array-index expressions but array 'a' has " - "none" in str(excinfo.value)) - - # If array syntax notation is found, it must be for all elements - # in that dimension - array_type = ArrayType(REAL_TYPE, [10, 10, 10]) - symbol = DataSymbol("a", array_type) - lbound_op1 = IntrinsicCall.create( - IntrinsicCall.Intrinsic.LBOUND, - [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) - ubound_op1 = IntrinsicCall.create( - IntrinsicCall.Intrinsic.UBOUND, - [Reference(symbol), ("dim", Literal("1", INTEGER_TYPE))]) - lbound_op3 = IntrinsicCall.create( - IntrinsicCall.Intrinsic.LBOUND, - [Reference(symbol), ("dim", Literal("3", INTEGER_TYPE))]) - ubound_op3 = IntrinsicCall.create( - IntrinsicCall.Intrinsic.UBOUND, - [Reference(symbol), ("dim", Literal("3", INTEGER_TYPE))]) - - range1 = Range.create(lbound_op1, ubound_op1) - range2 = Range.create(lbound_op3, ubound_op3) - one = Literal("1", INTEGER_TYPE) - array = ArrayReference.create(symbol, [range1, one.copy(), range2]) - result = Fparser2Reader._array_notation_rank(array) - # Two array dimensions use array notation. - assert result == 2 - - # Make one of the array notation dimensions differ from what is required. - range2 = Range.create(lbound_op3.copy(), one.copy()) - array = ArrayReference.create(symbol, [range1.copy(), one.copy(), - range2.copy()]) - with pytest.raises(NotImplementedError) as excinfo: - Fparser2Reader._array_notation_rank(array) - assert ("Only array notation of the form my_array(:, :, ...) is " - "supported." in str(excinfo.value)) - - def test_get_routine_schedules_wrong_module(parser): '''Test that get_routine_schedules() raises the expected errors if there are no or too many modules in the supplied parse tree.''' diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 9f284683ff..56ee0939dd 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -525,7 +525,7 @@ def test_where_mask_containing_sum_with_dim(fortran_reader): @pytest.mark.usefixtures("parser") -def test_where_with_scalar_assignment(rhs): +def test_where_with_scalar_assignment(): ''' Test that a WHERE containing a scalar assignment is handled correctly. Currently it is not as we do not distinguish between a scalar and an array reference that is missing its colons. This will be fixed in #717. From 72e13f2cfd590367140e132cb146bb0cc25133a3 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:02:56 +0100 Subject: [PATCH 03/27] Extra import still present after last commit --- src/psyclone/tests/psyir/frontend/fparser2_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index e4a1fda056..e3dd7c2bab 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -58,7 +58,7 @@ from psyclone.psyir.nodes import ( Schedule, CodeBlock, Assignment, Return, UnaryOperation, BinaryOperation, IfBlock, Reference, ArrayReference, Literal, Range, KernelSchedule, - RegionDirective, Routine, StandaloneDirective, StructureReference, + RegionDirective, Routine, StandaloneDirective, ArrayOfStructuresReference, Call, IntrinsicCall) from psyclone.psyir.symbols import ( DataSymbol, ContainerSymbol, ArgumentInterface, ArrayType, From c9be14e2866d5023d15b2b397daa9350b63a9b2a Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:13:16 +0100 Subject: [PATCH 04/27] Extra import still present after last commit --- src/psyclone/tests/psyir/frontend/fparser2_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index e3dd7c2bab..f5638ee504 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -59,7 +59,7 @@ Schedule, CodeBlock, Assignment, Return, UnaryOperation, BinaryOperation, IfBlock, Reference, ArrayReference, Literal, Range, KernelSchedule, RegionDirective, Routine, StandaloneDirective, - ArrayOfStructuresReference, Call, IntrinsicCall) + Call, IntrinsicCall) from psyclone.psyir.symbols import ( DataSymbol, ContainerSymbol, ArgumentInterface, ArrayType, SymbolError, ScalarType, INTEGER_TYPE, REAL_TYPE, RoutineSymbol, From 79e92660c37f84e75491bb1be662cd3d1ef9e6c5 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:35:22 +0100 Subject: [PATCH 05/27] Switch to use the Reference2ArrayRangeTrans to simplify this --- src/psyclone/psyir/frontend/fparser2.py | 61 ++++++------------- .../frontend/fparser2_where_handler_test.py | 32 +--------- 2 files changed, 23 insertions(+), 70 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 1958cb706c..fd4def1cd9 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4123,31 +4123,7 @@ def _array_syntax_to_indexed(self, parent, loop_vars): # in the PSyIR if possible. table = parent.scope.symbol_table one = Literal("1", INTEGER_TYPE) - refs = parent.walk(Reference) arrays = parent.walk(ArrayMixin) - for ref in refs: - add_op = BinaryOperation.Operator.ADD - sub_op = BinaryOperation.Operator.SUB - if (type(ref) is Reference and type(ref.symbol) is not Symbol and - isinstance(ref.symbol.datatype, ArrayType)): - # Skip if the shape doesn't match the loop_vars length. - if len(ref.datatype.shape) != len(loop_vars): - continue - # Have an implicit full range reference to an array which - # we need to convert ourselves. - temp_vals = [":" for i in range(len(ref.datatype.shape))] - array = ArrayReference.create(ref.symbol, temp_vals) - for dim in range(len(ref.datatype.shape)): - # Create the index expression. - symbol = table.lookup(loop_vars[dim]) - # We don't know what the lower bound is so have to - # have an expression: - # idx-expr = array-lower-bound + loop-idx - 1 - lbound = array.get_lbound_expression(dim) - expr = BinaryOperation.create( - add_op, lbound, Reference(symbol)) - expr2 = BinaryOperation.create(sub_op, expr, one.copy()) - array.children[dim] = expr2 first_rank = None for array in arrays: @@ -4257,6 +4233,9 @@ def _where_construct_handler(self, node, parent): not use array notation. ''' + from psyclone.psyir.transformations import ( + Reference2ArrayRangeTrans, TransformationError) + def _contains_intrinsic_reduction(pnodes): ''' Utility to check for Fortran intrinsics that perform a reduction @@ -4345,25 +4324,18 @@ def _contains_intrinsic_reduction(pnodes): # parent for this logical expression we will repeat the processing. fake_parent = Assignment(parent=parent) self.process_nodes(fake_parent, logical_expr) + # We want to convert all the non-explicit array syntax code to use + # explicit array syntax. + # TODO 1799 If we have two arrays where one uses a non-maximal range + # that is defined we will not convert this correctly yet. + for ref in fake_parent.walk(Reference): + if isinstance(ref.symbol, DataSymbol): + try: + Reference2ArrayRangeTrans().apply(ref) + except TransformationError: + pass arrays = fake_parent.walk(ArrayMixin) - if not arrays: - # If the PSyIR doesn't contain any Arrays then that must be - # because the code doesn't use explicit array syntax. At least one - # variable in the logical-array expression must be an array for - # this to be a valid WHERE(). - # TODO #1799. Look-up the shape of the array in the SymbolTable. - for ref in fake_parent.walk(Reference): - if isinstance(ref.datatype, ArrayType): - ranges = [] - for dim in range(len(ref.datatype.shape)): - ranges.append(":") - if not isinstance(ref.datatype.datatype, ScalarType): - print("Hello") - raise NotImplementedError("TODO") - replace_ref = ArrayReference.create(ref.symbol, ranges) - ref.replace_with(replace_ref) - arrays.append(replace_ref) for array in arrays: if any(isinstance(idx, Range) for idx in array.indices): first_array = array @@ -4457,6 +4429,13 @@ def _contains_intrinsic_reduction(pnodes): # second time here now that we have the correct parent node in the # PSyIR (and thus a SymbolTable) to refer to. self.process_nodes(ifblock, logical_expr) + # Convert References to arrays to use the array range notation. + for ref in ifblock.walk(Reference): + if isinstance(ref.symbol, DataSymbol): + try: + Reference2ArrayRangeTrans().apply(ref) + except TransformationError: + pass # Each array reference must now be indexed by the loop variables # of the loops we've just created. diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 56ee0939dd..f63d65f8d6 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -816,7 +816,9 @@ def test_where_noarray_syntax_derived_types(fortran_reader, fortran_writer, ("where (var > var2)\n" "var = 3.0\n"), ("where (var(:) > var2)\n" - "var(:) = 3.0\n")]) + "var(:) = 3.0\n"), + ("where (var > var2(1:20))\n" + "var = 3.0\n")]) def test_where_scalar_var(fortran_reader, fortran_writer, code): '''Test where we have a scalar variable in a WHERE clause with no array index clause.''' @@ -844,31 +846,3 @@ def test_where_scalar_var(fortran_reader, fortran_writer, code): array_members = loops[0].walk(ArrayMember) for member in array_members: assert "+ widx1 - 1" in member.indices[0].debug_string() - - -def test_where_other_cases(fortran_reader, fortran_writer): - code = ''' - program where_test - integer, parameter :: nc=20 - integer :: ii - real :: var(nc) - real :: var2(nc) - var = 1.5 - var2 = 1.6 - where (var > var2(1:20)) - var = 3.0 - end where - end program''' - psyir = fortran_reader.psyir_from_source(code) - loops = psyir.walk(Loop) - assert len(loops) == 1 - assert isinstance(loops[0].stop_expr, Literal) - assert loops[0].stop_expr.debug_string() == "20" - assert loops[0].start_expr.debug_string() == "1" - assert isinstance(loops[0].loop_body[0], IfBlock) - # All Range nodes should have been replaced - assert not loops[0].walk(Range) - # All ArrayMember accesses should now use the `widx1` loop variable - array_members = loops[0].walk(ArrayMember) - for member in array_members: - assert "+ widx1 - 1" in member.indices[0].debug_string() From 4752ef9974ebd47a3c64b27084444c16fbf78cd1 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:34:39 +0100 Subject: [PATCH 06/27] Error in the xfailing tests --- .../tests/psyir/frontend/fparser2_where_handler_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index f63d65f8d6..8916b7d6a7 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -776,13 +776,13 @@ def test_where_derived_type(fortran_reader, fortran_writer, code, size_arg): @pytest.mark.parametrize( "code", [("where (my_type%var > epsi20)\n" - "my_type%array(:,jl) = 3.0\n", "my_type%var"), + "my_type%array(:,jl) = 3.0\n"), ("where (my_type%var > epsi20)\n" - "my_type(jl)%array(:,jl) = 3.0\n", "my_type%var"), + "my_type(jl)%array(:,jl) = 3.0\n"), ("where (my_type%block(jl)%var > epsi20)\n" - "my_type(jl%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), + "my_type(jl%array(:,jl) = 3.0\n"), ("where (my_type%block(jl)%var > epsi20)\n" - "my_type%block(jl)%var = 3.0\n", "my_type%block(jl)%var")]) + "my_type%block(jl)%var = 3.0\n")]) @pytest.mark.xfail(reason="#1960 Can't handle WHERE constructs without " "explicit array notation inside derived types.") def test_where_noarray_syntax_derived_types(fortran_reader, fortran_writer, From b40ed651c99f3e3b13ea482d8533cc82d1afb3ea Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:17:18 +0100 Subject: [PATCH 07/27] Fixed issue with intrinsics (such as SUM) being broken and finished transforming other non-array notated ranges --- src/psyclone/psyir/frontend/fparser2.py | 20 ++++++++++++++++--- .../frontend/fparser2_where_handler_test.py | 20 ++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index fd4def1cd9..83480b733c 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4328,8 +4328,11 @@ def _contains_intrinsic_reduction(pnodes): # explicit array syntax. # TODO 1799 If we have two arrays where one uses a non-maximal range # that is defined we will not convert this correctly yet. + # Convert References to arrays to use the array range notation unless + # they have an IntrinsicCall parent. for ref in fake_parent.walk(Reference): - if isinstance(ref.symbol, DataSymbol): + if (isinstance(ref.symbol, DataSymbol) and + not ref.ancestor(IntrinsicCall)): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: @@ -4429,9 +4432,11 @@ def _contains_intrinsic_reduction(pnodes): # second time here now that we have the correct parent node in the # PSyIR (and thus a SymbolTable) to refer to. self.process_nodes(ifblock, logical_expr) - # Convert References to arrays to use the array range notation. + # Convert References to arrays to use the array range notation unless + # they have an IntrinsicCall parent. for ref in ifblock.walk(Reference): - if isinstance(ref.symbol, DataSymbol): + if (isinstance(ref.symbol, DataSymbol) and + not ref.ancestor(IntrinsicCall)): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: @@ -4519,6 +4524,15 @@ def _contains_intrinsic_reduction(pnodes): # the single if block. self.process_nodes(sched, node.content[1:-1]) + # Convert References to arrays to use the array range notation unless + # they have an IntrinsicCall parent. + for ref in ifblock.walk(Reference): + if (isinstance(ref.symbol, DataSymbol) and + not ref.ancestor(IntrinsicCall)): + try: + Reference2ArrayRangeTrans().apply(ref) + except TransformationError: + pass # Convert all uses of array syntax to indexed accesses self._array_syntax_to_indexed(ifblock, loop_vars) # Return the top-level loop generated by this handler diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 8916b7d6a7..46060c074b 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -45,9 +45,9 @@ from psyclone.errors import InternalError from psyclone.psyir.frontend.fparser2 import Fparser2Reader from psyclone.psyir.nodes import ( - ArrayMember, ArrayReference, Assignment, BinaryOperation, Call, CodeBlock, - Container, IfBlock, IntrinsicCall, Literal, Loop, Range, Reference, - Routine, Schedule, UnaryOperation) + ArrayMember, ArrayReference, Assignment, BinaryOperation, + Call, CodeBlock, Container, IfBlock, IntrinsicCall, Literal, Loop, Range, + Reference, Routine, Schedule, UnaryOperation) from psyclone.psyir.symbols import ( DataSymbol, ScalarType, INTEGER_TYPE, UnresolvedInterface) @@ -837,12 +837,18 @@ def test_where_scalar_var(fortran_reader, fortran_writer, code): psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) assert len(loops) == 1 + print(fortran_writer(psyir)) assert isinstance(loops[0].stop_expr, Reference) assert loops[0].stop_expr.debug_string() == "nc" assert isinstance(loops[0].loop_body[0], IfBlock) # All Range nodes should have been replaced assert not loops[0].walk(Range) - # All ArrayMember accesses should now use the `widx1` loop variable - array_members = loops[0].walk(ArrayMember) - for member in array_members: - assert "+ widx1 - 1" in member.indices[0].debug_string() + array_refs = loops[0].walk(ArrayReference) + for member in array_refs: + assert "widx1" in member.indices[0].debug_string() + + # All References to var or var2 should be ArrayReferences + refs = loops[0].walk(Reference) + for ref in refs: + if "var" in ref.symbol.name: + assert isinstance(ref, ArrayReference) From 7138f8bd844402376eba1eb9d626b51b8cf78ac2 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:42:58 +0100 Subject: [PATCH 08/27] Updates towards review --- src/psyclone/psyir/frontend/fparser2.py | 40 +++++++-------- .../frontend/fparser2_where_handler_test.py | 50 ++++++++++++++----- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 83480b733c..fb43f9fc5c 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4117,10 +4117,17 @@ def _array_syntax_to_indexed(self, parent, loop_vars): :raises NotImplementedError: if array sections of differing ranks are found. ''' - # If the supplied code accidentally omits array - # notation for an array reference on the RHS then we will - # identify it as a scalar and the produce array notated code - # in the PSyIR if possible. + from psyclone.psyir.transformations import ( + Reference2ArrayRangeTrans, TransformationError) + # Convert References to arrays to use the array range notation unless + # they have an IntrinsicCall parent. + for ref in parent.walk(Reference): + if (isinstance(ref.symbol, DataSymbol) and + not ref.ancestor(IntrinsicCall)): + try: + Reference2ArrayRangeTrans().apply(ref) + except TransformationError: + pass table = parent.scope.symbol_table one = Literal("1", INTEGER_TYPE) arrays = parent.walk(ArrayMixin) @@ -4324,7 +4331,7 @@ def _contains_intrinsic_reduction(pnodes): # parent for this logical expression we will repeat the processing. fake_parent = Assignment(parent=parent) self.process_nodes(fake_parent, logical_expr) - # We want to convert all the non-explicit array syntax code to use + # We want to convert all the plain references that are arrays to use # explicit array syntax. # TODO 1799 If we have two arrays where one uses a non-maximal range # that is defined we will not convert this correctly yet. @@ -4432,15 +4439,6 @@ def _contains_intrinsic_reduction(pnodes): # second time here now that we have the correct parent node in the # PSyIR (and thus a SymbolTable) to refer to. self.process_nodes(ifblock, logical_expr) - # Convert References to arrays to use the array range notation unless - # they have an IntrinsicCall parent. - for ref in ifblock.walk(Reference): - if (isinstance(ref.symbol, DataSymbol) and - not ref.ancestor(IntrinsicCall)): - try: - Reference2ArrayRangeTrans().apply(ref) - except TransformationError: - pass # Each array reference must now be indexed by the loop variables # of the loops we've just created. @@ -4526,13 +4524,13 @@ def _contains_intrinsic_reduction(pnodes): # Convert References to arrays to use the array range notation unless # they have an IntrinsicCall parent. - for ref in ifblock.walk(Reference): - if (isinstance(ref.symbol, DataSymbol) and - not ref.ancestor(IntrinsicCall)): - try: - Reference2ArrayRangeTrans().apply(ref) - except TransformationError: - pass + #for ref in ifblock.walk(Reference): + # if (isinstance(ref.symbol, DataSymbol) and + # not ref.ancestor(IntrinsicCall)): + # try: + # Reference2ArrayRangeTrans().apply(ref) + # except TransformationError: + # pass # Convert all uses of array syntax to indexed accesses self._array_syntax_to_indexed(ifblock, loop_vars) # Return the top-level loop generated by this handler diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 46060c074b..a879397865 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -525,23 +525,49 @@ def test_where_mask_containing_sum_with_dim(fortran_reader): @pytest.mark.usefixtures("parser") -def test_where_with_scalar_assignment(): +def test_where_with_scalar_assignment(fortran_reader, fortran_writer): ''' Test that a WHERE containing a scalar assignment is handled correctly. - Currently it is not as we do not distinguish between a scalar and an array - reference that is missing its colons. This will be fixed in #717. ''' - fake_parent, _ = process_where( - "WHERE (dry(1, :, :))\n" - " var1 = depth\n" - " z1_st(:, 2, :) = var1 / ptsu(:, :, 3)\n" - "END WHERE\n", Fortran2003.Where_Construct, - ["dry", "z1_st", "depth", "ptsu", "var1"]) + code = ''' + subroutine sub() + integer, dimension(100,100,100) :: dry, z1_st, ptsu + integer :: var1, depth + + where(dry(1, :, :)) + var1 = depth + z1_st(:,2,:) = var1 / ptsu(:, :, 3) + end where + end subroutine sub + ''' + fake_parent = fortran_reader.psyir_from_source(code) # We should have a doubly-nested loop with an IfBlock inside loops = fake_parent.walk(Loop) assert len(loops) == 2 for loop in loops: assert "was_where" in loop.annotations assert isinstance(loops[1].loop_body[0], IfBlock) + out = fortran_writer(fake_parent) + correct = '''subroutine sub() + integer, dimension(100,100,100) :: dry + integer, dimension(100,100,100) :: z1_st + integer, dimension(100,100,100) :: ptsu + integer :: var1 + integer :: depth + integer :: widx2 + integer :: widx1 + + do widx2 = 1, SIZE(dry, dim=3), 1 + do widx1 = 1, SIZE(dry, dim=2), 1 + if (dry(1,widx1,widx2)) then + var1 = depth + z1_st(widx1,2,widx2) = var1 / ptsu(widx1,widx2,3) + end if + enddo + enddo + +end subroutine sub +''' + assert out == correct @pytest.mark.usefixtures("parser") @@ -785,8 +811,7 @@ def test_where_derived_type(fortran_reader, fortran_writer, code, size_arg): "my_type%block(jl)%var = 3.0\n")]) @pytest.mark.xfail(reason="#1960 Can't handle WHERE constructs without " "explicit array notation inside derived types.") -def test_where_noarray_syntax_derived_types(fortran_reader, fortran_writer, - code): +def test_where_noarray_syntax_derived_types(fortran_reader, code): '''Xfailing test for when a derived type access in a where condition doesn't use range syntax.''' code = (f"module my_mod\n" @@ -819,7 +844,7 @@ def test_where_noarray_syntax_derived_types(fortran_reader, fortran_writer, "var(:) = 3.0\n"), ("where (var > var2(1:20))\n" "var = 3.0\n")]) -def test_where_scalar_var(fortran_reader, fortran_writer, code): +def test_where_scalar_var(fortran_reader, code): '''Test where we have a scalar variable in a WHERE clause with no array index clause.''' code = ( @@ -837,7 +862,6 @@ def test_where_scalar_var(fortran_reader, fortran_writer, code): psyir = fortran_reader.psyir_from_source(code) loops = psyir.walk(Loop) assert len(loops) == 1 - print(fortran_writer(psyir)) assert isinstance(loops[0].stop_expr, Reference) assert loops[0].stop_expr.debug_string() == "nc" assert isinstance(loops[0].loop_body[0], IfBlock) From aef4b6e7e0fcfd1df25175e0f0a083f97e006cf4 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:59:02 +0100 Subject: [PATCH 09/27] flaking error --- src/psyclone/psyir/frontend/fparser2.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index d7e2d3caec..c9539841f1 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4589,15 +4589,6 @@ def _contains_intrinsic_reduction(pnodes): # the single if block. self.process_nodes(sched, node.content[1:-1]) - # Convert References to arrays to use the array range notation unless - # they have an IntrinsicCall parent. - #for ref in ifblock.walk(Reference): - # if (isinstance(ref.symbol, DataSymbol) and - # not ref.ancestor(IntrinsicCall)): - # try: - # Reference2ArrayRangeTrans().apply(ref) - # except TransformationError: - # pass # Convert all uses of array syntax to indexed accesses self._array_syntax_to_indexed(ifblock, loop_vars) # Return the top-level loop generated by this handler From 2fb1ba3d03a8c9d98b84087a38aa3764bf476499 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:45:44 +0100 Subject: [PATCH 10/27] Fixed issues with imported symbols --- src/psyclone/psyir/frontend/fparser2.py | 21 ++++++++++-- .../frontend/fparser2_where_handler_test.py | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index c9539841f1..5c699a5321 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4189,6 +4189,10 @@ def _array_syntax_to_indexed(self, parent, loop_vars): # Convert References to arrays to use the array range notation unless # they have an IntrinsicCall parent. for ref in parent.walk(Reference): + if isinstance(ref.symbol.interface, ImportInterface): + raise NotImplementedError( + "PSyclone doesn't yet support reference to imported " + "symbols inside WHERE clauses.") if (isinstance(ref.symbol, DataSymbol) and not ref.ancestor(IntrinsicCall)): try: @@ -4404,13 +4408,26 @@ def _contains_intrinsic_reduction(pnodes): # that is defined we will not convert this correctly yet. # Convert References to arrays to use the array range notation unless # they have an IntrinsicCall parent. - for ref in fake_parent.walk(Reference): + references = fake_parent.walk(Reference) + for ref in references: + if isinstance(ref.symbol.interface, ImportInterface): + raise NotImplementedError( + "PSyclone doesn't yet support reference to imported " + "symbols inside WHERE clauses.") + intrinsic_ancestor = ref.ancestor(IntrinsicCall) if (isinstance(ref.symbol, DataSymbol) and - not ref.ancestor(IntrinsicCall)): + not intrinsic_ancestor): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: pass + elif (intrinsic_ancestor is not None and + intrinsic_ancestor.intrinsic.name.lower() not in + ["minval", "maxval", "sum", "lbound", "ubound"]): + raise NotImplementedError( + f"Intrinsic '{intrinsic_ancestor.intrinsic.name}' is " + f" not supported in a WHERE region") + arrays = fake_parent.walk(ArrayMixin) for array in arrays: diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index a879397865..664fac22a6 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -876,3 +876,36 @@ def test_where_scalar_var(fortran_reader, code): for ref in refs: if "var" in ref.symbol.name: assert isinstance(ref, ArrayReference) + + +def test_import_in_where_clause(fortran_reader): + ''' + Test that imported symbols inside a where clause produce a code block + as we don't know what is scalar vs an array. + ''' + code = ''' + program where_test + implicit none + use some_module, only: a, b, c, d + + where(a(:) + b > 1) + b = c + d + end where + end program + ''' + psyir = fortran_reader.psyir_from_source(code) + assert isinstance(psyir.children[0].children[0], CodeBlock) + + code2 = ''' + program where_test + implicit none + use some_module, only: c, d + integer, dimension(100) :: a, b + + where(a(:) + b > 1) + b = c + d + end where + end program + ''' + psyir = fortran_reader.psyir_from_source(code2) + assert isinstance(psyir.children[0].children[0], CodeBlock) From 94e82c732585a15ec64ecfd2149eac346e28905d Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:28:31 +0100 Subject: [PATCH 11/27] fix import outside top level statements --- src/psyclone/psyir/frontend/fparser2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 5c699a5321..2bae682cbc 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4184,6 +4184,7 @@ def _array_syntax_to_indexed(self, parent, loop_vars): :raises NotImplementedError: if array sections of differing ranks are found. ''' + # pylint: disable=import-outside-toplevel from psyclone.psyir.transformations import ( Reference2ArrayRangeTrans, TransformationError) # Convert References to arrays to use the array range notation unless @@ -4311,6 +4312,7 @@ def _where_construct_handler(self, node, parent): not use array notation. ''' + # pylint: disable=import-outside-toplevel from psyclone.psyir.transformations import ( Reference2ArrayRangeTrans, TransformationError) From dcf891186ee600d872cb85ab6b97209dbd3dc0eb Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:51:37 +0100 Subject: [PATCH 12/27] Failing coverage was unreachable, so removed --- src/psyclone/psyir/frontend/fparser2.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index af9d337770..1ec69912e2 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4423,12 +4423,6 @@ def _contains_intrinsic_reduction(pnodes): Reference2ArrayRangeTrans().apply(ref) except TransformationError: pass - elif (intrinsic_ancestor is not None and - intrinsic_ancestor.intrinsic.name.lower() not in - ["minval", "maxval", "sum", "lbound", "ubound"]): - raise NotImplementedError( - f"Intrinsic '{intrinsic_ancestor.intrinsic.name}' is " - f" not supported in a WHERE region") arrays = fake_parent.walk(ArrayMixin) From 9ac2e32df6ff37f7aca7568eb32ac8ebbcf9f3c5 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:21:42 +0100 Subject: [PATCH 13/27] Added a handler for non-reduction intrinsics --- src/psyclone/psyir/frontend/fparser2.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 1ec69912e2..baebeb16a9 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4194,8 +4194,16 @@ def _array_syntax_to_indexed(self, parent, loop_vars): raise NotImplementedError( "PSyclone doesn't yet support reference to imported " "symbols inside WHERE clauses.") + intrinsic_ancestor = ref.ancestor(IntrinsicCall) if (isinstance(ref.symbol, DataSymbol) and - not ref.ancestor(IntrinsicCall)): + not intrinsic_ancestor): + try: + Reference2ArrayRangeTrans().apply(ref) + except TransformationError: + pass + elif (isinstance(ref.symbol, DataSymbol) and intrinsic_ancestor + is not None and intrinsic_ancestor.intrinsic.name.upper() + not in Fortran2003.Intrinsic_Name.array_reduction_names): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: From 4163c4b6f1558d362077d38aa9137fbc5c07c36b Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 12 Sep 2024 10:45:15 +0100 Subject: [PATCH 14/27] Added a test for non reduction intrinsic --- .../frontend/fparser2_where_handler_test.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 664fac22a6..d54698b314 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -909,3 +909,37 @@ def test_import_in_where_clause(fortran_reader): ''' psyir = fortran_reader.psyir_from_source(code2) assert isinstance(psyir.children[0].children[0], CodeBlock) + + +def test_non_array_reduction_intrinsic(fortran_reader, fortran_writer): + ''' + Test that a non-array reduction intrinsic (such as SIN) produces + the correct PSyIR. + ''' + code = ''' + program where_test + implicit none + integer, dimension(100) :: a, b, c + + where(a < b) + c = sin(a) + end where + end program + ''' + psyir = fortran_reader.psyir_from_source(code) + assert isinstance(psyir.children[0].children[0], Loop) + out = fortran_writer(psyir) + correct = '''program where_test + integer, dimension(100) :: a + integer, dimension(100) :: b + integer, dimension(100) :: c + integer :: widx1 + + do widx1 = 1, 100, 1 + if (a(widx1) < b(widx1)) then + c(widx1) = SIN(a(widx1)) + end if + enddo + +end program where_test''' + assert correct in out From 3bd47f0b9a9d280d72a1b8facc0480e3670092f8 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:46:30 +0100 Subject: [PATCH 15/27] Handle the cases where there is a non elemental, non inquiry intrinsic call --- src/psyclone/psyir/frontend/fparser2.py | 5 ++-- .../frontend/fparser2_where_handler_test.py | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index baebeb16a9..aeb1adfcbb 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4202,8 +4202,8 @@ def _array_syntax_to_indexed(self, parent, loop_vars): except TransformationError: pass elif (isinstance(ref.symbol, DataSymbol) and intrinsic_ancestor - is not None and intrinsic_ancestor.intrinsic.name.upper() - not in Fortran2003.Intrinsic_Name.array_reduction_names): + is not None and (intrinsic_ancestor.is_elemental or + intrinsic_ancestor.is_inquiry)): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: @@ -4609,7 +4609,6 @@ def _contains_intrinsic_reduction(pnodes): # No elsewhere clauses were found so put the whole body into # the single if block. self.process_nodes(sched, node.content[1:-1]) - # Convert all uses of array syntax to indexed accesses self._array_syntax_to_indexed(ifblock, loop_vars) # Return the top-level loop generated by this handler diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index d54698b314..338ff5b2d8 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -943,3 +943,30 @@ def test_non_array_reduction_intrinsic(fortran_reader, fortran_writer): end program where_test''' assert correct in out + + +def test_non_elemental_intrinsic(fortran_reader, fortran_writer): + ''' + Test that a non-elemental reduction intrinsic (such as DOT_PRODUCT) + produces the correct PSyIR. + ''' + code = ''' + program where_test + implicit none + integer, dimension(100) :: a, b, c + + where(a < b) + c = DOT_PRODUCT(a, b) + end where + end program + ''' + psyir = fortran_reader.psyir_from_source(code) + # This should be a Loop I suppose, this is valid Fortran + # as long as a and b aren't expanded + assert isinstance(psyir.children[0].children[0], Loop) + intrinsic = psyir.walk(IntrinsicCall)[0] + assert intrinsic.intrinsic.name == "DOT_PRODUCT" + assert intrinsic.children[1].name == "a" + assert not isinstance(intrinsic.children[1], ArrayReference) + assert intrinsic.children[2].name == "b" + assert not isinstance(intrinsic.children[2], ArrayReference) From d8443b666552e44864a0277508d40d78df27d3c8 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:46:38 +0100 Subject: [PATCH 16/27] Removed the check for inquiry intrinsics as they don't care about the content of the arguments, so expanding the implcit array access is unnecessary --- src/psyclone/psyir/frontend/fparser2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index aeb1adfcbb..bea32a10d6 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4202,8 +4202,7 @@ def _array_syntax_to_indexed(self, parent, loop_vars): except TransformationError: pass elif (isinstance(ref.symbol, DataSymbol) and intrinsic_ancestor - is not None and (intrinsic_ancestor.is_elemental or - intrinsic_ancestor.is_inquiry)): + is not None and intrinsic_ancestor.is_elemental): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: From 200f28cd9b6510e51f7747543554f4c096cf80f1 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:27:29 +0100 Subject: [PATCH 17/27] Test for missing coverage --- .../frontend/fparser2_where_handler_test.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 338ff5b2d8..86f9b73c41 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -945,7 +945,7 @@ def test_non_array_reduction_intrinsic(fortran_reader, fortran_writer): assert correct in out -def test_non_elemental_intrinsic(fortran_reader, fortran_writer): +def test_non_elemental_intrinsic(fortran_reader): ''' Test that a non-elemental reduction intrinsic (such as DOT_PRODUCT) produces the correct PSyIR. @@ -970,3 +970,25 @@ def test_non_elemental_intrinsic(fortran_reader, fortran_writer): assert not isinstance(intrinsic.children[1], ArrayReference) assert intrinsic.children[2].name == "b" assert not isinstance(intrinsic.children[2], ArrayReference) + + +def test_intrinsic_transformation_error(fortran_reader): + ''' + Test for coverage for the try/except in the intrinsic_ancestor section + of _array_syntax_to_indexed sub function.''' + + code = ''' + program where_test + implicit none + integer, dimension(100) :: a, b, c + real :: d + + where(a < b) + c = sin(d) + end where + end program + ''' + psyir = fortran_reader.psyir_from_source(code) + references = psyir.walk(Reference) + # The d should not have been transformed into an array. + assert not isinstance(references[7], ArrayReference) From 4ab7966bf702ba687d8960baa73c8c2b5b8fefa0 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:01:07 +0100 Subject: [PATCH 18/27] Improve implicitCall handling to also handle elemental functions --- src/psyclone/psyir/frontend/fparser2.py | 11 ++++++----- .../psyir/frontend/fparser2_where_handler_test.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index bea32a10d6..06bb29ba0c 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4190,19 +4190,20 @@ def _array_syntax_to_indexed(self, parent, loop_vars): # Convert References to arrays to use the array range notation unless # they have an IntrinsicCall parent. for ref in parent.walk(Reference): - if isinstance(ref.symbol.interface, ImportInterface): + if isinstance(ref.symbol.interface, (ImportInterface, + UnresolvedInterface)): raise NotImplementedError( "PSyclone doesn't yet support reference to imported " "symbols inside WHERE clauses.") - intrinsic_ancestor = ref.ancestor(IntrinsicCall) + call_ancestor = ref.ancestor(Call) if (isinstance(ref.symbol, DataSymbol) and - not intrinsic_ancestor): + not call_ancestor): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: pass - elif (isinstance(ref.symbol, DataSymbol) and intrinsic_ancestor - is not None and intrinsic_ancestor.is_elemental): + elif (isinstance(ref.symbol, DataSymbol) and call_ancestor + is not None and call_ancestor.is_elemental): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 86f9b73c41..f67af1ce16 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -972,7 +972,7 @@ def test_non_elemental_intrinsic(fortran_reader): assert not isinstance(intrinsic.children[2], ArrayReference) -def test_intrinsic_transformation_error(fortran_reader): +def test_non_array_ref_intrinsic_transformation_error(fortran_reader): ''' Test for coverage for the try/except in the intrinsic_ancestor section of _array_syntax_to_indexed sub function.''' From 7772e88336a5a815a70f733e70ffa262fd2e2060 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:28:32 +0100 Subject: [PATCH 19/27] Changes from the review --- examples/nemo/scripts/acc_kernels_trans.py | 10 +-- examples/nemo/scripts/acc_loops_trans.py | 11 +-- examples/nemo/scripts/omp_gpu_trans.py | 11 +-- src/psyclone/psyir/frontend/fparser2.py | 1 + .../frontend/fparser2_where_handler_test.py | 88 +++++++++++-------- 5 files changed, 56 insertions(+), 65 deletions(-) diff --git a/examples/nemo/scripts/acc_kernels_trans.py b/examples/nemo/scripts/acc_kernels_trans.py index 3099bc87a3..e9cc5b1109 100755 --- a/examples/nemo/scripts/acc_kernels_trans.py +++ b/examples/nemo/scripts/acc_kernels_trans.py @@ -395,14 +395,8 @@ def trans(psyir): # In the lib_fortran file we annotate each routine that does not # have a Loop or unsupported Calls with the OpenACC Routine Directive if psyir.name == "lib_fortran.f90": - if not subroutine.walk(Loop): - calls = subroutine.walk(Call) - if all(call.is_available_on_device() for call in calls): - # SIGN_ARRAY_1D has a CodeBlock because of a WHERE without - # array notation. (TODO #717) - ACC_ROUTINE_TRANS.apply(subroutine, - options={"force": True}) - continue + if subroutine.name.lower().startswith("sign_"): + OMPDeclareTargetTrans().apply(subroutine) # Attempt to add OpenACC directives unless we are ignoring this routine if subroutine.name.lower() not in ACC_IGNORE: diff --git a/examples/nemo/scripts/acc_loops_trans.py b/examples/nemo/scripts/acc_loops_trans.py index bf7bb9994b..148dada444 100755 --- a/examples/nemo/scripts/acc_loops_trans.py +++ b/examples/nemo/scripts/acc_loops_trans.py @@ -110,15 +110,8 @@ def trans(psyir): # For performance in lib_fortran, mark serial routines as GPU-enabled if psyir.name == "lib_fortran.f90": - if not subroutine.walk(Loop): - try: - # We need the 'force' option. - # SIGN_ARRAY_1D has a CodeBlock because of a WHERE without - # array notation. (TODO #717) - ACCRoutineTrans().apply(subroutine, - options={"force": True}) - except TransformationError as err: - print(err) + if subroutine.name.lower().startswith("sign_"): + OMPDeclareTargetTrans().apply(subroutine) insert_explicit_loop_parallelism( subroutine, diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py index c890c8fe30..d32fcf1e93 100755 --- a/examples/nemo/scripts/omp_gpu_trans.py +++ b/examples/nemo/scripts/omp_gpu_trans.py @@ -100,15 +100,8 @@ def trans(psyir): # For performance in lib_fortran, mark serial routines as GPU-enabled if psyir.name == "lib_fortran.f90": - if not subroutine.walk(Loop): - try: - # We need the 'force' option. - # SIGN_ARRAY_1D has a CodeBlock because of a WHERE without - # array notation. (TODO #717) - OMPDeclareTargetTrans().apply(subroutine, - options={"force": True}) - except TransformationError as err: - print(err) + if subroutine.name.lower().startswith("sign_"): + OMPDeclareTargetTrans().apply(subroutine) # For now this is a special case for stpctl.f90 because it forces # loops to parallelise without many safety checks diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index c91f0f43d0..8021ff1bc5 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4245,6 +4245,7 @@ def _array_syntax_to_indexed(self, parent, loop_vars): base_ref = _copy_full_base_reference(array) array_ref = array.ancestor(Reference, include_self=True) if not isinstance(array_ref.datatype, ArrayType): + print(array_ref.debug_string()) raise NotImplementedError( f"We can not get the resulting shape of the expression: " f"{array_ref.debug_string()}") diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index f67af1ce16..d6e33e9d69 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -49,8 +49,7 @@ Call, CodeBlock, Container, IfBlock, IntrinsicCall, Literal, Loop, Range, Reference, Routine, Schedule, UnaryOperation) from psyclone.psyir.symbols import ( - DataSymbol, ScalarType, INTEGER_TYPE, - UnresolvedInterface) + DataSymbol, ScalarType, INTEGER_TYPE) def process_where(code, fparser_cls, symbols=None): @@ -239,8 +238,8 @@ def test_where_symbol_clash(fortran_reader): def test_where_within_loop(fortran_reader): - ''' Test for correct operation when we have a WHERE within an existing - loop and the referenced arrays are brought in from a module. ''' + ''' Test for correct operation (Codeblock) when we have a WHERE within an + existing loop and the referenced arrays are brought in from a module. ''' code = ("module my_mod\n" " use some_mod\n" "contains\n" @@ -259,27 +258,8 @@ def test_where_within_loop(fortran_reader): mymod = psyir.children[0] assert isinstance(mymod, Container) sub = mymod.children[0] - assert "var" in sub.symbol_table - assert "var2" in sub.symbol_table - assert isinstance(sub.symbol_table.lookup("var").interface, - UnresolvedInterface) - assert isinstance(sub.symbol_table.lookup("var2").interface, - UnresolvedInterface) assert isinstance(sub, Routine) - assert isinstance(sub[0], Loop) - assert sub[0].variable.name == "jl" - where_loop = sub[0].loop_body[0] - assert isinstance(where_loop, Loop) - assert where_loop.variable.name == "widx1" - assert len(where_loop.loop_body.children) == 1 - assert isinstance(where_loop.loop_body[0], IfBlock) - assign = where_loop.loop_body[0].if_body[0] - assert isinstance(assign, Assignment) - assert (assign.lhs.indices[0].debug_string() == - "LBOUND(var2, dim=1) + widx1 - 1") - assert assign.lhs.indices[1].debug_string() == "jl" - assert where_loop.start_expr.value == "1" - assert where_loop.stop_expr.debug_string() == "SIZE(var, dim=1)" + assert isinstance(sub[0].loop_body.children[0], CodeBlock) @pytest.mark.usefixtures("parser") @@ -349,10 +329,17 @@ def test_where_mask_starting_value(fortran_reader, fortran_writer): code = '''\ program my_sub use some_mod + type :: thing + real, dimension(100000, 100000, 1) :: z3 + end type + integer, parameter :: jpl = 123435 + integer, parameter :: jpr_ievp = 123 + type(thing), dimension(10000) :: frcv real, dimension(-5:5,-5:5) :: picefr real, DIMENSION(11,11,jpl) :: zevap_ice real, dimension(-2:8,jpl,-3:7) :: snow real, dimension(-22:0,jpl,-32:0) :: slush + integer, dimension(jpl) :: map WHERE( picefr(:,:) > 1.e-10 ) zevap_ice(:,:,1) = snow(:,3,:) * frcv(jpr_ievp)%z3(:,:,1) / picefr(:,:) @@ -368,8 +355,7 @@ def test_where_mask_starting_value(fortran_reader, fortran_writer): do widx1 = 1, 5 - (-5) + 1, 1 if (picefr(-5 + widx1 - 1,-5 + widx2 - 1) > 1.e-10) then zevap_ice(widx1,widx2,1) = snow(-2 + widx1 - 1,3,-3 + widx2 - 1) * \ -frcv(jpr_ievp)%z3(LBOUND(frcv(jpr_ievp)%z3, dim=1) + widx1 - 1,\ -LBOUND(frcv(jpr_ievp)%z3, dim=2) + widx2 - 1,1) / \ +frcv(jpr_ievp)%z3(widx1,widx2,1) / \ picefr(-5 + widx1 - 1,-5 + widx2 - 1) else zevap_ice(widx1,widx2,1) = snow(-2 + widx1 - 1,map(jpl),\ @@ -385,7 +371,14 @@ def test_where_mask_is_slice(fortran_reader, fortran_writer): ''' code = '''\ program my_sub - use some_mod + type :: thing + real, dimension(100000, 100000, 1) :: z3 + end type + type(thing), dimension(10000) :: frcv + real, dimension(-5:5,-5:5) :: picefr + real, DIMENSION(11,11,jpl) :: zevap_ice + integer :: jstart, jstop, jpl, jpr_ievp + WHERE( picefr(2:4,jstart:jstop) > 1.e-10 ) zevap_ice(:,:,1) = frcv(jpr_ievp)%z3(:,:,1) / picefr(:,:) ELSEWHERE ( picefr(1:3,jstart:jstop) > 4.e-10) @@ -398,11 +391,8 @@ def test_where_mask_is_slice(fortran_reader, fortran_writer): # Check that created loops have the correct number of iterations assert "do widx2 = 1, jstop - jstart + 1, 1" in out assert "do widx1 = 1, 4 - 2 + 1, 1" in out - # Check that the indexing into the mask expression uses the lower bounds - # specified in the original slice. assert "if (picefr(2 + widx1 - 1,jstart + widx2 - 1) > 1.e-10)" in out - assert ("zevap_ice(LBOUND(zevap_ice, dim=1) + widx1 - 1," - "LBOUND(zevap_ice, dim=2) + widx2 - 1,1) = 0.0" in out) + assert ("zevap_ice(widx1,widx2,1) = 0.0" in out) # If the lower bound of the slice is unity then we can use the loop # index directly. assert "if (picefr(widx1,jstart + widx2 - 1) > 4.e-10)" in out @@ -416,8 +406,13 @@ def test_where_mask_is_slice_lower_limit(fortran_reader, fortran_writer): ''' code = '''\ subroutine my_sub(picefr) - use some_mod real, dimension(3:,2:) :: picefr + type :: thing + real, dimension(100000, 100000, 1) :: z3 + end type + type(thing), dimension(10000) :: frcv + real, DIMENSION(11,11,jpl) :: zevap_ice + integer :: jstart, jstop, jpl, jpr_ievp WHERE( picefr(:4,jstart:) > 1.e-10 ) zevap_ice(:,:,1) = frcv(jpr_ievp)%z3(:,:,1) / picefr(:,:) ELSEWHERE ( picefr(4:5,jstart:) > 4.e-10) @@ -434,8 +429,7 @@ def test_where_mask_is_slice_lower_limit(fortran_reader, fortran_writer): # specified in the original slice. assert ("if (picefr(LBOUND(picefr, dim=1) + widx1 - 1," "jstart + widx2 - 1) > 1.e-10)" in out) - assert ("zevap_ice(LBOUND(zevap_ice, dim=1) + widx1 - 1," - "LBOUND(zevap_ice, dim=2) + widx2 - 1,1) = 0.0" in out) + assert ("zevap_ice(widx1,widx2,1) = 0.0" in out) assert "if (picefr(4 + widx1 - 1,jstart + widx2 - 1) > 4.e-10)" in out @@ -484,6 +478,11 @@ def test_where_containing_sum_no_dim(fortran_reader, fortran_writer): REAL(wp), INTENT(in), DIMENSION(:,:) :: picefr REAL(wp), DIMENSION(jpi,jpj,jpl) :: zevap_ice REAL(wp), ALLOCATABLE, SAVE, DIMENSION(:,:,:) :: a_i_last_couple + type :: thing + real, dimension(100000, 100000, 1) :: z3 + end type + integer :: jpr_ievp + type(thing), dimension(10000) :: frcv WHERE( picefr(:,:) > SUM(picefr) ) zevap_ice(:,:,1) = frcv(jpr_ievp)%z3(:,:,1) * & SUM( a_i_last_couple ) / picefr(:,:) @@ -764,21 +763,32 @@ def test_where_ordering(parser): [("where (my_type%var(:) > epsi20)\n" "my_type%array(:,jl) = 3.0\n", "my_type%var"), ("where (my_type%var(:) > epsi20)\n" - "my_type(jl)%array(:,jl) = 3.0\n", "my_type%var"), + "my_type2(jl)%array2(:,jl) = 3.0\n", "my_type%var"), ("where (my_type%block(jl)%var(:) > epsi20)\n" "my_type%block%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), ("where (my_type%block(jl)%var(:) > epsi20)\n" - "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), - ("where (my_type(:)%var > epsi20)\n" - "my_type%array(:,jl) = 3.0\n", "my_type")]) -def test_where_derived_type(fortran_reader, fortran_writer, code, size_arg): + "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"),]) +def test_where_derived_type(fortran_reader, code, size_arg): ''' Test that we handle the case where array members of a derived type are accessed within a WHERE. ''' code = (f"module my_mod\n" f" use some_mod\n" f"contains\n" f"subroutine my_sub()\n" + f" type :: subtype\n" + f" real, dimension(:) :: var\n" + f" real, dimension(:,:) :: array\n" + f" end type\n" + f" type :: sometype\n" + f" real, dimension(:) :: var\n" + f" real, dimension(:) :: array\n" + f" real, dimension(:,:) :: array2\n" + f" type(subtype), dimension(:) :: block\n" + f" end type\n" + f" type(sometype) :: my_type\n" + f" type(sometype), dimension(:) :: my_type2\n" f" integer :: jl\n" + f" real :: epsi20\n" f" do jl = 1, 10\n" f"{code}" f" end where\n" From e62f349c43387fa392ff2e01a9036b33429954ac Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:48:01 +0100 Subject: [PATCH 20/27] Fixed errors in nemo scripts --- examples/nemo/scripts/acc_kernels_trans.py | 4 ++-- examples/nemo/scripts/acc_loops_trans.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/nemo/scripts/acc_kernels_trans.py b/examples/nemo/scripts/acc_kernels_trans.py index e9cc5b1109..ceac3e9038 100755 --- a/examples/nemo/scripts/acc_kernels_trans.py +++ b/examples/nemo/scripts/acc_kernels_trans.py @@ -63,7 +63,7 @@ from psyclone.psyGen import TransInfo from psyclone.psyir.nodes import ( IfBlock, ArrayReference, Assignment, BinaryOperation, Loop, Routine, - Literal, Call, ACCLoopDirective) + Literal, ACCLoopDirective) from psyclone.psyir.transformations import (ACCKernelsTrans, ACCUpdateTrans, TransformationError, ProfileTrans) from psyclone.transformations import ACCEnterDataTrans @@ -396,7 +396,7 @@ def trans(psyir): # have a Loop or unsupported Calls with the OpenACC Routine Directive if psyir.name == "lib_fortran.f90": if subroutine.name.lower().startswith("sign_"): - OMPDeclareTargetTrans().apply(subroutine) + ACC_ROUTINE_TRANS.apply(subroutine) # Attempt to add OpenACC directives unless we are ignoring this routine if subroutine.name.lower() not in ACC_IGNORE: diff --git a/examples/nemo/scripts/acc_loops_trans.py b/examples/nemo/scripts/acc_loops_trans.py index 148dada444..7d9466817f 100755 --- a/examples/nemo/scripts/acc_loops_trans.py +++ b/examples/nemo/scripts/acc_loops_trans.py @@ -40,9 +40,9 @@ from utils import ( insert_explicit_loop_parallelism, normalise_loops, add_profiling, enhance_tree_information, NOT_PERFORMANT, NOT_WORKING) -from psyclone.psyir.nodes import Loop, Routine +from psyclone.psyir.nodes import Routine from psyclone.transformations import ( - ACCParallelTrans, ACCLoopTrans, ACCRoutineTrans, TransformationError) + ACCParallelTrans, ACCLoopTrans, ACCRoutineTrans) PROFILING_ENABLED = True @@ -111,7 +111,7 @@ def trans(psyir): # For performance in lib_fortran, mark serial routines as GPU-enabled if psyir.name == "lib_fortran.f90": if subroutine.name.lower().startswith("sign_"): - OMPDeclareTargetTrans().apply(subroutine) + ACCRoutineTrans().apply(subroutine) insert_explicit_loop_parallelism( subroutine, From 96818705afc596d50bcd748796cfcb150e7e0be4 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:50:44 +0100 Subject: [PATCH 21/27] Extra comma removal --- .../tests/psyir/frontend/fparser2_where_handler_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index d6e33e9d69..7769e988e0 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -767,7 +767,7 @@ def test_where_ordering(parser): ("where (my_type%block(jl)%var(:) > epsi20)\n" "my_type%block%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), ("where (my_type%block(jl)%var(:) > epsi20)\n" - "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"),]) + "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var")]) def test_where_derived_type(fortran_reader, code, size_arg): ''' Test that we handle the case where array members of a derived type are accessed within a WHERE. ''' From 8e9d743b64d95a1985cfe58ac9114d86a9b55a0c Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:03:20 +0100 Subject: [PATCH 22/27] Readded continue to add kernels trans script that was removed in error --- examples/nemo/scripts/acc_kernels_trans.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/nemo/scripts/acc_kernels_trans.py b/examples/nemo/scripts/acc_kernels_trans.py index ceac3e9038..a5d57d7ec6 100755 --- a/examples/nemo/scripts/acc_kernels_trans.py +++ b/examples/nemo/scripts/acc_kernels_trans.py @@ -397,6 +397,7 @@ def trans(psyir): if psyir.name == "lib_fortran.f90": if subroutine.name.lower().startswith("sign_"): ACC_ROUTINE_TRANS.apply(subroutine) + continue # Attempt to add OpenACC directives unless we are ignoring this routine if subroutine.name.lower() not in ACC_IGNORE: From b8104b0acec57fada1ea36f5691203cdaa4cc67b Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:38:07 +0100 Subject: [PATCH 23/27] where handler change --- .../tests/psyir/frontend/fparser2_where_handler_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index 7769e988e0..f8efbddc65 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -767,7 +767,9 @@ def test_where_ordering(parser): ("where (my_type%block(jl)%var(:) > epsi20)\n" "my_type%block%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), ("where (my_type%block(jl)%var(:) > epsi20)\n" - "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var")]) + "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), + ("where (my_type2(:)%var > epsi20)\n" + "mytype2(:)%var = 3.0\n", "my_type2")]) def test_where_derived_type(fortran_reader, code, size_arg): ''' Test that we handle the case where array members of a derived type are accessed within a WHERE. ''' From 4f21a41bc2ba71f2298bff0f036e023f980e27e0 Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:40:14 +0100 Subject: [PATCH 24/27] Added AoS where test --- .../tests/psyir/frontend/fparser2_where_handler_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py index f8efbddc65..3b51510dcb 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_where_handler_test.py @@ -769,7 +769,7 @@ def test_where_ordering(parser): ("where (my_type%block(jl)%var(:) > epsi20)\n" "my_type%block(jl)%array(:,jl) = 3.0\n", "my_type%block(jl)%var"), ("where (my_type2(:)%var > epsi20)\n" - "mytype2(:)%var = 3.0\n", "my_type2")]) + "my_type2(:)%var = 3.0\n", "my_type2")]) def test_where_derived_type(fortran_reader, code, size_arg): ''' Test that we handle the case where array members of a derived type are accessed within a WHERE. ''' From 07f378831dc36c2163934e7221ded86ce9adb53d Mon Sep 17 00:00:00 2001 From: Aidan Chalk <3043914+LonelyCat124@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:16:39 +0100 Subject: [PATCH 25/27] Add continue into the acc loops trans script to mimic previous behaviour --- examples/nemo/scripts/acc_loops_trans.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/nemo/scripts/acc_loops_trans.py b/examples/nemo/scripts/acc_loops_trans.py index 7d9466817f..cc21500a76 100755 --- a/examples/nemo/scripts/acc_loops_trans.py +++ b/examples/nemo/scripts/acc_loops_trans.py @@ -112,6 +112,7 @@ def trans(psyir): if psyir.name == "lib_fortran.f90": if subroutine.name.lower().startswith("sign_"): ACCRoutineTrans().apply(subroutine) + continue insert_explicit_loop_parallelism( subroutine, From e714fcfa890e69f251112a39c31a047ca6df2bfe Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 30 Sep 2024 15:48:13 +0100 Subject: [PATCH 26/27] #2684 Update some comments --- examples/nemo/scripts/acc_kernels_trans.py | 4 ++-- examples/nemo/scripts/acc_loops_trans.py | 3 ++- examples/nemo/scripts/omp_gpu_trans.py | 6 +++++- src/psyclone/psyir/frontend/fparser2.py | 20 +++++++------------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/examples/nemo/scripts/acc_kernels_trans.py b/examples/nemo/scripts/acc_kernels_trans.py index a5d57d7ec6..a08439130c 100755 --- a/examples/nemo/scripts/acc_kernels_trans.py +++ b/examples/nemo/scripts/acc_kernels_trans.py @@ -392,8 +392,8 @@ def trans(psyir): for subroutine in psyir.walk(Routine): print(f"Transforming subroutine: {subroutine.name}") - # In the lib_fortran file we annotate each routine that does not - # have a Loop or unsupported Calls with the OpenACC Routine Directive + # In the lib_fortran file we annotate each routine of the SIGN_* + # interface with the OpenACC Routine Directive if psyir.name == "lib_fortran.f90": if subroutine.name.lower().startswith("sign_"): ACC_ROUTINE_TRANS.apply(subroutine) diff --git a/examples/nemo/scripts/acc_loops_trans.py b/examples/nemo/scripts/acc_loops_trans.py index cc21500a76..6d293d4b63 100755 --- a/examples/nemo/scripts/acc_loops_trans.py +++ b/examples/nemo/scripts/acc_loops_trans.py @@ -108,7 +108,8 @@ def trans(psyir): hoist_expressions=True ) - # For performance in lib_fortran, mark serial routines as GPU-enabled + # In the lib_fortran file we annotate each routine of the SIGN_* + # interface with the OpenACC Routine Directive if psyir.name == "lib_fortran.f90": if subroutine.name.lower().startswith("sign_"): ACCRoutineTrans().apply(subroutine) diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py index d32fcf1e93..916dc98688 100755 --- a/examples/nemo/scripts/omp_gpu_trans.py +++ b/examples/nemo/scripts/omp_gpu_trans.py @@ -98,10 +98,14 @@ def trans(psyir): hoist_expressions=True ) - # For performance in lib_fortran, mark serial routines as GPU-enabled + # In the lib_fortran file we annotate each routine of the SIGN_* + # interface with the OpenMP Declare Target Directive if psyir.name == "lib_fortran.f90": if subroutine.name.lower().startswith("sign_"): OMPDeclareTargetTrans().apply(subroutine) + # We continue parallelising inside the routine, but this could + # change if the parallelisation directive are not nestable, in + # which case we could add a 'continue' here # For now this is a special case for stpctl.f90 because it forces # loops to parallelise without many safety checks diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 8021ff1bc5..0fdcb32a63 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -4179,15 +4179,13 @@ def _process_case_value(self, selector, node, parent): def _array_syntax_to_indexed(self, parent, loop_vars): ''' - Utility function that modifies each ArrayReference object in the - supplied PSyIR fragment so that they are indexed using the supplied - loop variables rather than having colon array notation. This indexing - is always done relative to the declared lower bound of the array being - accessed. + Utility function that modifies bare References to arrays as + ArrayReferences, and each ArrayReference that is not inside an + elemental call to use the provided loop index. :param parent: root of PSyIR sub-tree to search for Array references to modify. - :type parent: :py:class:`psyclone.psyir.nodes.Node` + :type parent: :py:class:`psyclone.psyir.nodes.Node` :param loop_vars: the variable names for the array indices. :type loop_vars: list of str @@ -4206,8 +4204,7 @@ def _array_syntax_to_indexed(self, parent, loop_vars): "PSyclone doesn't yet support reference to imported " "symbols inside WHERE clauses.") call_ancestor = ref.ancestor(Call) - if (isinstance(ref.symbol, DataSymbol) and - not call_ancestor): + if (isinstance(ref.symbol, DataSymbol) and not call_ancestor): try: Reference2ArrayRangeTrans().apply(ref) except TransformationError: @@ -4245,7 +4242,6 @@ def _array_syntax_to_indexed(self, parent, loop_vars): base_ref = _copy_full_base_reference(array) array_ref = array.ancestor(Reference, include_self=True) if not isinstance(array_ref.datatype, ArrayType): - print(array_ref.debug_string()) raise NotImplementedError( f"We can not get the resulting shape of the expression: " f"{array_ref.debug_string()}") @@ -4425,10 +4421,8 @@ def _contains_intrinsic_reduction(pnodes): self.process_nodes(fake_parent, logical_expr) # We want to convert all the plain references that are arrays to use # explicit array syntax. - # TODO 1799 If we have two arrays where one uses a non-maximal range - # that is defined we will not convert this correctly yet. - # Convert References to arrays to use the array range notation unless - # they have an IntrinsicCall parent. + # TODO 2722: Should have the same logic as array_syntax_to_indexed + # regarding UnresolvedInterface and Elemental calls? references = fake_parent.walk(Reference) for ref in references: if isinstance(ref.symbol.interface, ImportInterface): From 9340fd6959ac3fdb2c33e3b208dcbc3eb8435731 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 30 Sep 2024 15:52:13 +0100 Subject: [PATCH 27/27] #2684 Update changelog --- changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog b/changelog index f730cbc747..a8f79b45fa 100644 --- a/changelog +++ b/changelog @@ -228,6 +228,9 @@ 78) PR #2678 for #2636. Alter LFRic PSy-layer to lookup nlevels for each kernel + 79) PR #2687 for #2684. Add support for WHERE constructs that contain + references without range-notation (and prevent some incorrect cases). + release 2.5.0 14th of February 2024 1) PR #2199 for #2189. Fix bugs with missing maps in enter data