[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