Skip to content

Commit d0edd93

Browse files
authored
[Coroutines] Mark parameter allocas with coro.outside.frame metadata (#127653)
Parameters to a coroutine get copied (moved) to coroutine-local instances which code inside the coroutine then uses. The original parameters should not be part of the frame. Normally CoroSplit figures that out by itself, but for [[clang::trivial_abi]] parameters which, get destructed at the end of the ramp function, it does not (see bug), causing use-after-free's if the frame is destroyed before the end of the ramp (as happens if it doesn't suspend). Since Clang knows these should never be part of the frame, use metadata to make it so. Fixes #127499
1 parent a8db1fb commit d0edd93

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

clang/lib/CodeGen/CGCoroutine.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,20 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
855855
// Create parameter copies. We do it before creating a promise, since an
856856
// evolution of coroutine TS may allow promise constructor to observe
857857
// parameter copies.
858+
for (const ParmVarDecl *Parm : FnArgs) {
859+
// If the original param is in an alloca, exclude it from the coroutine
860+
// frame. The parameter copy will be part of the frame, but the original
861+
// parameter memory should remain on the stack. This is necessary to
862+
// ensure that parameters destroyed in callees, as with `trivial_abi` or
863+
// in the MSVC C++ ABI, are appropriately destroyed after setting up the
864+
// coroutine.
865+
Address ParmAddr = GetAddrOfLocalVar(Parm);
866+
if (auto *ParmAlloca =
867+
dyn_cast<llvm::AllocaInst>(ParmAddr.getBasePointer())) {
868+
ParmAlloca->setMetadata(llvm::LLVMContext::MD_coro_outside_frame,
869+
llvm::MDNode::get(CGM.getLLVMContext(), {}));
870+
}
871+
}
858872
for (auto *PM : S.getParamMoves()) {
859873
EmitStmt(PM);
860874
ParamReplacer.addCopy(cast<DeclStmt>(PM));

clang/test/CodeGenCoroutines/coro-params.cpp

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Vefifies that parameter copies are used in the body of the coroutine
44
// Verifies that parameter copies are used to construct the promise type, if that type has a matching constructor
55
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes -fexceptions | FileCheck %s
6+
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-pc-win32 -emit-llvm -o - %s -disable-llvm-passes -fexceptions | FileCheck %s --check-prefix=MSABI
67

78
namespace std {
89
template <typename... T> struct coroutine_traits;
@@ -59,46 +60,65 @@ struct MoveAndCopy {
5960
~MoveAndCopy();
6061
};
6162

62-
void consume(int,int,int) noexcept;
63+
struct [[clang::trivial_abi]] TrivialABI {
64+
int val;
65+
TrivialABI(TrivialABI&&) noexcept;
66+
~TrivialABI();
67+
};
68+
69+
void consume(int,int,int,int) noexcept;
6370

6471
// TODO: Add support for CopyOnly params
65-
// CHECK: define{{.*}} void @_Z1fi8MoveOnly11MoveAndCopy(i32 noundef %val, ptr noundef %[[MoParam:.+]], ptr noundef %[[McParam:.+]]) #0 personality ptr @__gxx_personality_v0
66-
void f(int val, MoveOnly moParam, MoveAndCopy mcParam) {
72+
// CHECK: define{{.*}} void @_Z1fi8MoveOnly11MoveAndCopy10TrivialABI(i32 noundef %val, ptr noundef %[[MoParam:.+]], ptr noundef %[[McParam:.+]], i32 %[[TrivialParam:.+]]) #0 personality ptr @__gxx_personality_v0
73+
void f(int val, MoveOnly moParam, MoveAndCopy mcParam, TrivialABI trivialParam) {
74+
// CHECK: %[[TrivialAlloca:.+]] = alloca %struct.TrivialABI,
75+
// CHECK-SAME: !coro.outside.frame
6776
// CHECK: %[[MoCopy:.+]] = alloca %struct.MoveOnly,
6877
// CHECK: %[[McCopy:.+]] = alloca %struct.MoveAndCopy,
78+
// CHECK: %[[TrivialCopy:.+]] = alloca %struct.TrivialABI,
6979
// CHECK: store i32 %val, ptr %[[ValAddr:.+]]
7080

7181
// CHECK: call ptr @llvm.coro.begin(
7282
// CHECK: call void @_ZN8MoveOnlyC1EOS_(ptr {{[^,]*}} %[[MoCopy]], ptr noundef nonnull align 4 dereferenceable(4) %[[MoParam]])
7383
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
7484
// CHECK-NEXT: call void @_ZN11MoveAndCopyC1EOS_(ptr {{[^,]*}} %[[McCopy]], ptr noundef nonnull align 4 dereferenceable(4) %[[McParam]]) #
7585
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
76-
// CHECK-NEXT: invoke void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeC1Ev(
86+
// CHECK-NEXT: call void @_ZN10TrivialABIC1EOS_(ptr {{[^,]*}} %[[TrivialCopy]], ptr {{[^,]*}} %[[TrivialAlloca]])
87+
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
88+
// CHECK-NEXT: invoke void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopy10TrivialABIEE12promise_typeC1Ev(
7789

7890
// CHECK: call void @_ZN14suspend_always12await_resumeEv(
7991
// CHECK: %[[IntParam:.+]] = load i32, ptr %{{.*}}
8092
// CHECK: %[[MoGep:.+]] = getelementptr inbounds nuw %struct.MoveOnly, ptr %[[MoCopy]], i32 0, i32 0
8193
// CHECK: %[[MoVal:.+]] = load i32, ptr %[[MoGep]]
82-
// CHECK: %[[McGep:.+]] = getelementptr inbounds nuw %struct.MoveAndCopy, ptr %[[McCopy]], i32 0, i32 0
94+
// CHECK: %[[McGep:.+]] = getelementptr inbounds nuw %struct.MoveAndCopy, ptr %[[McCopy]], i32 0, i32 0
8395
// CHECK: %[[McVal:.+]] = load i32, ptr %[[McGep]]
84-
// CHECK: call void @_Z7consumeiii(i32 noundef %[[IntParam]], i32 noundef %[[MoVal]], i32 noundef %[[McVal]])
96+
// CHECK: %[[TrivialGep:.+]] = getelementptr inbounds nuw %struct.TrivialABI, ptr %[[TrivialCopy]], i32 0, i32 0
97+
// CHECK: %[[TrivialVal:.+]] = load i32, ptr %[[TrivialGep]]
98+
// CHECK: call void @_Z7consumeiiii(i32 noundef %[[IntParam]], i32 noundef %[[MoVal]], i32 noundef %[[McVal]], i32 noundef %[[TrivialVal]])
8599

86-
consume(val, moParam.val, mcParam.val);
100+
consume(val, moParam.val, mcParam.val, trivialParam.val);
87101
co_return;
88102

89103
// Skip to final suspend:
90-
// CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_type13final_suspendEv(
104+
// CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopy10TrivialABIEE12promise_type13final_suspendEv(
91105
// CHECK: call void @_ZN14suspend_always12await_resumeEv(
92106

93107
// Destroy promise, then parameter copies:
94-
// CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeD1Ev(ptr {{[^,]*}} %__promise)
108+
// CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopy10TrivialABIEE12promise_typeD1Ev(ptr {{[^,]*}} %__promise)
109+
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
110+
// CHECK-NEXT: call void @_ZN10TrivialABID1Ev(ptr {{[^,]*}} %[[TrivialCopy]])
95111
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
96112
// CHECK-NEXT: call void @_ZN11MoveAndCopyD1Ev(ptr {{[^,]*}} %[[McCopy]])
97113
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
98114
// CHECK-NEXT: call void @_ZN8MoveOnlyD1Ev(ptr {{[^,]*}} %[[MoCopy]]
99115
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
100116
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
101117
// CHECK-NEXT: call ptr @llvm.coro.free(
118+
119+
// The original trivial_abi parameter is destroyed when returning from the ramp.
120+
// CHECK: call i1 @llvm.coro.end
121+
// CHECK: call void @_ZN10TrivialABID1Ev(ptr {{[^,]*}} %[[TrivialAlloca]])
102122
}
103123

104124
// CHECK-LABEL: void @_Z16dependent_paramsI1A1BEvT_T0_S3_(ptr noundef %x, ptr noundef %0, ptr noundef %y)
@@ -190,3 +210,38 @@ method some_class::good_coroutine_calls_custom_constructor(float) {
190210
// CHECK: invoke void @_ZNSt16coroutine_traitsIJ6methodR10some_classfEE12promise_typeC1ES2_f(ptr {{[^,]*}} %__promise, ptr noundef nonnull align 1 dereferenceable(1) %{{.+}}, float
191211
co_return;
192212
}
213+
214+
215+
struct MSParm {
216+
int val;
217+
~MSParm();
218+
};
219+
220+
void consume(int) noexcept;
221+
222+
// Similarly to the [[clang::trivial_abi]] parameters, with the MSVC ABI
223+
// parameters are also destroyed by the callee, and on x86-64 such parameters
224+
// may get passed in registers. In that case it's again important that the
225+
// parameter's local alloca does not become part of the coro frame since that
226+
// may be destroyed before the destructor call.
227+
void msabi(MSParm p) {
228+
// MSABI: define{{.*}} void @"?msabi@@YAXUMSParm@@@Z"(i32 %[[Param:.+]])
229+
230+
// The parameter's local alloca is marked not part of the frame.
231+
// MSABI: %[[ParamAlloca:.+]] = alloca %struct.MSParm
232+
// MSABI-SAME: !coro.outside.frame
233+
234+
// MSABI: %[[ParamCopy:.+]] = alloca %struct.MSParm
235+
236+
consume(p.val);
237+
// The parameter's copy is used by the coroutine.
238+
// MSABI: %[[ValPtr:.+]] = getelementptr inbounds nuw %struct.MSParm, ptr %[[ParamCopy]], i32 0, i32 0
239+
// MSABI: %[[Val:.+]] = load i32, ptr %[[ValPtr]]
240+
// MSABI: call void @"?consume@@YAXH@Z"(i32{{.*}} %[[Val]])
241+
242+
co_return;
243+
244+
// The local alloca is used for the destructor call at the end of the ramp.
245+
// MSABI: call i1 @llvm.coro.end
246+
// MSABI: call void @"??1MSParm@@QEAA@XZ"(ptr{{.*}} %[[ParamAlloca]])
247+
}

0 commit comments

Comments
 (0)