[flang-commits] [flang] [flang][cuda] Fix invalid host free of managed allocatable function results (PR #209945)

Zhen Wang via flang-commits flang-commits at lists.llvm.org
Thu Jul 16 09:59:36 PDT 2026


https://github.com/wangzpgi updated https://github.com/llvm/llvm-project/pull/209945

>From a5addce4d63b49b6bdf36357a0116106267069da Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 14 Jul 2026 18:47:49 -0700
Subject: [PATCH 1/4] Fix invalid host free of managed allocatable function
 results

---
 flang/lib/Lower/ConvertCall.cpp               | 41 ++++++++++++++++---
 .../Lower/CUDA/cuda-managed-func-result.cuf   | 30 ++++++++++++++
 2 files changed, 65 insertions(+), 6 deletions(-)
 create mode 100644 flang/test/Lower/CUDA/cuda-managed-func-result.cuf

diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 0eaee77251357..c6fe927bf0701 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -13,6 +13,7 @@
 #include "flang/Lower/ConvertCall.h"
 #include "flang/Lower/Allocatable.h"
 #include "flang/Lower/ConvertExprToHLFIR.h"
+#include "flang/Lower/CUDA.h"
 #include "flang/Lower/ConvertProcedureDesignator.h"
 #include "flang/Lower/ConvertVariable.h"
 #include "flang/Lower/CustomIntrinsicCall.h"
@@ -1972,25 +1973,53 @@ genUserCall(Fortran::lower::PreparedActualArguments &loweredActuals,
     // Allocatable result must be freed, other results are stack allocated.
     const auto *allocatable = result.getBoxOf<fir::MutableBoxValue>();
     const bool mustFree = allocatable != nullptr;
+    // A CUDA allocatable result is allocated with the CUDA allocator and must
+    // be released with the CUDA-aware deallocation, not the host free emitted
+    // by the plain hlfir.expr move + destroy path.
+    const Fortran::semantics::Symbol *resultSym = nullptr;
+    cuf::DataAttributeAttr resultCudaAttr;
+    if (mustFree && caller.getInterfaceDetails()) {
+      resultSym = &caller.getResultSymbol();
+      resultCudaAttr = Fortran::lower::translateSymbolCUFDataAttribute(
+          builder.getContext(), *resultSym);
+    }
+    const bool isCudaAllocatableResult = static_cast<bool>(resultCudaAttr);
+    auto genCudaResultCleanUp = [&]() {
+      callContext.stmtCtx.attachCleanup(
+          [&converter = callContext.converter, loc, box = *allocatable,
+           resultSym]() {
+            Fortran::lower::genDeallocateIfAllocated(converter, box, loc,
+                                                     resultSym);
+          });
+    };
     resultEntity = loadTrivialScalar(loc, builder, resultEntity);
     if (resultEntity.isVariable()) {
       // If the result has no finalization, it can be moved into an expression.
+      // Do not let a CUDA result own the buffer (mustFree=false) so its
+      // destruction emits no host free; it is released by genCudaResultCleanUp.
       mlir::Value asExpr = hlfir::AsExprOp::create(
-          builder, loc, resultEntity, builder.createBool(loc, mustFree));
+          builder, loc, resultEntity,
+          builder.createBool(loc, isCudaAllocatableResult ? false : mustFree));
       if (!isElemental) {
         // Insert clean-up for the expression, except for elemental call where
         // the cleaned-up is inserted at the array level.
         callContext.stmtCtx.attachCleanup([bldr = &builder, loc, asExpr]() {
           hlfir::DestroyOp::create(*bldr, loc, asExpr, /*finalize=*/false);
         });
+        if (isCudaAllocatableResult)
+          genCudaResultCleanUp();
       }
       return hlfir::EntityWithAttributes{asExpr};
     }
-    if (allocatable)
-      callContext.stmtCtx.attachCleanup(
-          [bldr = &builder, loc, box = *allocatable]() {
-            fir::factory::genFreememIfAllocated(*bldr, loc, box);
-          });
+    if (allocatable) {
+      if (isCudaAllocatableResult)
+        genCudaResultCleanUp();
+      else
+        callContext.stmtCtx.attachCleanup(
+            [bldr = &builder, loc, box = *allocatable]() {
+              fir::factory::genFreememIfAllocated(*bldr, loc, box);
+            });
+    }
     return hlfir::EntityWithAttributes{resultEntity};
   }
   // If the result has finalization, it cannot be moved because use of its
diff --git a/flang/test/Lower/CUDA/cuda-managed-func-result.cuf b/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
new file mode 100644
index 0000000000000..e2800e72d2aa5
--- /dev/null
+++ b/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
@@ -0,0 +1,30 @@
+! RUN: bbc -emit-hlfir -fcuda %s -o - | FileCheck %s
+
+! An allocatable function result with a CUDA data attribute is allocated with
+! the CUDA allocator. Its temporary must be released with the CUDA-aware
+! deallocation and not moved into an hlfir.expr that would free it with the
+! host deallocator.
+
+module m
+contains
+  function fsum() result(res)
+    integer(4), allocatable, managed :: res(:)
+    allocate(res(4))
+    res = 0
+  end function
+end module
+
+subroutine test()
+  use m
+  integer(4), managed :: b(4)
+  b = fsum()
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest()
+! CHECK: %[[RES:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<?xi32>>> {bindc_name = ".result"}
+! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[RES]] {uniq_name = ".tmp.func_result"}
+! CHECK: fir.call @_QMmPfsum()
+! CHECK: %[[FALSE:.*]] = arith.constant false
+! CHECK: hlfir.as_expr %{{.*}} move %[[FALSE]]
+! CHECK: fir.if
+! CHECK: cuf.deallocate %[[DECL]]#{{[0-9]}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>{{.*}}<managed>

>From a9f9f9c78b7eb8c8f2f437c88bca0bcd93632126 Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Wed, 15 Jul 2026 14:48:07 -0700
Subject: [PATCH 2/4] change test

---
 flang/test/Lower/CUDA/cuda-managed-func-result.cuf | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/test/Lower/CUDA/cuda-managed-func-result.cuf b/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
index e2800e72d2aa5..5697aa6ec4af9 100644
--- a/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
@@ -27,4 +27,4 @@ end subroutine
 ! CHECK: %[[FALSE:.*]] = arith.constant false
 ! CHECK: hlfir.as_expr %{{.*}} move %[[FALSE]]
 ! CHECK: fir.if
-! CHECK: cuf.deallocate %[[DECL]]#{{[0-9]}} : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>{{.*}}<managed>
+! CHECK: cuf.deallocate %[[RES]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>{{.*}}<managed>

>From da1cb7cad1b384455a8d7f13869c1edde0b1671a Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Wed, 15 Jul 2026 20:43:58 -0700
Subject: [PATCH 3/4] format

---
 flang/lib/Lower/ConvertCall.cpp | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index c6fe927bf0701..3d683adaf4b9f 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -12,8 +12,8 @@
 
 #include "flang/Lower/ConvertCall.h"
 #include "flang/Lower/Allocatable.h"
-#include "flang/Lower/ConvertExprToHLFIR.h"
 #include "flang/Lower/CUDA.h"
+#include "flang/Lower/ConvertExprToHLFIR.h"
 #include "flang/Lower/ConvertProcedureDesignator.h"
 #include "flang/Lower/ConvertVariable.h"
 #include "flang/Lower/CustomIntrinsicCall.h"
@@ -1985,12 +1985,11 @@ genUserCall(Fortran::lower::PreparedActualArguments &loweredActuals,
     }
     const bool isCudaAllocatableResult = static_cast<bool>(resultCudaAttr);
     auto genCudaResultCleanUp = [&]() {
-      callContext.stmtCtx.attachCleanup(
-          [&converter = callContext.converter, loc, box = *allocatable,
-           resultSym]() {
-            Fortran::lower::genDeallocateIfAllocated(converter, box, loc,
-                                                     resultSym);
-          });
+      callContext.stmtCtx.attachCleanup([&converter = callContext.converter,
+                                         loc, box = *allocatable, resultSym]() {
+        Fortran::lower::genDeallocateIfAllocated(converter, box, loc,
+                                                 resultSym);
+      });
     };
     resultEntity = loadTrivialScalar(loc, builder, resultEntity);
     if (resultEntity.isVariable()) {

>From 56317016df641bcf51c77521d36ac7bce5b503cd Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Thu, 16 Jul 2026 09:14:31 -0700
Subject: [PATCH 4/4] Keep managed allocatable result as a variable instead of
 moving it

---
 flang/lib/Lower/ConvertCall.cpp               | 34 +++++++------------
 .../Lower/CUDA/cuda-managed-func-result.cuf   | 10 +++---
 2 files changed, 17 insertions(+), 27 deletions(-)

diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 3d683adaf4b9f..19678e429249b 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -1973,9 +1973,10 @@ genUserCall(Fortran::lower::PreparedActualArguments &loweredActuals,
     // Allocatable result must be freed, other results are stack allocated.
     const auto *allocatable = result.getBoxOf<fir::MutableBoxValue>();
     const bool mustFree = allocatable != nullptr;
-    // A CUDA allocatable result is allocated with the CUDA allocator and must
-    // be released with the CUDA-aware deallocation, not the host free emitted
-    // by the plain hlfir.expr move + destroy path.
+    // A CUDA allocatable result lives in device/managed/unified memory: it was
+    // allocated with the CUDA allocator and cannot be moved into an hlfir.expr
+    // whose buffer would be released with the host deallocator. Keep it as a
+    // variable and release it with the CUDA-aware deallocation.
     const Fortran::semantics::Symbol *resultSym = nullptr;
     cuf::DataAttributeAttr resultCudaAttr;
     if (mustFree && caller.getInterfaceDetails()) {
@@ -1983,42 +1984,33 @@ genUserCall(Fortran::lower::PreparedActualArguments &loweredActuals,
       resultCudaAttr = Fortran::lower::translateSymbolCUFDataAttribute(
           builder.getContext(), *resultSym);
     }
-    const bool isCudaAllocatableResult = static_cast<bool>(resultCudaAttr);
-    auto genCudaResultCleanUp = [&]() {
+    if (resultCudaAttr) {
       callContext.stmtCtx.attachCleanup([&converter = callContext.converter,
                                          loc, box = *allocatable, resultSym]() {
         Fortran::lower::genDeallocateIfAllocated(converter, box, loc,
                                                  resultSym);
       });
-    };
+      return hlfir::EntityWithAttributes{resultEntity};
+    }
     resultEntity = loadTrivialScalar(loc, builder, resultEntity);
     if (resultEntity.isVariable()) {
       // If the result has no finalization, it can be moved into an expression.
-      // Do not let a CUDA result own the buffer (mustFree=false) so its
-      // destruction emits no host free; it is released by genCudaResultCleanUp.
       mlir::Value asExpr = hlfir::AsExprOp::create(
-          builder, loc, resultEntity,
-          builder.createBool(loc, isCudaAllocatableResult ? false : mustFree));
+          builder, loc, resultEntity, builder.createBool(loc, mustFree));
       if (!isElemental) {
         // Insert clean-up for the expression, except for elemental call where
         // the cleaned-up is inserted at the array level.
         callContext.stmtCtx.attachCleanup([bldr = &builder, loc, asExpr]() {
           hlfir::DestroyOp::create(*bldr, loc, asExpr, /*finalize=*/false);
         });
-        if (isCudaAllocatableResult)
-          genCudaResultCleanUp();
       }
       return hlfir::EntityWithAttributes{asExpr};
     }
-    if (allocatable) {
-      if (isCudaAllocatableResult)
-        genCudaResultCleanUp();
-      else
-        callContext.stmtCtx.attachCleanup(
-            [bldr = &builder, loc, box = *allocatable]() {
-              fir::factory::genFreememIfAllocated(*bldr, loc, box);
-            });
-    }
+    if (allocatable)
+      callContext.stmtCtx.attachCleanup(
+          [bldr = &builder, loc, box = *allocatable]() {
+            fir::factory::genFreememIfAllocated(*bldr, loc, box);
+          });
     return hlfir::EntityWithAttributes{resultEntity};
   }
   // If the result has finalization, it cannot be moved because use of its
diff --git a/flang/test/Lower/CUDA/cuda-managed-func-result.cuf b/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
index 5697aa6ec4af9..3bbb5db60a742 100644
--- a/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
+++ b/flang/test/Lower/CUDA/cuda-managed-func-result.cuf
@@ -1,9 +1,9 @@
 ! RUN: bbc -emit-hlfir -fcuda %s -o - | FileCheck %s
 
 ! An allocatable function result with a CUDA data attribute is allocated with
-! the CUDA allocator. Its temporary must be released with the CUDA-aware
-! deallocation and not moved into an hlfir.expr that would free it with the
-! host deallocator.
+! the CUDA allocator. It must be kept as a variable and released with the
+! CUDA-aware deallocation, not moved into an hlfir.expr that would free it with
+! the host deallocator.
 
 module m
 contains
@@ -22,9 +22,7 @@ end subroutine
 
 ! CHECK-LABEL: func.func @_QPtest()
 ! CHECK: %[[RES:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<?xi32>>> {bindc_name = ".result"}
-! CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[RES]] {uniq_name = ".tmp.func_result"}
+! CHECK: hlfir.declare %[[RES]] {uniq_name = ".tmp.func_result"}
 ! CHECK: fir.call @_QMmPfsum()
-! CHECK: %[[FALSE:.*]] = arith.constant false
-! CHECK: hlfir.as_expr %{{.*}} move %[[FALSE]]
 ! CHECK: fir.if
 ! CHECK: cuf.deallocate %[[RES]] : !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>>{{.*}}<managed>



More information about the flang-commits mailing list