[flang-commits] [flang] [flang] Copy named-constant arrays passed through implicit interfaces (PR #228582)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Fri Oct 2 13:52:06 PDT 2026


https://github.com/eugeneepshteyn created https://github.com/llvm/llvm-project/pull/228582

Since #222986, a named constant's own storage can be the actual argument. A named constant is not definable, so a conforming procedure never defines the dummy argument, but programs do pass named constants through implicit interfaces to procedures that define the dummy argument, and the dummy's characteristics are not known at such call sites. These programs used to work, because the named constant was passed in a temporary, and now crash writing into read-only memory. For example:

```fortran
program p
  integer, parameter :: c(4) = 1
  call fill(c)
end program
subroutine fill(x)
  integer, intent(out) :: x(4)
  x = 2
end subroutine
```

Pass a whole named-constant array or a section of one in a temporary again when the procedure is called through an implicit interface, including an external procedure defined in the same compilation unit, whose calls lowering prepares from the actual arguments. Array elements keep the named constant's storage, since they may start a sequence association that a temporary of the element alone would break, and calls through explicit interfaces are unchanged.

Assisted-by: AI


>From 5263a14c3563fd51c2f73f7debe23ada1437d058 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Fri, 2 Oct 2026 13:36:15 -0700
Subject: [PATCH] [flang] Copy named-constant arrays passed through implicit
 interfaces

Since #222986, a named constant's own storage can be the actual argument.
A named constant is not definable, so a conforming procedure never
defines the dummy argument, but programs do pass named constants through
implicit interfaces to procedures that define the dummy argument, and
the dummy's characteristics are not known at such call sites. These
programs used to work, because the named constant was passed in a
temporary, and now crash writing into read-only memory.

Pass a whole named-constant array or a section of one in a temporary
again when the procedure is called through an implicit interface,
including an external procedure defined in the same compilation unit,
whose calls are prepared from the actual arguments. Array elements keep
the named constant's storage, since they may start a sequence
association that a temporary of the element alone would break, and
calls through explicit interfaces are unchanged.
---
 flang/include/flang/Lower/CallInterface.h     |  9 ++
 flang/lib/Lower/CallInterface.cpp             | 25 +++++-
 flang/lib/Lower/ConvertCall.cpp               | 23 ++++-
 .../Lower/HLFIR/call-parameter-array-arg.f90  |  8 +-
 .../call-parameter-implicit-interface.f90     | 88 +++++++++++++++++++
 5 files changed, 146 insertions(+), 7 deletions(-)
 create mode 100644 flang/test/Lower/HLFIR/call-parameter-implicit-interface.f90

diff --git a/flang/include/flang/Lower/CallInterface.h b/flang/include/flang/Lower/CallInterface.h
index df0c73bd91eef..525d22e03a825 100644
--- a/flang/include/flang/Lower/CallInterface.h
+++ b/flang/include/flang/Lower/CallInterface.h
@@ -460,6 +460,15 @@ class CalleeInterface : public CallInterface<CalleeInterface> {
   Fortran::lower::pft::FunctionLikeUnit &funit;
 };
 
+/// Is the procedure called through an implicit interface at \p procRef?  This
+/// includes calls to an external procedure defined in the same compilation
+/// unit that can be called through an implicit interface: such calls are
+/// prepared according to the actual arguments, like calls to procedures whose
+/// definition is not visible.
+bool isCalledThroughImplicitInterface(
+    const Fortran::evaluate::ProcedureRef &procRef,
+    Fortran::evaluate::FoldingContext &foldingContext);
+
 /// Translate a procedure characteristics to an mlir::FunctionType signature.
 mlir::FunctionType
 translateSignature(const Fortran::evaluate::ProcedureDesignator &,
diff --git a/flang/lib/Lower/CallInterface.cpp b/flang/lib/Lower/CallInterface.cpp
index 2bfe94559cd88..d246f6eb448cb 100644
--- a/flang/lib/Lower/CallInterface.cpp
+++ b/flang/lib/Lower/CallInterface.cpp
@@ -279,6 +279,27 @@ static bool isExternalDefinedInSameCompilationUnit(
   return false;
 }
 
+/// Is a call to \p proc, whose characteristics are \p characteristic,
+/// prepared according to the actual arguments rather than to the dummy
+/// arguments of the procedure (see CallerInterface::characterize)?
+static bool isPreparedFromActualArguments(
+    const Fortran::evaluate::characteristics::Procedure &characteristic,
+    const Fortran::evaluate::ProcedureDesignator &proc) {
+  return !characteristic.HasExplicitInterface() ||
+         (isExternalDefinedInSameCompilationUnit(proc) &&
+          characteristic.CanBeCalledViaImplicitInterface());
+}
+
+bool Fortran::lower::isCalledThroughImplicitInterface(
+    const Fortran::evaluate::ProcedureRef &procRef,
+    Fortran::evaluate::FoldingContext &foldingContext) {
+  std::optional<Fortran::evaluate::characteristics::Procedure> characteristic =
+      Fortran::evaluate::characteristics::Procedure::Characterize(
+          procRef.proc(), foldingContext, /*emitError=*/false);
+  return !characteristic ||
+         isPreparedFromActualArguments(*characteristic, procRef.proc());
+}
+
 Fortran::evaluate::characteristics::Procedure
 Fortran::lower::CallerInterface::characterize() const {
   Fortran::evaluate::FoldingContext &foldingContext =
@@ -290,9 +311,7 @@ Fortran::lower::CallerInterface::characterize() const {
   // The characteristic may not contain the argument characteristic if the
   // ProcedureDesignator has no interface, or may mismatch in case of implicit
   // interface.
-  if (!characteristic->HasExplicitInterface() ||
-      (isExternalDefinedInSameCompilationUnit(procRef.proc()) &&
-       characteristic->CanBeCalledViaImplicitInterface())) {
+  if (isPreparedFromActualArguments(*characteristic, procRef.proc())) {
     // In HLFIR lowering, calls to subprogram with implicit interfaces are
     // always prepared according to the actual arguments. This is to support
     // cases where the implicit interfaces are "abused" in old and not so old
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 6cb12b8208203..8568dd3e6b9c8 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -1038,6 +1038,14 @@ struct CallContext {
     return false;
   }
 
+  /// Is the procedure called through an implicit interface?
+  bool calleeHasImplicitInterface() {
+    if (!implicitInterface)
+      implicitInterface = Fortran::lower::isCalledThroughImplicitInterface(
+          procRef, converter.getFoldingContext());
+    return *implicitInterface;
+  }
+
   const Fortran::evaluate::ProcedureRef &procRef;
   Fortran::lower::AbstractConverter &converter;
   Fortran::lower::SymMap &symMap;
@@ -1045,6 +1053,9 @@ struct CallContext {
   std::optional<mlir::Type> resultType;
   mlir::Location loc;
   bool doCopyIn;
+
+private:
+  std::optional<bool> implicitInterface;
 };
 
 using ExvAndCleanup =
@@ -1511,8 +1522,18 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
         // (genCopyIn requires a descriptor) and because compiler-generated
         // copy-out must never target the read-only storage of a
         // non-definable actual argument.
+        // A named constant is not definable, so a conforming procedure never
+        // defines a dummy argument associated with one.  Still, programs pass
+        // named constants through implicit interfaces to procedures that do
+        // define the dummy argument, and the dummy's characteristics are not
+        // known at such call sites.  Copy a whole named-constant array or a
+        // section of one in that case too, as was done before named constants
+        // were associated with their storage.  An array element is not
+        // copied: it may start a sequence association (F'2023 15.5.2.12),
+        // which a temporary holding only the element would break.
         (isParameterObjectOrSubObject(entity) &&
-         (suggestCopyIn || suggestCopyOut))) {
+         (suggestCopyIn || suggestCopyOut ||
+          (entity.isArray() && callContext.calleeHasImplicitInterface())))) {
       // Make a copy in a temporary.
       auto copy = hlfir::AsExprOp::create(builder, loc, entity);
       mlir::Type storageType = entity.getType();
diff --git a/flang/test/Lower/HLFIR/call-parameter-array-arg.f90 b/flang/test/Lower/HLFIR/call-parameter-array-arg.f90
index db89bd9462655..ea2cb462a8195 100644
--- a/flang/test/Lower/HLFIR/call-parameter-array-arg.f90
+++ b/flang/test/Lower/HLFIR/call-parameter-array-arg.f90
@@ -106,11 +106,13 @@ subroutine value_dummy()
   call byval(gp)
 end subroutine
 
-! Implicit interface: the whole named-constant array's address is passed
-! directly (known contiguous).
+! Implicit interface: the procedure may define the dummy argument, so the
+! whole named-constant array is passed in a temporary (see
+! call-parameter-implicit-interface.f90).
 ! CHECK-LABEL: func.func @_QPimplicit_iface
 ! CHECK: fir.address_of(@_QMmECgp)
-! CHECK-NOT: hlfir.as_expr
+! CHECK: hlfir.as_expr
+! CHECK: hlfir.associate
 ! CHECK: fir.call @_QPext_sub
 subroutine implicit_iface()
   use m
diff --git a/flang/test/Lower/HLFIR/call-parameter-implicit-interface.f90 b/flang/test/Lower/HLFIR/call-parameter-implicit-interface.f90
new file mode 100644
index 0000000000000..4a968af3273c0
--- /dev/null
+++ b/flang/test/Lower/HLFIR/call-parameter-implicit-interface.f90
@@ -0,0 +1,88 @@
+! RUN: %flang_fc1 -emit-hlfir %s -o - 2>/dev/null | FileCheck %s
+
+! Test that named-constant (PARAMETER) arrays and sections passed through an
+! implicit interface are copied into a temporary, since the procedure may
+! define the dummy argument, while array elements keep the named constant's
+! storage (they may start a sequence association) and calls through an
+! explicit interface still pass the storage directly.
+
+module m
+  implicit none
+contains
+  subroutine expl(x)
+    integer :: x(4)
+  end subroutine
+end module
+
+! Whole named-constant array to an external without an interface.
+! CHECK-LABEL: func.func @_QPwhole_implicit
+! CHECK:         %[[ADDR:.*]] = fir.address_of(@_QFwhole_implicitECc)
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ADDR]]
+! CHECK:         %[[EXPR:.*]] = hlfir.as_expr %[[DECL]]#0
+! CHECK:         %[[TMP:.*]]:3 = hlfir.associate %[[EXPR]]
+! CHECK:         fir.call @_QPext(%[[TMP]]#0)
+! CHECK:         hlfir.end_associate %[[TMP]]#1, %[[TMP]]#2
+subroutine whole_implicit()
+  integer, parameter :: c(4) = [1, 2, 3, 4]
+  call ext(c)
+end subroutine
+
+! Contiguous section of a named constant to an external without an interface.
+! CHECK-LABEL: func.func @_QPsection_implicit
+! CHECK:         %[[SEC:.*]] = hlfir.designate %{{.*}} (%{{.*}}:%{{.*}}:%{{.*}})
+! CHECK:         %[[EXPR:.*]] = hlfir.as_expr %[[SEC]]
+! CHECK:         %[[TMP:.*]]:3 = hlfir.associate %[[EXPR]]
+! CHECK:         %[[CAST:.*]] = fir.convert %[[TMP]]#0
+! CHECK:         fir.call @_QPext(%[[CAST]])
+! CHECK:         hlfir.end_associate %[[TMP]]#1, %[[TMP]]#2
+subroutine section_implicit()
+  integer, parameter :: c(4) = [1, 2, 3, 4]
+  call ext(c(2:3))
+end subroutine
+
+! Whole named-constant array to an external defined in this file but called
+! without an interface: the call is prepared from the actual arguments, as for
+! an external defined elsewhere.
+! CHECK-LABEL: func.func @_QPwhole_same_file
+! CHECK:         %[[EXPR:.*]] = hlfir.as_expr
+! CHECK:         %[[TMP:.*]]:3 = hlfir.associate %[[EXPR]]
+! CHECK:         fir.call @_QPdefine_it(
+subroutine whole_same_file()
+  integer, parameter :: c(4) = [1, 2, 3, 4]
+  call define_it(c)
+end subroutine
+subroutine define_it(x)
+  integer, intent(out) :: x(4)
+  x = 0
+end subroutine
+
+! Named-constant array element to an external without an interface: the
+! element's address is passed, so that a sequence association sees the rest
+! of the named constant.
+! CHECK-LABEL: func.func @_QPelement_implicit
+! CHECK:         %[[ADDR:.*]] = fir.address_of(@_QFelement_implicitECc)
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ADDR]]
+! CHECK:         %[[ELT:.*]] = hlfir.designate %[[DECL]]#0 (%{{.*}})
+! CHECK-NOT:     hlfir.as_expr
+! CHECK-NOT:     hlfir.associate
+! CHECK:         %[[CAST:.*]] = fir.convert %[[ELT]]
+! CHECK-NOT:     hlfir.associate
+! CHECK:         fir.call @_QPext(%[[CAST]])
+subroutine element_implicit()
+  integer, parameter :: c(4) = [1, 2, 3, 4]
+  call ext(c(2))
+end subroutine
+
+! Whole named-constant array through an explicit interface: the named
+! constant's storage is passed directly.
+! CHECK-LABEL: func.func @_QPwhole_explicit
+! CHECK:         %[[ADDR:.*]] = fir.address_of(@_QFwhole_explicitECc)
+! CHECK:         %[[DECL:.*]]:2 = hlfir.declare %[[ADDR]]
+! CHECK-NOT:     hlfir.as_expr
+! CHECK-NOT:     hlfir.associate
+! CHECK:         fir.call @_QMmPexpl(%[[DECL]]#0)
+subroutine whole_explicit()
+  use m
+  integer, parameter :: c(4) = [1, 2, 3, 4]
+  call expl(c)
+end subroutine



More information about the flang-commits mailing list