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.
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