[flang-commits] [flang] [llvm] [flang] Finalize allocatable components in expression results (PR #153509)

Peter Klausler via flang-commits flang-commits at lists.llvm.org
Wed Aug 13 16:44:20 PDT 2025


https://github.com/klausler created https://github.com/llvm/llvm-project/pull/153509

When an expression result is being destroyed, it isn't finalized in general -- there's no item in the list of events that trigger finalization (F'2023 7.5.6.3p2) that applies.  However, any subobjects in the expression result that are allocatable must be deallocated, and deallocation is on that list of events, so make sure to finalize any allocatable finalizable subobject, unless the destruction is part of the left-hand side of an intrinsic assignment, in which case it will have already been finalized.

Note: the "stop 171" case in the test
  llvm-test-suite/Fortran/gfortran/regression/finalize_38.f90
will now fail, and a companion patch will disable that test when this patch is merged.

>From 9716cf5a47881cb93e5c1fc8398efb65b19cd21b Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Wed, 13 Aug 2025 16:18:05 -0700
Subject: [PATCH] [flang] Finalize allocatable components in expression results

When an expression result is being destroyed, it isn't finalized
in general -- there's no item in the list of events that trigger
finalization (F'2023 7.5.6.3p2) that applies.  However, any
subobjects in the expression result that are allocatable must be
deallocated, and deallocation is on that list of events, so
make sure to finalize any allocatable finalizable subobject,
unless the destruction is part of the left-hand side of an intrinsic
assignment, in which case it will have already been finalized.

Note: the "stop 171" case in the test
  llvm-test-suite/Fortran/gfortran/regression/finalize_38.f90
will now fail, and a companion patch will disable that test when
this patch is merged.
---
 .../include/flang-rt/runtime/work-queue.h     | 12 ++++++-----
 flang-rt/lib/runtime/assign.cpp               |  7 ++++---
 flang-rt/lib/runtime/derived-api.cpp          |  2 +-
 flang-rt/lib/runtime/derived.cpp              | 21 ++++++++++++-------
 flang/docs/Extensions.md                      | 20 ++++++------------
 5 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/flang-rt/include/flang-rt/runtime/work-queue.h b/flang-rt/include/flang-rt/runtime/work-queue.h
index 7d7f8ad991a57..cdcb0ec94d82a 100644
--- a/flang-rt/include/flang-rt/runtime/work-queue.h
+++ b/flang-rt/include/flang-rt/runtime/work-queue.h
@@ -298,15 +298,16 @@ class DestroyTicket : public ImmediateTicketRunner<DestroyTicket>,
                       private ComponentsOverElements {
 public:
   RT_API_ATTRS DestroyTicket(const Descriptor &instance,
-      const typeInfo::DerivedType &derived, bool finalize)
+      const typeInfo::DerivedType &derived, bool finalize, bool inLHS)
       : ImmediateTicketRunner<DestroyTicket>{*this},
         ComponentsOverElements{instance, derived}, finalize_{finalize},
-        fixedStride_{instance.FixedStride()} {}
+        inLHS_{inLHS}, fixedStride_{instance.FixedStride()} {}
   RT_API_ATTRS int Begin(WorkQueue &);
   RT_API_ATTRS int Continue(WorkQueue &);
 
 private:
   bool finalize_{false};
+  bool inLHS_{false};
   common::optional<SubscriptValue> fixedStride_;
 };
 
@@ -479,11 +480,12 @@ class WorkQueue {
     }
   }
   RT_API_ATTRS int BeginDestroy(const Descriptor &descriptor,
-      const typeInfo::DerivedType &derived, bool finalize) {
+      const typeInfo::DerivedType &derived, bool finalize, bool inLHS) {
     if (runTicketsImmediately_) {
-      return DestroyTicket{descriptor, derived, finalize}.Run(*this);
+      return DestroyTicket{descriptor, derived, finalize, inLHS}.Run(*this);
     } else {
-      StartTicket().u.emplace<DestroyTicket>(descriptor, derived, finalize);
+      StartTicket().u.emplace<DestroyTicket>(
+          descriptor, derived, finalize, inLHS);
       return StatContinue;
     }
   }
diff --git a/flang-rt/lib/runtime/assign.cpp b/flang-rt/lib/runtime/assign.cpp
index 6aeb103208785..5cb1163d20f21 100644
--- a/flang-rt/lib/runtime/assign.cpp
+++ b/flang-rt/lib/runtime/assign.cpp
@@ -373,7 +373,8 @@ RT_API_ATTRS int AssignTicket::Begin(WorkQueue &workQueue) {
       // is executed, any noncoarray allocated allocatable subobject of the
       // variable is deallocated before the assignment takes place."
       if (int status{
-              workQueue.BeginDestroy(to_, *toDerived_, /*finalize=*/false)};
+              workQueue.BeginDestroy(to_, *toDerived_, /*finalize=*/false,
+                  /*inLHS=*/true)};
           status != StatOk && status != StatContinue) {
         return status;
       }
@@ -683,8 +684,8 @@ RT_API_ATTRS int DerivedAssignTicket<IS_COMPONENTWISE>::Continue(
         if (toDesc->IsAllocated()) {
           if (this->phase_ == 0) {
             if (componentDerived && !componentDerived->noDestructionNeeded()) {
-              if (int status{workQueue.BeginDestroy(
-                      *toDesc, *componentDerived, /*finalize=*/false)};
+              if (int status{workQueue.BeginDestroy(*toDesc, *componentDerived,
+                      /*finalize=*/false, /*inLHS=*/true)};
                   status != StatOk) {
                 this->phase_++;
                 return status;
diff --git a/flang-rt/lib/runtime/derived-api.cpp b/flang-rt/lib/runtime/derived-api.cpp
index bb08e0397fe9c..38fd3557fb89c 100644
--- a/flang-rt/lib/runtime/derived-api.cpp
+++ b/flang-rt/lib/runtime/derived-api.cpp
@@ -46,7 +46,7 @@ void RTDEF(Destroy)(const Descriptor &descriptor) {
       if (!derived->noDestructionNeeded()) {
         // TODO: Pass source file & line information to the API
         // so that a good Terminator can be passed
-        Destroy(descriptor, true, *derived, nullptr);
+        Destroy(descriptor, /*finalize=*/true, *derived, nullptr);
       }
     }
   }
diff --git a/flang-rt/lib/runtime/derived.cpp b/flang-rt/lib/runtime/derived.cpp
index 2dddf079f91db..26d3d24644311 100644
--- a/flang-rt/lib/runtime/derived.cpp
+++ b/flang-rt/lib/runtime/derived.cpp
@@ -396,14 +396,15 @@ RT_API_ATTRS int FinalizeTicket::Continue(WorkQueue &workQueue) {
 
 // The order of finalization follows Fortran 2018 7.5.6.2, with
 // elementwise finalization of non-parent components taking place
-// before parent component finalization, and with all finalization
-// preceding any deallocation.
+// before parent component finalization, and with all "top level"
+// finalization preceding any deallocation.
 RT_API_ATTRS void Destroy(const Descriptor &descriptor, bool finalize,
     const typeInfo::DerivedType &derived, Terminator *terminator) {
   if (descriptor.IsAllocated() && !derived.noDestructionNeeded()) {
     Terminator stubTerminator{"Destroy() in Fortran runtime", 0};
     WorkQueue workQueue{terminator ? *terminator : stubTerminator};
-    if (workQueue.BeginDestroy(descriptor, derived, finalize) == StatContinue) {
+    if (workQueue.BeginDestroy(
+            descriptor, derived, finalize, /*inLHS=*/false) == StatContinue) {
       workQueue.Run();
     }
   }
@@ -440,8 +441,12 @@ RT_API_ATTRS int DestroyTicket::Continue(WorkQueue &workQueue) {
         if (d.IsAllocated()) {
           if (componentDerived && !componentDerived->noDestructionNeeded() &&
               phase_ == 0) {
-            if (int status{workQueue.BeginDestroy(
-                    d, *componentDerived, /*finalize=*/false)};
+            // Per F'2023 7.5.6.3p2, deallocations are finalization-triggering
+            // events -- so if an allocatable component was not finalized
+            // before, and we're not in the left-hand side of an intrinsic
+            // assignment, it must be finalized now.
+            if (int status{workQueue.BeginDestroy(d, *componentDerived,
+                    /*finalize=*/(!finalize_ && !inLHS_), inLHS_)};
                 status != StatOk) {
               ++phase_;
               return status;
@@ -466,7 +471,7 @@ RT_API_ATTRS int DestroyTicket::Continue(WorkQueue &workQueue) {
           compDesc.set_base_addr(p);
           ++elementAt_;
           if (int status{workQueue.BeginDestroy(
-                  compDesc, compType, /*finalize=*/false)};
+                  compDesc, compType, /*finalize=*/false, inLHS_)};
               status != StatOk) {
             return status;
           }
@@ -481,8 +486,8 @@ RT_API_ATTRS int DestroyTicket::Continue(WorkQueue &workQueue) {
             instance_.ElementComponent<char>(subscripts_, component_->offset()),
             component_->rank(), extents);
         Advance();
-        if (int status{
-                workQueue.BeginDestroy(compDesc, compType, /*finalize=*/false)};
+        if (int status{workQueue.BeginDestroy(
+                compDesc, compType, /*finalize=*/false, inLHS_)};
             status != StatOk) {
           return status;
         }
diff --git a/flang/docs/Extensions.md b/flang/docs/Extensions.md
index b20503e542fb8..f62c47a50b48c 100644
--- a/flang/docs/Extensions.md
+++ b/flang/docs/Extensions.md
@@ -817,20 +817,12 @@ end
   `INDEX` include an optional `BACK=` argument, but it doesn't actually
   work.
 
-* Allocatable components of array and structure constructors are deallocated
-  after use without calling final subroutines.
-  The standard does not specify when and how deallocation of array and structure
-  constructors allocatable components should happen. All compilers free the
-  memory after use, but the behavior when the allocatable component is a derived
-  type with finalization differ, especially when dealing with nested array and
-  structure constructors expressions. Some compilers call final routine for the
-  allocatable components of each constructor sub-expressions, some call it only
-  for the allocatable component of the top level constructor, and some only
-  deallocate the memory. Deallocating only the memory offers the most
-  flexibility when lowering such expressions, and it is not clear finalization
-  is desirable in such context (Fortran interop 1.6.2 in F2018 standards require
-  array and structure constructors not to be finalized, so it also makes sense
-  not to finalize their allocatable components when releasing their storage).
+* Allocatable components nested in expression results are finalized before being
+  deallocated.  F'2023 identifies expression results as "data entities", and
+  the deallocation of non-coarray allocatables as being an event that triggers
+  finalization.  Compilers differ widely in their finalization behavior in
+  expression results, especially with structure constructors, but at least two
+  others match this interpretation and it seems clear enough in the standard.
 
 * F'2023 19.4 paragraph 5: "If integer-type-spec appears in data-implied-do or
   ac-implied-do-control it has the specified type and type parameters; otherwise



More information about the flang-commits mailing list