[PATCH] fortran: [PR53800] Wrong copy-in/out with CLASS array to assumed-shape TYPE

Jerry D <[email protected]>
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.fortran
Message-ID <[email protected]>
See the attached patch.

As stated in the PR we were missing the stride multiplier (ie span)

I will note as a useful tool: I had an initial patch for this but wanted to 
exercise it. I prompted Claude Pro Opus 5 to create numerous variations on the 
original test case provided by Tobias Burnus in the original report. The result 
was 25 to 30 variations which were helpful in identifying code paths I would not 
have otherwise found.  I am not using those test cases here.

I flagged out this one to work on as it has been around a long time and we were 
giving wrong results without any idea it was happening.

I also wanted to mention that the fortran standardese is difficult to interpret 
but I think I got this right.

Regression tested on x86_64.

OK for mainline?

Comments appreciated.

Regards,

Jerry
---
     fortran: [PR53800] Wrong copy-in/out with CLASS array to assumed-shape TYPE

     A CLASS array actual passed to a plain assumed-shape derived-type dummy
     was always copied in and out, so pointers associated with the dummy went
     stale on return.  F2023 15.5.2.5 paragraph 9 requires the dummy to be
     associated with the actual's storage when it has the TARGET attribute and
     lacks CONTIGUOUS and VALUE; paragraph 8 leaves every other case free to
     use a copy.  Pass such a dummy by reference and address its elements with
     the descriptor's runtime span.

             PR fortran/53800

     gcc/fortran/ChangeLog:

             * trans-array.cc (need_span): New function.
             (gfc_get_array_span): Read the span from the saved descriptor of a
             span-array dummy, but not for a sub-array reached through a
             component.
             (gfc_conv_scalarized_array_ref): Use need_span.
             (gfc_conv_array_ref): Likewise.
             (build_array_ref): Default DECL to DESC for a span-array decl.
             * trans-decl.cc (gfc_build_dummy_array_decl): Mark a TARGET
             assumed-shape derived-type dummy as needing runtime span
             addressing, unless it is repacked.
             * trans-expr.cc (class_actual_aliases_type_dummy): New function.
             (gfc_class_array_data_assign): Also copy the span field.
             (gfc_conv_procedure_call): Use class_actual_aliases_type_dummy to
             skip copy-in/copy-out for both the class array reference and the
             class array function actual argument.
             * trans.cc (get_array_span): Read the span from the dummy's saved
             descriptor for span-array decls.
             * trans.h (struct lang_decl): Add span_array bitfield.
             (GFC_DECL_SPAN_ARRAY_P): New macro.
             (GFC_DECL_GET_SPAN_ARRAY_P): New macro.

     gcc/testsuite/ChangeLog:

             * gfortran.dg/class_to_type_5.f90: New test.
             * gfortran.dg/class_to_type_6.f90: New test.
pr53800-submit.diff (text/x-patch, 14.5 KB)
commit c31a44e4e831b16f5b509f28d79814c82ce8991b
Author: Jerry DeLisle <[email protected]>
Date:   Sat Jul 25 09:37:18 2026 -0700

    fortran: [PR53800] Wrong copy-in/out with CLASS array to assumed-shape TYPE
    
    A CLASS array actual passed to a plain assumed-shape derived-type dummy
    was always copied in and out, so pointers associated with the dummy went
    stale on return.  F2023 15.5.2.5 paragraph 9 requires the dummy to be
    associated with the actual's storage when it has the TARGET attribute and
    lacks CONTIGUOUS and VALUE; paragraph 8 leaves every other case free to
    use a copy.  Pass such a dummy by reference and address its elements with
    the descriptor's runtime span.
    
            PR fortran/53800
    
    gcc/fortran/ChangeLog:
    
            * trans-array.cc (need_span): New function.
            (gfc_get_array_span): Read the span from the saved descriptor of a
            span-array dummy, but not for a sub-array reached through a
            component.
            (gfc_conv_scalarized_array_ref): Use need_span.
            (gfc_conv_array_ref): Likewise.
            (build_array_ref): Default DECL to DESC for a span-array decl.
            * trans-decl.cc (gfc_build_dummy_array_decl): Mark a TARGET
            assumed-shape derived-type dummy as needing runtime span
            addressing, unless it is repacked.
            * trans-expr.cc (class_actual_aliases_type_dummy): New function.
            (gfc_class_array_data_assign): Also copy the span field.
            (gfc_conv_procedure_call): Use class_actual_aliases_type_dummy to
            skip copy-in/copy-out for both the class array reference and the
            class array function actual argument.
            * trans.cc (get_array_span): Read the span from the dummy's saved
            descriptor for span-array decls.
            * trans.h (struct lang_decl): Add span_array bitfield.
            (GFC_DECL_SPAN_ARRAY_P): New macro.
            (GFC_DECL_GET_SPAN_ARRAY_P): New macro.
    
    gcc/testsuite/ChangeLog:
    
            * gfortran.dg/class_to_type_5.f90: New test.
            * gfortran.dg/class_to_type_6.f90: New test.

diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc
index cf7eddf6e1d..0daa3f2ddad 100644
--- a/gcc/fortran/trans-array.cc
+++ b/gcc/fortran/trans-array.cc
@@ -490,6 +490,19 @@ is_pointer_array (tree expr)
 }
 
 
+/* Helper functon to identify when span is needed.  */
+
+static bool
+need_span (tree expr)
+{
+  if (expr == NULL_TREE || !VAR_P (expr))
+    return false;
+
+  return GFC_ARRAY_TYPE_P (TREE_TYPE (expr))
+	 && GFC_DECL_GET_SPAN_ARRAY_P (expr);
+}
+
+
 /* If the symbol or expression reference a CFI descriptor, return the
    pointer to the converted gfc descriptor. If an array reference is
    present as the last argument, check that it is the one applied to
@@ -588,6 +601,29 @@ gfc_get_array_span (tree desc, gfc_expr *expr)
     /* Having escaped the above, this can only be a class array dummy.  */
     tmp = class_array_element_size (sym->backend_decl,
 				    UNLIMITED_POLY (sym));
+  else if (sym && sym->backend_decl
+	   && GFC_DECL_GET_SPAN_ARRAY_P (sym->backend_decl)
+	   && (expr->ref == NULL
+	       || (expr->ref->type == REF_ARRAY && expr->ref->next == NULL)))
+    {
+      /* A assumed-shape derived-type dummy re-passed to another
+	 procedure; read its runtime span from the saved descriptor rather
+	 than using the compile-time element size.  The span applies to the
+	 dummy itself, not to a sub-array reached through a component.  */
+      tree saved_desc = GFC_DECL_SAVED_DESCRIPTOR (sym->backend_decl);
+      if (POINTER_TYPE_P (TREE_TYPE (saved_desc)))
+	saved_desc = build_fold_indirect_ref_loc (input_location, saved_desc);
+      tmp = gfc_conv_descriptor_span_get (saved_desc);
+
+      /* An absent optional dummy has no valid saved descriptor to read;
+	 avoid dereferencing it and fall back to the static element size. */
+      if (sym->attr.dummy && sym->attr.optional)
+	tmp = build3_loc (input_location, COND_EXPR, TREE_TYPE (tmp),
+			   gfc_conv_expr_present (sym), tmp,
+			   fold_convert (TREE_TYPE (tmp),
+					 TYPE_SIZE_UNIT (
+					   gfc_get_element_type (TREE_TYPE (desc)))));
+    }
   else
     {
       /* If none of the fancy stuff works, the span is the element
@@ -3980,6 +4016,7 @@ gfc_conv_scalarized_array_ref (gfc_se * se, gfc_array_ref * ar,
      the descriptor, mark the resulting variable decl and pass it to
      gfc_build_array_ref.  */
   if (is_pointer_array (info->descriptor)
+      || need_span (info->descriptor)
       || (expr && expr->ts.deferred && info->descriptor
 	  && GFC_DESCRIPTOR_TYPE_P (TREE_TYPE (info->descriptor))))
     {
@@ -4066,6 +4103,13 @@ build_array_ref (tree desc, tree offset, tree decl, tree vptr)
 	}
     }
 
+  /* Fall back to DESC itself so get_array_span can recognize a
+     span_array-marked dummy re-passed as a section actual argument.  */
+  if (decl == NULL_TREE
+      && (VAR_P (desc) || TREE_CODE (desc) == PARM_DECL)
+      && GFC_DECL_GET_SPAN_ARRAY_P (desc))
+    decl = desc;
+
   tmp = gfc_conv_array_data (desc);
   tmp = build_fold_indirect_ref_loc (input_location, tmp);
   tmp = gfc_build_array_ref (tmp, offset, decl,
@@ -4232,7 +4276,7 @@ gfc_conv_array_ref (gfc_se * se, gfc_array_ref * ar, gfc_expr *expr,
   if (get_CFI_desc (sym, expr, &decl, ar))
     decl = build_fold_indirect_ref_loc (input_location, decl);
   if (!expr->ts.deferred && !sym->attr.codimension
-      && is_pointer_array (se->expr))
+      && (is_pointer_array (se->expr) || need_span (se->expr)))
     {
       if (TREE_CODE (se->expr) == COMPONENT_REF)
 	decl = se->expr;
diff --git a/gcc/fortran/trans-decl.cc b/gcc/fortran/trans-decl.cc
index 1632556ae40..68e06a03cce 100644
--- a/gcc/fortran/trans-decl.cc
+++ b/gcc/fortran/trans-decl.cc
@@ -1408,6 +1408,15 @@ gfc_build_dummy_array_decl (gfc_symbol * sym, tree dummy)
 
   GFC_DECL_SAVED_DESCRIPTOR (decl) = dummy;
 
+  /* Mark dummies that may alias a CLASS actual's storage and so address
+     elements by the descriptor's runtime span.  A repacked dummy is a
+     contiguous local copy, so it is excluded.  */
+  if (sym->ts.type == BT_DERIVED && !is_classarray
+      && sym->attr.target && !sym->attr.pointer && !sym->attr.value
+      && !sym->attr.contiguous && as->type == AS_ASSUMED_SHAPE
+      && packed == PACKED_NO)
+    GFC_DECL_SPAN_ARRAY_P (decl) = 1;
+
   if (sym->ns->proc_name->backend_decl == current_function_decl
       || sym->attr.contained)
     gfc_add_decl_to_function (decl);
diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index 585d27fa5a2..c9e1f66edb1 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -837,6 +837,8 @@ gfc_class_array_data_assign (stmtblock_t *block, tree lhs_desc, tree rhs_desc,
 
   gfc_conv_descriptor_dtype_set (block, lhs_desc,
 				 gfc_conv_descriptor_dtype_get (rhs_desc));
+  gfc_conv_descriptor_span_set (block, lhs_desc,
+				gfc_conv_descriptor_span_get (rhs_desc));
 
   /* Assign the dimension as range-ref.  */
   lhs_dim = gfc_get_descriptor_dimension (lhs_desc);
@@ -7222,6 +7224,26 @@ conv_null_actual (gfc_se * parmse, gfc_expr * e, gfc_symbol * fsym)
 }
 
 
+/* Return true if a CLASS array actual may alias the derived-type dummy
+   FSYM rather than be passed through a copy-in/copy-out temporary.  F2023
+   15.5.2.5 paragraph 9 requires the association only for a TARGET
+   assumed-shape dummy without CONTIGUOUS or VALUE.  */
+
+static bool
+class_actual_aliases_type_dummy (gfc_symbol *fsym, bool nodesc_arg)
+{
+  return fsym != NULL
+	 && fsym->ts.type == BT_DERIVED
+	 && fsym->attr.target
+	 && !fsym->attr.pointer
+	 && !fsym->attr.value
+	 && !fsym->attr.contiguous
+	 && !nodesc_arg
+	 && fsym->as != NULL
+	 && fsym->as->type == AS_ASSUMED_SHAPE;
+}
+
+
 /* Generate code for a procedure call.  Note can return se->post != NULL.
    If se->direct_byref is set then se->expr contains the return parameter.
    Return nonzero, if the call has alternate specifiers.
@@ -8301,20 +8323,19 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
 		parmse.expr = e->symtree->n.sym->backend_decl;
 
 	      else if (gfc_is_class_array_ref (e, NULL)
-		       && fsym && fsym->ts.type == BT_DERIVED)
+		       && fsym && fsym->ts.type == BT_DERIVED
+		       && !class_actual_aliases_type_dummy (fsym, nodesc_arg))
 		/* The actual argument is a component reference to an
 		   array of derived types.  In this case, the argument
 		   is converted to a temporary, which is passed and then
-		   written back after the procedure call.
-		   OOP-TODO: Insert code so that if the dynamic type is
-		   the same as the declared type, copy-in/copy-out does
-		   not occur.  */
+		   written back after the procedure call.  */
 		gfc_conv_subref_array_arg (&parmse, e, nodesc_arg,
 					   fsym->attr.intent,
 					   fsym->attr.pointer);
 
 	      else if (gfc_is_class_array_function (e)
-		       && fsym && fsym->ts.type == BT_DERIVED)
+		       && fsym && fsym->ts.type == BT_DERIVED
+		       && !class_actual_aliases_type_dummy (fsym, nodesc_arg))
 		/* See previous comment.  For function actual argument,
 		   the write out is not needed so the intent is set as
 		   intent in.  */
diff --git a/gcc/fortran/trans.cc b/gcc/fortran/trans.cc
index cf37261673c..fd3df702dbb 100644
--- a/gcc/fortran/trans.cc
+++ b/gcc/fortran/trans.cc
@@ -454,6 +454,17 @@ get_array_span (tree type, tree decl)
 	    decl = build_fold_indirect_ref_loc (input_location, decl);
 	  span = gfc_conv_descriptor_span_get (decl);
 	}
+      else if (GFC_DECL_GET_SPAN_ARRAY_P (decl))
+	{
+	  /* The descriptor for the dummy is stored in the saved
+	     descriptor of its nodesc array decl.  */
+	  tree saved_desc = decl;
+	  if (DECL_LANG_SPECIFIC (decl) && GFC_DECL_SAVED_DESCRIPTOR (decl))
+	    saved_desc = GFC_DECL_SAVED_DESCRIPTOR (decl);
+	  if (POINTER_TYPE_P (TREE_TYPE (saved_desc)))
+	    saved_desc = build_fold_indirect_ref_loc (input_location, saved_desc);
+	  span = gfc_conv_descriptor_span_get (saved_desc);
+	}
       else
 	span = NULL_TREE;
     }
diff --git a/gcc/fortran/trans.h b/gcc/fortran/trans.h
index 7b28ecfce47..4063bbe70c4 100644
--- a/gcc/fortran/trans.h
+++ b/gcc/fortran/trans.h
@@ -1070,6 +1070,7 @@ struct GTY(()) lang_decl {
   unsigned int scalar_pointer : 1;
   unsigned int scalar_target : 1;
   unsigned int optional_arg : 1;
+  unsigned int span_array : 1;
 };
 
 
@@ -1087,12 +1088,16 @@ struct GTY(()) lang_decl {
   (DECL_LANG_SPECIFIC (node)->scalar_target)
 #define GFC_DECL_OPTIONAL_ARGUMENT(node) \
   (DECL_LANG_SPECIFIC (node)->optional_arg)
+#define GFC_DECL_SPAN_ARRAY_P(node) \
+  (DECL_LANG_SPECIFIC (node)->span_array)
 #define GFC_DECL_GET_SCALAR_ALLOCATABLE(node) \
   (DECL_LANG_SPECIFIC (node) ? GFC_DECL_SCALAR_ALLOCATABLE (node) : 0)
 #define GFC_DECL_GET_SCALAR_POINTER(node) \
   (DECL_LANG_SPECIFIC (node) ? GFC_DECL_SCALAR_POINTER (node) : 0)
 #define GFC_DECL_GET_SCALAR_TARGET(node) \
   (DECL_LANG_SPECIFIC (node) ? GFC_DECL_SCALAR_TARGET (node) : 0)
+#define GFC_DECL_GET_SPAN_ARRAY_P(node) \
+  (DECL_LANG_SPECIFIC (node) ? GFC_DECL_SPAN_ARRAY_P (node) : 0)
 #define GFC_DECL_PACKED_ARRAY(node) DECL_LANG_FLAG_0(node)
 #define GFC_DECL_PARTIAL_PACKED_ARRAY(node) DECL_LANG_FLAG_1(node)
 #define GFC_DECL_ASSIGN(node) DECL_LANG_FLAG_2(node)
diff --git a/gcc/testsuite/gfortran.dg/class_to_type_5.f90 b/gcc/testsuite/gfortran.dg/class_to_type_5.f90
new file mode 100644
index 00000000000..ad299db514d
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/class_to_type_5.f90
@@ -0,0 +1,35 @@
+! { dg-do run }
+! PR 53800
+
+! Check that a CLASS array with an extended dynamic type passed to an
+! assumed-shape TYPE dummy aliases the original storage, rather
+! than a copy-in/copy-out temporary that goes stale after return.
+!
+! Reported by Tobias Burnus  <[email protected]>
+
+program class_to_type
+  implicit none
+  type t
+    integer :: i
+  end type t
+  type, extends(t) :: t2
+    integer :: j
+  end type t2
+  class(t), target, allocatable :: a(:,:)
+  type(t), pointer :: ptr
+
+  allocate (t2 :: a(5,5))
+  a(:,:)%i = 53
+  a(3,3)%i = 42
+  a(4,4)%i = 74
+
+  call f (a)
+  if (ptr%i /= 42) stop 1
+  a(3,3)%i = 999
+  if (ptr%i /= 999) stop 2
+contains
+  subroutine f(x)
+    type(t), target :: x(:,:)
+    ptr => x(3,3)
+  end subroutine f
+end program class_to_type
diff --git a/gcc/testsuite/gfortran.dg/class_to_type_6.f90 b/gcc/testsuite/gfortran.dg/class_to_type_6.f90
new file mode 100644
index 00000000000..67d02c67fb8
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/class_to_type_6.f90
@@ -0,0 +1,93 @@
+! { dg-do run }
+! PR53800
+
+! A CLASS array actual passed to an assumed-shape TYPE dummy only
+! aliases the actual's storage when the dummy has the TARGET attribute.
+!
+module m
+  implicit none
+  type :: t
+    integer :: i
+  end type
+  type, extends(t) :: t2
+    integer :: pad(4)
+  end type
+  type :: u
+    integer :: k
+  end type
+  type :: c
+    integer  :: i
+    type(u)  :: sub(3)
+  end type
+  type, extends(c) :: c2
+    integer :: pad(4)
+  end type
+contains
+  ! A dummy (non-target): copy-in/copy-out,
+  subroutine plain (x)
+    type(t) :: x(:)
+    if (any (x%i /= [1,2,3,4,5])) stop 1
+    call expl (x)
+    if (any (cshift (x%i, 1) /= [2,3,4,5,1])) stop 3
+    if (any (pack (x%i, [.true.,.false.,.true.,.false.,.true.]) &
+             /= [1,3,5])) stop 4
+    if (any (reshape (x%i, [1,5]) /= reshape ([1,2,3,4,5], [1,5]))) stop 5
+    call to_class (x)
+  end subroutine
+
+  subroutine expl (y)
+    type(t) :: y(5)
+    if (any (y%i /= [1,2,3,4,5])) stop 2
+  end subroutine
+
+  subroutine to_class (z)
+    class(t) :: z(:)
+    if (any (z%i /= [1,2,3,4,5])) stop 6
+  end subroutine
+
+  ! A component sub-array of a span-carrying dummy has its own element
+  ! size and must not inherit the parent's span.
+  subroutine comp (x)
+    type(c), target :: x(:)
+    call inner (x(2)%sub)
+  end subroutine
+
+  subroutine inner (s)
+    type(u) :: s(:)
+    if (any (s%k /= [21,22,23])) stop 7
+  end subroutine
+end module
+
+program class_to_type_6
+  use m
+  implicit none
+  class(t), target, allocatable :: a(:)
+  class(c), target, allocatable :: b(:)
+  type(t), pointer :: p
+  integer :: n
+
+  allocate (t2 :: a(5))
+  do n = 1, 5
+    a(n)%i = n
+  end do
+  call plain (a)
+
+  allocate (c2 :: b(3))
+  do n = 1, 3
+    b(n)%i = 10 * n
+    b(n)%sub(:)%k = [10*n+1, 10*n+2, 10*n+3]
+  end do
+  call comp (b)
+
+  ! A TARGET assumed-shape dummy without CONTIGUOUS does alias.
+  call aliased (a)
+  if (p%i /= 3) stop 8
+  a(3)%i = 999
+  if (p%i /= 999) stop 9
+
+contains
+  subroutine aliased (x)
+    type(t), target :: x(:)
+    p => x(3)
+  end subroutine
+end program
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.