Dear all,

this patch addresses a long-standing difference between gfortran and
other brands: when an array actual argument was passed to a procedure,
and the dummy argument had no intent specified, we would often create
packing and unpacking code.  Only the case of the dummy argument
having intent(in) did avoid the unpacking.

This resulted in the user experience that passing an array having the
PARAMETER attribute, i.e. a named constant that gcc could place in
read-only memory, was generating a segfault at runtime even if the
dummy argument was not modified in the invoked procedure.  It therefore
was often not possible passing such an argument to a procedure without
explicitly requiring a temporary that might not be needed. (*)

Other brands tested do not crash:

(1) Intel, Nvidia, AMD flang, g95 seem to not put PARAMETER into read-only
    memory.  One can write code by lying to the compiler and modify the
    array values.

(2) NAG appears to prevent modification of variables with the PARAMETER
    attribute.  Code that lies to the compiler seems to have no effect,
    but that compiler has a checking option that detects an illegal
    assignment in the called procedure.

The proposal is to simply not generate the unpacking / copying-back code
if the actual argument has the PARAMETER attribute.  Non-conforming code
should rather be either detected at compile-time (which we do to a
reasonable extent), or we might add (in the future) new checking code
that detects modification of the dummy similar to case (2) above.
(We do something like this e.g. for do-loop indices passed as actual
arguments).

How do you think of this approach?

BTW: attached patch regtests fine on x86_64-pc-linux-gnu.  ON for mainline?

Thanks,
Harald

(*) There is a missed-optimization in that we do not simply create
suitable array descriptors when passing to assumed-shape dummies,
which may avoid the packing.

From 387177dbeed5a2c6563d3c2275fee8a4d756d7a5 Mon Sep 17 00:00:00 2001
From: Harald Anlauf <anl...@gmx.de>
Date: Sun, 19 Jan 2025 21:06:56 +0100
Subject: [PATCH] Fortran: do not copy back for parameter actual arguments
 [PR81978]

When an array is packed for passing as an actual argument, and the array
has the PARAMETER attribute (i.e., it is a named constant that can reside
in read-only memory), do not copy back (unpack) from the temporary.

	PR fortran/81978

gcc/fortran/ChangeLog:

	* trans-array.cc (gfc_conv_array_parameter): Do not copy back data
	if actual array parameter has the PARAMETER attribute.
	* trans-expr.cc (gfc_conv_subref_array_arg): Likewise.

gcc/testsuite/ChangeLog:

	* gfortran.dg/pr81978.f90: New test.
---
 gcc/fortran/trans-array.cc            |  10 ++-
 gcc/fortran/trans-expr.cc             |  11 ++-
 gcc/testsuite/gfortran.dg/pr81978.f90 | 107 ++++++++++++++++++++++++++
 3 files changed, 124 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/pr81978.f90

diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc
index 44b091af2c6..ec627dddffd 100644
--- a/gcc/fortran/trans-array.cc
+++ b/gcc/fortran/trans-array.cc
@@ -8925,6 +8925,7 @@ gfc_conv_array_parameter (gfc_se *se, gfc_expr *expr, bool g77,
   bool good_allocatable;
   bool ultimate_ptr_comp;
   bool ultimate_alloc_comp;
+  bool readonly;
   gfc_symbol *sym;
   stmtblock_t block;
   gfc_ref *ref;
@@ -9381,8 +9382,13 @@ gfc_conv_array_parameter (gfc_se *se, gfc_expr *expr, bool g77,
 
       gfc_start_block (&block);
 
-      /* Copy the data back.  */
-      if (fsym == NULL || fsym->attr.intent != INTENT_IN)
+      /* Copy the data back.  If input expr is read-only, e.g. a PARAMETER
+	 array, copying back modified values is undefined behavior.  */
+      readonly = (expr->expr_type == EXPR_VARIABLE
+		  && expr->symtree
+		  && expr->symtree->n.sym->attr.flavor == FL_PARAMETER);
+
+      if ((fsym == NULL || fsym->attr.intent != INTENT_IN) && !readonly)
 	{
 	  if (ctree)
 	    {
diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index bef49d32a58..dcf42d53175 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -5200,6 +5200,7 @@ gfc_conv_subref_array_arg (gfc_se *se, gfc_expr * expr, int g77,
   gfc_se work_se;
   gfc_se *parmse;
   bool pass_optional;
+  bool readonly;
 
   pass_optional = fsym && fsym->attr.optional && sym && sym->attr.optional;
 
@@ -5416,8 +5417,14 @@ gfc_conv_subref_array_arg (gfc_se *se, gfc_expr * expr, int g77,
 
   /* Wrap the whole thing up by adding the second loop to the post-block
      and following it by the post-block of the first loop.  In this way,
-     if the temporary needs freeing, it is done after use!  */
-  if (intent != INTENT_IN)
+     if the temporary needs freeing, it is done after use!
+     If input expr is read-only, e.g. a PARAMETER array, copying back
+     modified values is undefined behavior.  */
+  readonly = (expr->expr_type == EXPR_VARIABLE
+	      && expr->symtree
+	      && expr->symtree->n.sym->attr.flavor == FL_PARAMETER);
+
+  if ((intent != INTENT_IN) && !readonly)
     {
       gfc_add_block_to_block (&parmse->post, &loop2.pre);
       gfc_add_block_to_block (&parmse->post, &loop2.post);
diff --git a/gcc/testsuite/gfortran.dg/pr81978.f90 b/gcc/testsuite/gfortran.dg/pr81978.f90
new file mode 100644
index 00000000000..b377eef7a16
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/pr81978.f90
@@ -0,0 +1,107 @@
+! { dg-do run }
+! PR fortran/81978 - do not copy back for parameter actual arguments
+
+module test_mod
+  implicit none
+
+  type pp_struct
+     character(10) :: name
+     real          :: value
+  end type pp_struct
+
+  type(pp_struct), parameter :: pp(4) = [ &
+       pp_struct('one',   1.), &
+       pp_struct('two',   2.), &
+       pp_struct('three', 3.), &
+       pp_struct('four',  4.)  ]
+
+contains
+
+  subroutine match_word (names)
+    character(*)        :: names(:)
+  end subroutine match_word
+
+  subroutine sub0 (a)
+    real                :: a(:)
+  end
+
+  subroutine sub1 (a, n)
+    integer, intent(in) :: n
+    real                :: a(n)
+  end
+
+  subroutine subx (a)
+    real                :: a(..)
+  end
+end module
+
+program test
+  use test_mod
+  implicit none
+  integer :: i, n
+  integer, parameter :: m = 8
+  real,    parameter :: x(m) = [(i,i=1,m)]
+
+  n = size (x)
+  call sub0 (x)
+  call sub1 (x, n)
+  call sub2 (x, n)
+  call subx (x)
+
+  i = 1
+  call sub0 (x(1::i))
+  call sub1 (x(1::i), n)
+  call sub2 (x(1::i), n)
+  call subx (x(1::i))
+
+  n = size (x(1::2))
+  call sub0 (x(1::2))
+  call sub1 (x(1::2), n)
+  call sub2 (x(1::2), n)
+  call subx (x(1::2))
+
+  i = 2
+  call sub0 (x(1::i))
+  call sub1 (x(1::i), n)
+  call sub2 (x(1::i), n)
+  call subx (x(1::i))
+
+  call match_word (pp%name)
+  call sub0       (pp%value)
+  call subx       (pp%value)
+  call match_word (pp(1::2)%name)
+  call sub0       (pp(1::2)%value)
+  call subx       (pp(1::2)%value)
+  i = 1
+  call match_word (pp(1::i)%name)
+  call sub0       (pp(1::i)%value)
+  call subx       (pp(1::i)%value)
+  i = 2
+  call match_word (pp(1::i)%name)
+  call sub0       (pp(1::i)%value)
+  call subx       (pp(1::i)%value)
+
+  call foo  (pp%name,        size(pp%name))
+  call foo  (pp(1::2)%name,  size(pp(1::2)%name))
+  call sub1 (pp(1::2)%value, size(pp(1::2)%value))
+  call sub2 (pp(1::2)%value, size(pp(1::2)%value))
+  i = 1
+  call foo  (pp(1::i)%name,  size(pp(1::i)%name))
+  call sub1 (pp(1::i)%value, size(pp(1::i)%value))
+  call sub2 (pp(1::i)%value, size(pp(1::i)%value))
+  i = 2
+  call foo  (pp(1::i)%name,  size(pp(1::i)%name))
+  call sub1 (pp(1::i)%value, size(pp(1::i)%value))
+  call sub2 (pp(1::i)%value, size(pp(1::i)%value))
+end program
+
+subroutine sub2 (a, n)
+  integer, intent(in) :: n
+  real                :: a(n)
+end
+
+subroutine foo (s, n)
+  integer, intent(in) :: n
+  character(*)        :: s(*)
+! print *, len(s), n, s(n)
+end
-- 
2.43.0

Reply via email to