diff --git a/changelog b/changelog index eefb52933b..9f5a9c54ff 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,5 @@ + 21) PR #3521 for #3460. Fixes issues with recursive datatype copies. + 20) PR #3518 for #3516. Fix issue with disappearing Interface declarations. 19) PR #3515 for #3513. Add a compilation test job with ifx and OpenMP flags. diff --git a/src/psyclone/psyir/symbols/data_type_symbol.py b/src/psyclone/psyir/symbols/data_type_symbol.py index ae983adf83..d325ca9cb5 100644 --- a/src/psyclone/psyir/symbols/data_type_symbol.py +++ b/src/psyclone/psyir/symbols/data_type_symbol.py @@ -74,7 +74,8 @@ def copy(self): :rtype: :py:class:`psyclone.psyir.symbols.TypeSymbol` ''' - copy = type(self)(self.name, self.datatype, visibility=self.visibility, + copy = type(self)(self.name, self.datatype.copy(), + visibility=self.visibility, interface=self.interface.copy()) copy.preceding_comment = self.preceding_comment copy.inline_comment = self.inline_comment diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index aa70b5cdb9..2c6b8a6404 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -317,10 +317,13 @@ def copy(self): new_init_value = self.initial_value.copy() else: new_init_value = None - if self.is_array: + # pylint: disable=import-outside-toplevel + from psyclone.psyir.symbols.datatypes import UnsupportedFortranType + if self.is_array or isinstance(self.datatype, UnsupportedFortranType): # Ensure any References in the shape definition of an ArrayType - # are also copied. They will still point to the - # same Symbols as the original. + # or in the partial datatype of an UnsupportedFortranType are also + # copied. They will still point to the same Symbols as the + # original. new_datatype = self.datatype.copy() else: new_datatype = self.datatype diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 654c42065a..188678ef9f 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -303,7 +303,8 @@ def replace_symbols_using(self, table_or_symbol): have matching names. If there is no match for a given Symbol then it is left unchanged. - This base implementation simply propagates the call to any child Nodes. + A partial datatype that is a DataTypeSymbol is replaced directly. It + must not be traversed because the datatype definition may be recursive. :param table_or_symbol: the symbol table from which to get replacement symbols or a single, replacement Symbol. @@ -311,8 +312,23 @@ def replace_symbols_using(self, table_or_symbol): :py:class:`psyclone.psyir.symbols.Symbol` ''' - if self.partial_datatype: - self.partial_datatype.replace_symbols_using(table_or_symbol) + partial_datatype = self.partial_datatype + if not partial_datatype: + return + + if not isinstance(partial_datatype, DataTypeSymbol): + partial_datatype.replace_symbols_using(table_or_symbol) + return + + if isinstance(table_or_symbol, Symbol): + if table_or_symbol.name.lower() != partial_datatype.name.lower(): + return + replacement = table_or_symbol + else: + replacement = table_or_symbol.lookup( + partial_datatype.name, otherwise=partial_datatype) + + self._partial_datatype = replacement @property def intrinsic(self): @@ -1467,16 +1483,20 @@ def replace_symbols_using(self, table_or_symbol): component.datatype.name, otherwise=component.datatype) else: - component.datatype.replace_symbols_using(table_or_symbol) - new_type = component.datatype + # Make a copy before updating any Symbol references so that + # replacing them does not modify the original StructureType. + new_type = component.datatype.copy() + new_type.replace_symbols_using(table_or_symbol) - if component.initial_value: - component.initial_value.replace_symbols_using(table_or_symbol) + initial_value = component.initial_value + if initial_value: + initial_value = initial_value.copy() + initial_value.replace_symbols_using(table_or_symbol) # Construct the new ComponentType key_name = component.name.lower() self.add(key_name, new_type, component.visibility, - component.initial_value, + initial_value, preceding_comment=component.preceding_comment, inline_comment=component.inline_comment) diff --git a/src/psyclone/psyir/transformations/inline_trans.py b/src/psyclone/psyir/transformations/inline_trans.py index 79bf05ce6f..c204794811 100644 --- a/src/psyclone/psyir/transformations/inline_trans.py +++ b/src/psyclone/psyir/transformations/inline_trans.py @@ -53,7 +53,6 @@ ArrayType, ScalarType, DataSymbol, - StructureType, SymbolError, UnresolvedType, UnsupportedType, @@ -238,6 +237,9 @@ def apply(self, f"have been caught by the validate() method. Original error " f"was {err}") from err + # TODO #3536: A shallow copy will create common subtrees/references + # between the inlined and the original. + # Replace any references to formal arguments with copies of the # actual arguments. formal_args = routine_table.argument_list @@ -275,34 +277,6 @@ def apply(self, new_shape.append(ArrayType.ArrayBounds(lower, upper)) sym.datatype = ArrayType(sym.datatype.elemental_type, new_shape) - for sym in table.datatypesymbols: - if not isinstance(sym.datatype, StructureType): - continue - for name, ctype in sym.datatype.components.items(): - if isinstance(ctype.datatype, ArrayType): - new_shape = [] - for dim in ctype.datatype.shape: - lower = self._replace_formal_args_in_expr( - dim.lower, node, formal_args, - routine_node=routine, - use_first_callee_and_no_arg_check=( - use_first_callee_and_no_arg_check), - ) - upper = self._replace_formal_args_in_expr( - dim.upper, node, formal_args, - routine_node=routine, - use_first_callee_and_no_arg_check=( - use_first_callee_and_no_arg_check), - ) - new_shape.append(ArrayType.ArrayBounds(lower, upper)) - sym.datatype.components[name] = ( - StructureType.ComponentType( - name=name, - datatype=ArrayType(ctype.datatype.elemental_type, - new_shape), - visibility=ctype.visibility, - initial_value=ctype.initial_value)) - # Copy the nodes from the Routine into the call site. # TODO #924 - while doing this we should ensure that any References # to common/shared Symbols in the inlined code are updated to point diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index b4fe8af1a7..ec855284e8 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -1392,3 +1392,64 @@ def test_structuretype___copy__(): # The components should be the same objects assert copied.components["nancy"] == stype.components["nancy"] assert copied.components["peggy"] == stype.components["peggy"] + + +def test_copy_with_recursions(fortran_reader): + '''Test the copy operation when the datatypes contain recursions between + them.''' + code = ''' + MODULE recursions + IMPLICIT NONE + + TYPE(recursive_type), POINTER :: use_before_decl + TYPE recursive_type + TYPE(recursive_type) :: supported + TYPE(recursive_type), POINTER :: unsupported + END TYPE recursive_type + TYPE(recursive_type), POINTER :: use_after_decl + + CONTAINS + SUBROUTINE mysub(arg) + TYPE(recursive_type), POINTER, INTENT(inout) :: arg + END SUBROUTINE mysub + + END MODULE recursions + ''' + psyir = fortran_reader.psyir_from_source(code) + copiedtree = psyir.copy() + + original_type = psyir.children[0].symbol_table.lookup("recursive_type") + copied_type = copiedtree.children[0].symbol_table.lookup("recursive_type") + assert copied_type is not original_type + + # Both components of the copied type must refer to the copied type symbol, + # and the equivalent components in the original must remain unchanged. + original_supported = original_type.datatype.lookup("supported").datatype + copied_supported = copied_type.datatype.lookup("supported").datatype + assert original_supported is original_type + assert copied_supported is copied_type + + original_unsupported = original_type.datatype.lookup( + "unsupported").datatype.partial_datatype + copied_unsupported = copied_type.datatype.lookup( + "unsupported").datatype.partial_datatype + assert original_unsupported is original_type + assert copied_unsupported is copied_type + + # Symbols declared both before and after the type definition must refer to + # the type symbol belonging to their respective trees. + for name in ["use_before_decl", "use_after_decl"]: + original_symbol = psyir.children[0].symbol_table.lookup(name) + copied_symbol = copiedtree.children[0].symbol_table.lookup(name) + assert copied_symbol is not original_symbol + assert copied_symbol.datatype is not original_symbol.datatype + assert original_symbol.datatype.partial_datatype is original_type + assert copied_symbol.datatype.partial_datatype is copied_type + + # The routine arguments must similarly refer to the type symbol belonging + # to their respective trees. + original_arg = psyir.walk(Routine)[0].symbol_table.lookup("arg") + copied_arg = copiedtree.walk(Routine)[0].symbol_table.lookup("arg") + assert copied_arg is not original_arg + assert original_arg.datatype.partial_datatype is original_type + assert copied_arg.datatype.partial_datatype is copied_type diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 6f9366357e..befc9ffb05 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -46,9 +46,10 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import ( Assignment, Call, IntrinsicCall, Loop, Node, Reference, - Routine, Statement) + Routine, Statement, Literal) from psyclone.psyir.symbols import ( - AutomaticInterface, DataSymbol, ImportInterface, UnresolvedType) + AutomaticInterface, DataSymbol, ImportInterface, UnresolvedType, + ScalarType) from psyclone.psyir.transformations import ( InlineTrans, TransformationError) from psyclone.tests.utilities import Compile, get_invoke @@ -2812,21 +2813,20 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): when inlined. ''' + # TODO #3534: Add an example of a len expression in an argument decalration code = ( "module test_mod\n" + " integer, parameter :: N = 10\n" "contains\n" "subroutine main()\n" - " real, dimension(10, 10) :: var = 0.0\n" + " real, dimension(N+10, 10) :: var = 0.0\n" " call sub(var, 10)\n" "end subroutine main\n" "subroutine sub(x, ilen)\n" " integer, intent(in) :: ilen\n" - " real, dimension(ilen, ilen), intent(inout) :: x\n" - " real, dimension(ilen, ilen) :: work\n" - " type nasty\n" - " integer, dimension(ilen+1) :: flag\n" - " end type nasty\n" - " type(nasty) :: oh_deary_me\n" + " real, dimension(N+ilen, ilen), intent(inout) :: x\n" + " real, dimension(N+ilen, ilen) :: work\n" + " character(len=3) :: string_work\n" " work = 2.0\n" " x(:,:) = x(:,:) + work(:,:)\n" "end subroutine sub\n" @@ -2836,17 +2836,34 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): call = psyir.walk(Call)[0] inline_trans = InlineTrans() inline_trans.apply(call) + + # ilen should not be in the caller main = psyir.children[0].find_routine_psyir("main") assert "ilen" not in main.symbol_table + main_output = fortran_writer(main) + assert "real, dimension(n + 10,10) :: work" in main_output + + # The the original should be unmodified + original = psyir.children[0].find_routine_psyir("sub") + original_output = fortran_writer(original) + assert "real, dimension(n + ilen,ilen) :: work" in original_output + + # The resulting code must be valid Fortran output = fortran_writer(psyir) - assert '''\ - type :: nasty - integer, dimension(10 + 1) :: flag - end type nasty''' in output - assert "real, dimension(10,10) :: work" in output - assert "type(nasty) :: oh_deary_me" in output assert Compile(tmp_path).string_compiles(output) + # After inlining whe should be able to modify them independently + str_work = main.symbol_table.lookup("string_work") + str_work.datatype.length = Literal("1", ScalarType.integer_type()) + main_output = fortran_writer(main) + original_output = fortran_writer(original) + assert "character(len=1) :: string_work" in main_output + + # The original should be (len=3) + assert "character(len=1) :: string_work" in original_output + pytest.xfail("#3536: After inlining symbols should be independent" + "copies") + def test_apply_array_access_check_unresolved_override_option( fortran_reader):