Skip to content

Commit 43c635e

Browse files
authored
[core] Fix memory leak when device kernels return vectors. (#2926)
Entry-point kernels expect vector data to be deallocated properly by mechanisms in the kernel launch (such as code in the host-side entry point). However, device kernels were simply leaking the memory. This patch fixes the memory leak for device kernel calls. Remove the aligns so test passes on ARM. Remove references to x86_64 and libstdc++ name mangling. Remove nonnull attributes. Signed-off-by: Eric Schweitz <eschweitz@nvidia.com>
1 parent c521d8d commit 43c635e

5 files changed

Lines changed: 395 additions & 149 deletions

File tree

lib/Frontend/nvqpp/ConvertExpr.cpp

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2282,8 +2282,42 @@ bool QuakeBridgeVisitor::VisitCallExpr(clang::CallExpr *x) {
22822282
convertKernelArgs(builder, loc, 0, args, mlirFuncTy.getInputs());
22832283
auto call = builder.create<func::CallIndirectOp>(loc, funcResults, calleeOp,
22842284
convertedArgs);
2285-
if (call.getNumResults() > 0)
2285+
if (call.getNumResults() > 0) {
2286+
if (call.getNumResults() != 1) {
2287+
reportClangError(x, mangler, "expect exactly one return value");
2288+
return false;
2289+
}
2290+
if (auto vecTy =
2291+
dyn_cast<cudaq::cc::StdvecType>(call.getResult(0).getType())) {
2292+
auto irBuilder = cudaq::IRBuilder::atBlockEnd(module.getBody());
2293+
if (failed(irBuilder.loadIntrinsic(module, "__nvqpp_vectorCopyToStack")))
2294+
module.emitError("failed to load intrinsic");
2295+
auto eleTy = [&]() -> Type {
2296+
auto et = vecTy.getElementType();
2297+
if (et == builder.getI1Type())
2298+
return builder.getI8Type();
2299+
return et;
2300+
}();
2301+
auto data = builder.create<cudaq::cc::StdvecDataOp>(
2302+
loc, cudaq::cc::PointerType::get(eleTy), call.getResult(0));
2303+
auto i64Ty = builder.getI64Type();
2304+
auto len = builder.create<cudaq::cc::StdvecSizeOp>(loc, i64Ty,
2305+
call.getResult(0));
2306+
auto eleSize = builder.create<cudaq::cc::SizeOfOp>(loc, i64Ty, eleTy);
2307+
auto size = builder.create<arith::MulIOp>(loc, len, eleSize);
2308+
auto buffer = builder.create<cudaq::cc::AllocaOp>(loc, eleTy, size);
2309+
auto i8PtrTy = cudaq::cc::PointerType::get(builder.getI8Type());
2310+
auto cbuffer = builder.create<cudaq::cc::CastOp>(loc, i8PtrTy, buffer);
2311+
auto cdata = builder.create<cudaq::cc::CastOp>(loc, i8PtrTy, data);
2312+
builder.create<func::CallOp>(loc, TypeRange{},
2313+
"__nvqpp_vectorCopyToStack",
2314+
ValueRange{cbuffer, cdata, size});
2315+
Value newSpan =
2316+
builder.create<cudaq::cc::StdvecInitOp>(loc, vecTy, buffer, len);
2317+
return pushValue(newSpan);
2318+
}
22862319
return pushValue(call.getResult(0));
2320+
}
22872321
return true;
22882322
}
22892323

lib/Optimizer/Builder/Intrinsics.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ static constexpr IntrinsicCode intrinsicTable[] = {
319319
R"#(
320320
func.func private @__nvqpp_initializer_list_to_vector_bool(!cc.ptr<none>, !cc.ptr<none>, i64) -> ())#"},
321321

322+
// This helper function copies a buffer off the stack to the heap. This is
323+
// required when the data on the stack is about to go out of scope but is
324+
// still live.
322325
{"__nvqpp_vectorCopyCtor", {cudaq::llvmMemCopyIntrinsic, "malloc"}, R"#(
323326
func.func private @__nvqpp_vectorCopyCtor(%arg0: !cc.ptr<i8>, %arg1: i64, %arg2: i64) -> !cc.ptr<i8> {
324327
%size = arith.muli %arg1, %arg2 : i64
@@ -328,6 +331,17 @@ static constexpr IntrinsicCode intrinsicTable[] = {
328331
return %0 : !cc.ptr<i8>
329332
})#"},
330333

334+
// This helper function copies a buffer that is in the heap to a buffer on
335+
// the stack. Both buffers must already exist. This helper matches
336+
// __nvqpp_vectorCopyCtor and eliminates memory leaks.
337+
{"__nvqpp_vectorCopyToStack", {cudaq::llvmMemCopyIntrinsic, "free"}, R"#(
338+
func.func private @__nvqpp_vectorCopyToStack(%to: !cc.ptr<i8>, %from: !cc.ptr<i8>, %size: i64) {
339+
%false = arith.constant false
340+
call @llvm.memcpy.p0i8.p0i8.i64(%to, %from, %size, %false) : (!cc.ptr<i8>, !cc.ptr<i8>, i64, i1) -> ()
341+
call @free(%from) : (!cc.ptr<i8>) -> ()
342+
return
343+
})#"},
344+
331345
// __nvqpp_vector_bool_free_temporary_lists
332346
{cudaq::stdvecBoolFreeTemporaryLists,
333347
{},

test/AST-Quake/call_qpu.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2022 - 2025 NVIDIA Corporation & Affiliates. *
3+
* All rights reserved. *
4+
* *
5+
* This source code and the accompanying materials are made available under *
6+
* the terms of the Apache License 2.0 which accompanies this distribution. *
7+
******************************************************************************/
8+
9+
// RUN: cudaq-quake %cpp_std %s | cudaq-opt -memtoreg=quantum=0 -canonicalize | FileCheck %s
10+
11+
#include <cudaq.h>
12+
13+
std::vector<bool> func_achat(cudaq::qview<> &qv) __qpu__ {
14+
// measure the entire register
15+
return mz(qv);
16+
}
17+
18+
// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_func_achat._Z10func_achatRN5cudaq5qviewILm2EEE(
19+
// CHECK-SAME: %[[VAL_0:.*]]: !quake.veq<?>) -> !cc.stdvec<i1> attributes {"cudaq-kernel", no_this} {
20+
// CHECK: %[[VAL_1:.*]] = arith.constant 1 : i64
21+
// CHECK: %[[VAL_2:.*]] = quake.mz %[[VAL_0]] : (!quake.veq<?>) -> !cc.stdvec<!quake.measure>
22+
// CHECK: %[[VAL_3:.*]] = quake.discriminate %[[VAL_2]] : (!cc.stdvec<!quake.measure>) -> !cc.stdvec<i1>
23+
// CHECK: %[[VAL_4:.*]] = cc.stdvec_data %[[VAL_3]] : (!cc.stdvec<i1>) -> !cc.ptr<i8>
24+
// CHECK: %[[VAL_5:.*]] = cc.stdvec_size %[[VAL_3]] : (!cc.stdvec<i1>) -> i64
25+
// CHECK: %[[VAL_6:.*]] = call @__nvqpp_vectorCopyCtor(%[[VAL_4]], %[[VAL_5]], %[[VAL_1]]) : (!cc.ptr<i8>, i64, i64) -> !cc.ptr<i8>
26+
// CHECK: %[[VAL_7:.*]] = cc.stdvec_init %[[VAL_6]], %[[VAL_5]] : (!cc.ptr<i8>, i64) -> !cc.stdvec<i1>
27+
// CHECK: return %[[VAL_7]] : !cc.stdvec<i1>
28+
// CHECK: }
29+
30+
int func_shiim(cudaq::qvector<> &qv) __qpu__ {
31+
auto vs = qv.slice(1, 3);
32+
auto bs = func_achat(vs);
33+
int i;
34+
for (auto b : bs) {
35+
if (b)
36+
++i;
37+
}
38+
return i;
39+
}
40+
41+
// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_func_shiim._Z10func_shiimRN5cudaq7qvectorILm2EEE(
42+
// CHECK-SAME: %[[VAL_0:.*]]: !quake.veq<?>) -> i32 attributes
43+
// CHECK-DAG: %[[VAL_1:.*]] = arith.constant 1 : i64
44+
// CHECK-DAG: %[[VAL_2:.*]] = arith.constant 0 : i64
45+
// CHECK-DAG: %[[VAL_3:.*]] = arith.constant 1 : i32
46+
// CHECK: %[[VAL_4:.*]] = quake.subveq %[[VAL_0]], 1, 3 : (!quake.veq<?>) -> !quake.veq<3>
47+
// CHECK: %[[VAL_5:.*]] = quake.relax_size %[[VAL_4]] : (!quake.veq<3>) -> !quake.veq<?>
48+
// CHECK: %[[VAL_6:.*]] = call @__nvqpp__mlirgen__function_func_achat._Z10func_achatRN5cudaq5qviewILm2EEE(%[[VAL_5]]) : (!quake.veq<?>) -> !cc.stdvec<i1>
49+
// CHECK: %[[VAL_7:.*]] = cc.stdvec_data %[[VAL_6]] : (!cc.stdvec<i1>) -> !cc.ptr<i8>
50+
// CHECK: %[[VAL_8:.*]] = cc.stdvec_size %[[VAL_6]] : (!cc.stdvec<i1>) -> i64
51+
// CHECK: %[[VAL_9:.*]] = cc.alloca i8{{\[}}%[[VAL_8]] : i64]
52+
// CHECK: %[[VAL_10:.*]] = cc.cast %[[VAL_9]] : (!cc.ptr<!cc.array<i8 x ?>>) -> !cc.ptr<i8>
53+
// CHECK: call @__nvqpp_vectorCopyToStack(%[[VAL_10]], %[[VAL_7]], %[[VAL_8]]) : (!cc.ptr<i8>, !cc.ptr<i8>, i64) -> ()
54+
// CHECK: %[[VAL_11:.*]] = cc.undef i32
55+
// CHECK: %[[VAL_12:.*]] = cc.cast %[[VAL_9]] : (!cc.ptr<!cc.array<i8 x ?>>) -> !cc.ptr<!cc.array<i1 x ?>>
56+
// CHECK: %[[VAL_13:.*]]:2 = cc.loop while ((%[[VAL_14:.*]] = %[[VAL_2]], %[[VAL_15:.*]] = %[[VAL_11]]) -> (i64, i32)) {
57+
// CHECK: %[[VAL_16:.*]] = arith.cmpi slt, %[[VAL_14]], %[[VAL_8]] : i64
58+
// CHECK: cc.condition %[[VAL_16]](%[[VAL_14]], %[[VAL_15]] : i64, i32)
59+
// CHECK: } do {
60+
// CHECK: ^bb0(%[[VAL_17:.*]]: i64, %[[VAL_18:.*]]: i32):
61+
// CHECK: %[[VAL_19:.*]] = cc.compute_ptr %[[VAL_12]]{{\[}}%[[VAL_17]]] : (!cc.ptr<!cc.array<i1 x ?>>, i64) -> !cc.ptr<i1>
62+
// CHECK: %[[VAL_20:.*]] = cc.load %[[VAL_19]] : !cc.ptr<i1>
63+
// CHECK: %[[VAL_21:.*]] = cc.if(%[[VAL_20]]) -> i32 {
64+
// CHECK: %[[VAL_22:.*]] = arith.addi %[[VAL_18]], %[[VAL_3]] : i32
65+
// CHECK: cc.continue %[[VAL_22]] : i32
66+
// CHECK: } else {
67+
// CHECK: cc.continue %[[VAL_18]] : i32
68+
// CHECK: }
69+
// CHECK: cc.continue %[[VAL_17]], %[[VAL_23:.*]] : i64, i32
70+
// CHECK: } step {
71+
// CHECK: ^bb0(%[[VAL_24:.*]]: i64, %[[VAL_25:.*]]: i32):
72+
// CHECK: %[[VAL_26:.*]] = arith.addi %[[VAL_24]], %[[VAL_1]] : i64
73+
// CHECK: cc.continue %[[VAL_26]], %[[VAL_25]] : i64, i32
74+
// CHECK: } {invariant}
75+
// CHECK: return %[[VAL_27:.*]]#1 : i32
76+
// CHECK: }
77+
78+
bool func_shlosh(cudaq::qvector<> &qv) __qpu__ {
79+
auto i = func_shiim(qv);
80+
return i;
81+
}
82+
83+
// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_func_shlosh._Z11func_shloshRN5cudaq7qvectorILm2EEE(
84+
// CHECK-SAME: %[[VAL_0:.*]]: !quake.veq<?>) -> i1 attributes {"cudaq-kernel", no_this} {
85+
// CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32
86+
// CHECK: %[[VAL_2:.*]] = call @__nvqpp__mlirgen__function_func_shiim._Z10func_shiimRN5cudaq7qvectorILm2EEE(%[[VAL_0]]) : (!quake.veq<?>) -> i32
87+
// CHECK: %[[VAL_3:.*]] = arith.cmpi ne, %[[VAL_2]], %[[VAL_1]] : i32
88+
// CHECK: return %[[VAL_3]] : i1
89+
// CHECK: }
90+
91+
void func_arba() __qpu__ {
92+
cudaq::qvector qv(10);
93+
z(qv);
94+
auto b = func_shlosh(qv);
95+
if (b)
96+
h(qv);
97+
x(qv);
98+
h(qv);
99+
}
100+
101+
// CHECK-LABEL: func.func @__nvqpp__mlirgen__function_func_arba._Z9func_arbav() attributes {"cudaq-entrypoint", "cudaq-kernel", no_this} {
102+
// CHECK-DAG: %[[VAL_0:.*]] = arith.constant 10 : i64
103+
// CHECK-DAG: %[[VAL_1:.*]] = arith.constant 1 : i64
104+
// CHECK-DAG: %[[VAL_2:.*]] = arith.constant 0 : i64
105+
// CHECK-DAG: %[[VAL_3:.*]] = quake.alloca !quake.veq<10>
106+
// CHECK: %[[VAL_4:.*]] = quake.relax_size %[[VAL_3]] : (!quake.veq<10>) -> !quake.veq<?>
107+
// CHECK: %[[VAL_5:.*]] = cc.loop while ((%[[VAL_6:.*]] = %[[VAL_2]]) -> (i64)) {
108+
// CHECK: %[[VAL_7:.*]] = arith.cmpi slt, %[[VAL_6]], %[[VAL_0]] : i64
109+
// CHECK: cc.condition %[[VAL_7]](%[[VAL_6]] : i64)
110+
// CHECK: } do {
111+
// CHECK: ^bb0(%[[VAL_8:.*]]: i64):
112+
// CHECK: %[[VAL_9:.*]] = quake.extract_ref %[[VAL_3]]{{\[}}%[[VAL_8]]] : (!quake.veq<10>, i64) -> !quake.ref
113+
// CHECK: quake.z %[[VAL_9]] : (!quake.ref) -> ()
114+
// CHECK: cc.continue %[[VAL_8]] : i64
115+
// CHECK: } step {
116+
// CHECK: ^bb0(%[[VAL_10:.*]]: i64):
117+
// CHECK: %[[VAL_11:.*]] = arith.addi %[[VAL_10]], %[[VAL_1]] : i64
118+
// CHECK: cc.continue %[[VAL_11]] : i64
119+
// CHECK: } {invariant}
120+
// CHECK: %[[VAL_12:.*]] = call @__nvqpp__mlirgen__function_func_shlosh._Z11func_shloshRN5cudaq7qvectorILm2EEE(%[[VAL_4]]) : (!quake.veq<?>) -> i1
121+
// CHECK: cc.if(%[[VAL_12]]) {
122+
// CHECK: %[[VAL_13:.*]] = cc.loop while ((%[[VAL_14:.*]] = %[[VAL_2]]) -> (i64)) {
123+
// CHECK: %[[VAL_15:.*]] = arith.cmpi slt, %[[VAL_14]], %[[VAL_0]] : i64
124+
// CHECK: cc.condition %[[VAL_15]](%[[VAL_14]] : i64)
125+
// CHECK: } do {
126+
// CHECK: ^bb0(%[[VAL_16:.*]]: i64):
127+
// CHECK: %[[VAL_17:.*]] = quake.extract_ref %[[VAL_3]]{{\[}}%[[VAL_16]]] : (!quake.veq<10>, i64) -> !quake.ref
128+
// CHECK: quake.h %[[VAL_17]] : (!quake.ref) -> ()
129+
// CHECK: cc.continue %[[VAL_16]] : i64
130+
// CHECK: } step {
131+
// CHECK: ^bb0(%[[VAL_18:.*]]: i64):
132+
// CHECK: %[[VAL_19:.*]] = arith.addi %[[VAL_18]], %[[VAL_1]] : i64
133+
// CHECK: cc.continue %[[VAL_19]] : i64
134+
// CHECK: } {invariant}
135+
// CHECK: }
136+
// CHECK: %[[VAL_20:.*]] = cc.loop while ((%[[VAL_21:.*]] = %[[VAL_2]]) -> (i64)) {
137+
// CHECK: %[[VAL_22:.*]] = arith.cmpi slt, %[[VAL_21]], %[[VAL_0]] : i64
138+
// CHECK: cc.condition %[[VAL_22]](%[[VAL_21]] : i64)
139+
// CHECK: } do {
140+
// CHECK: ^bb0(%[[VAL_23:.*]]: i64):
141+
// CHECK: %[[VAL_24:.*]] = quake.extract_ref %[[VAL_3]]{{\[}}%[[VAL_23]]] : (!quake.veq<10>, i64) -> !quake.ref
142+
// CHECK: quake.x %[[VAL_24]] : (!quake.ref) -> ()
143+
// CHECK: cc.continue %[[VAL_23]] : i64
144+
// CHECK: } step {
145+
// CHECK: ^bb0(%[[VAL_25:.*]]: i64):
146+
// CHECK: %[[VAL_26:.*]] = arith.addi %[[VAL_25]], %[[VAL_1]] : i64
147+
// CHECK: cc.continue %[[VAL_26]] : i64
148+
// CHECK: } {invariant}
149+
// CHECK: %[[VAL_27:.*]] = cc.loop while ((%[[VAL_28:.*]] = %[[VAL_2]]) -> (i64)) {
150+
// CHECK: %[[VAL_29:.*]] = arith.cmpi slt, %[[VAL_28]], %[[VAL_0]] : i64
151+
// CHECK: cc.condition %[[VAL_29]](%[[VAL_28]] : i64)
152+
// CHECK: } do {
153+
// CHECK: ^bb0(%[[VAL_30:.*]]: i64):
154+
// CHECK: %[[VAL_31:.*]] = quake.extract_ref %[[VAL_3]]{{\[}}%[[VAL_30]]] : (!quake.veq<10>, i64) -> !quake.ref
155+
// CHECK: quake.h %[[VAL_31]] : (!quake.ref) -> ()
156+
// CHECK: cc.continue %[[VAL_30]] : i64
157+
// CHECK: } step {
158+
// CHECK: ^bb0(%[[VAL_32:.*]]: i64):
159+
// CHECK: %[[VAL_33:.*]] = arith.addi %[[VAL_32]], %[[VAL_1]] : i64
160+
// CHECK: cc.continue %[[VAL_33]] : i64
161+
// CHECK: } {invariant}
162+
// CHECK: return
163+
// CHECK: }

0 commit comments

Comments
 (0)