mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-27 01:06:06 +00:00

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
248 lines
9.7 KiB
C++
248 lines
9.7 KiB
C++
// Verifies that parameters are copied with move constructors
|
|
// Verifies that parameter copies are destroyed
|
|
// Vefifies that parameter copies are used in the body of the coroutine
|
|
// Verifies that parameter copies are used to construct the promise type, if that type has a matching constructor
|
|
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes -fexceptions | FileCheck %s
|
|
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-pc-win32 -emit-llvm -o - %s -disable-llvm-passes -fexceptions | FileCheck %s --check-prefix=MSABI
|
|
|
|
namespace std {
|
|
template <typename... T> struct coroutine_traits;
|
|
|
|
template <class Promise = void> struct coroutine_handle {
|
|
coroutine_handle() = default;
|
|
static coroutine_handle from_address(void *) noexcept;
|
|
};
|
|
template <> struct coroutine_handle<void> {
|
|
static coroutine_handle from_address(void *) noexcept;
|
|
coroutine_handle() = default;
|
|
template <class PromiseType>
|
|
coroutine_handle(coroutine_handle<PromiseType>) noexcept;
|
|
};
|
|
} // namespace std
|
|
|
|
struct suspend_always {
|
|
bool await_ready() noexcept;
|
|
void await_suspend(std::coroutine_handle<>) noexcept;
|
|
void await_resume() noexcept;
|
|
};
|
|
|
|
template <typename... Args> struct std::coroutine_traits<void, Args...> {
|
|
struct promise_type {
|
|
void get_return_object() noexcept;
|
|
suspend_always initial_suspend() noexcept;
|
|
suspend_always final_suspend() noexcept;
|
|
void return_void() noexcept;
|
|
promise_type();
|
|
~promise_type() noexcept;
|
|
void unhandled_exception() noexcept;
|
|
};
|
|
};
|
|
|
|
// TODO: Not supported yet
|
|
struct CopyOnly {
|
|
int val;
|
|
CopyOnly(const CopyOnly&) noexcept;
|
|
CopyOnly(CopyOnly&&) = delete;
|
|
~CopyOnly();
|
|
};
|
|
|
|
struct MoveOnly {
|
|
int val;
|
|
MoveOnly(const MoveOnly&) = delete;
|
|
MoveOnly(MoveOnly&&) noexcept;
|
|
~MoveOnly();
|
|
};
|
|
|
|
struct MoveAndCopy {
|
|
int val;
|
|
MoveAndCopy(const MoveAndCopy&)noexcept;
|
|
MoveAndCopy(MoveAndCopy&&) noexcept;
|
|
~MoveAndCopy();
|
|
};
|
|
|
|
struct [[clang::trivial_abi]] TrivialABI {
|
|
int val;
|
|
TrivialABI(TrivialABI&&) noexcept;
|
|
~TrivialABI();
|
|
};
|
|
|
|
void consume(int,int,int,int) noexcept;
|
|
|
|
// TODO: Add support for CopyOnly params
|
|
// CHECK: define{{.*}} void @_Z1fi8MoveOnly11MoveAndCopy10TrivialABI(i32 noundef %val, ptr noundef %[[MoParam:.+]], ptr noundef %[[McParam:.+]], i32 %[[TrivialParam:.+]]) #0 personality ptr @__gxx_personality_v0
|
|
void f(int val, MoveOnly moParam, MoveAndCopy mcParam, TrivialABI trivialParam) {
|
|
// CHECK: %[[TrivialAlloca:.+]] = alloca %struct.TrivialABI,
|
|
// CHECK-SAME: !coro.outside.frame
|
|
// CHECK: %[[MoCopy:.+]] = alloca %struct.MoveOnly,
|
|
// CHECK: %[[McCopy:.+]] = alloca %struct.MoveAndCopy,
|
|
// CHECK: %[[TrivialCopy:.+]] = alloca %struct.TrivialABI,
|
|
// CHECK: store i32 %val, ptr %[[ValAddr:.+]]
|
|
|
|
// CHECK: call ptr @llvm.coro.begin(
|
|
// CHECK: call void @_ZN8MoveOnlyC1EOS_(ptr {{[^,]*}} %[[MoCopy]], ptr noundef nonnull align 4 dereferenceable(4) %[[MoParam]])
|
|
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
|
|
// CHECK-NEXT: call void @_ZN11MoveAndCopyC1EOS_(ptr {{[^,]*}} %[[McCopy]], ptr noundef nonnull align 4 dereferenceable(4) %[[McParam]]) #
|
|
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
|
|
// CHECK-NEXT: call void @_ZN10TrivialABIC1EOS_(ptr {{[^,]*}} %[[TrivialCopy]], ptr {{[^,]*}} %[[TrivialAlloca]])
|
|
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
|
|
// CHECK-NEXT: invoke void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopy10TrivialABIEE12promise_typeC1Ev(
|
|
|
|
// CHECK: call void @_ZN14suspend_always12await_resumeEv(
|
|
// CHECK: %[[IntParam:.+]] = load i32, ptr %{{.*}}
|
|
// CHECK: %[[MoGep:.+]] = getelementptr inbounds nuw %struct.MoveOnly, ptr %[[MoCopy]], i32 0, i32 0
|
|
// CHECK: %[[MoVal:.+]] = load i32, ptr %[[MoGep]]
|
|
// CHECK: %[[McGep:.+]] = getelementptr inbounds nuw %struct.MoveAndCopy, ptr %[[McCopy]], i32 0, i32 0
|
|
// CHECK: %[[McVal:.+]] = load i32, ptr %[[McGep]]
|
|
// CHECK: %[[TrivialGep:.+]] = getelementptr inbounds nuw %struct.TrivialABI, ptr %[[TrivialCopy]], i32 0, i32 0
|
|
// CHECK: %[[TrivialVal:.+]] = load i32, ptr %[[TrivialGep]]
|
|
// CHECK: call void @_Z7consumeiiii(i32 noundef %[[IntParam]], i32 noundef %[[MoVal]], i32 noundef %[[McVal]], i32 noundef %[[TrivialVal]])
|
|
|
|
consume(val, moParam.val, mcParam.val, trivialParam.val);
|
|
co_return;
|
|
|
|
// Skip to final suspend:
|
|
// CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopy10TrivialABIEE12promise_type13final_suspendEv(
|
|
// CHECK: call void @_ZN14suspend_always12await_resumeEv(
|
|
|
|
// Destroy promise, then parameter copies:
|
|
// CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopy10TrivialABIEE12promise_typeD1Ev(ptr {{[^,]*}} %__promise)
|
|
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
|
|
// CHECK-NEXT: call void @_ZN10TrivialABID1Ev(ptr {{[^,]*}} %[[TrivialCopy]])
|
|
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
|
|
// CHECK-NEXT: call void @_ZN11MoveAndCopyD1Ev(ptr {{[^,]*}} %[[McCopy]])
|
|
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
|
|
// CHECK-NEXT: call void @_ZN8MoveOnlyD1Ev(ptr {{[^,]*}} %[[MoCopy]]
|
|
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
|
|
// CHECK-NEXT: call void @llvm.lifetime.end.p0(
|
|
// CHECK-NEXT: call ptr @llvm.coro.free(
|
|
|
|
// The original trivial_abi parameter is destroyed when returning from the ramp.
|
|
// CHECK: call i1 @llvm.coro.end
|
|
// CHECK: call void @_ZN10TrivialABID1Ev(ptr {{[^,]*}} %[[TrivialAlloca]])
|
|
}
|
|
|
|
// CHECK-LABEL: void @_Z16dependent_paramsI1A1BEvT_T0_S3_(ptr noundef %x, ptr noundef %0, ptr noundef %y)
|
|
template <typename T, typename U>
|
|
void dependent_params(T x, U, U y) {
|
|
// CHECK: %[[x_copy:.+]] = alloca %struct.A,
|
|
// CHECK-NEXT: %[[unnamed_copy:.+]] = alloca %struct.B
|
|
// CHECK-NEXT: %[[y_copy:.+]] = alloca %struct.B
|
|
|
|
// CHECK: call ptr @llvm.coro.begin
|
|
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
|
|
// CHECK-NEXT: call void @_ZN1AC1EOS_(ptr {{[^,]*}} %[[x_copy]], ptr noundef nonnull align 4 dereferenceable(512) %x)
|
|
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
|
|
// CHECK-NEXT: call void @_ZN1BC1EOS_(ptr {{[^,]*}} %[[unnamed_copy]], ptr noundef nonnull align 4 dereferenceable(512) %0)
|
|
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
|
|
// CHECK-NEXT: call void @_ZN1BC1EOS_(ptr {{[^,]*}} %[[y_copy]], ptr noundef nonnull align 4 dereferenceable(512) %y)
|
|
// CHECK-NEXT: call void @llvm.lifetime.start.p0(
|
|
// CHECK-NEXT: invoke void @_ZNSt16coroutine_traitsIJv1A1BS1_EE12promise_typeC1Ev(
|
|
|
|
co_return;
|
|
}
|
|
|
|
struct A {
|
|
int WontFitIntoRegisterForSure[128];
|
|
A();
|
|
A(A&&) noexcept;
|
|
~A();
|
|
};
|
|
|
|
struct B {
|
|
int WontFitIntoRegisterForSure[128];
|
|
B();
|
|
B(B&&) noexcept;
|
|
~B();
|
|
};
|
|
|
|
void call_dependent_params() {
|
|
dependent_params(A{}, B{}, B{});
|
|
}
|
|
|
|
// Test that, when the promise type has a constructor whose signature matches
|
|
// that of the coroutine function, that constructor is used.
|
|
|
|
struct promise_matching_constructor {};
|
|
|
|
template <>
|
|
struct std::coroutine_traits<void, promise_matching_constructor, int, float, double> {
|
|
struct promise_type {
|
|
promise_type(promise_matching_constructor, int, float, double) {}
|
|
promise_type() = delete;
|
|
void get_return_object() {}
|
|
suspend_always initial_suspend() { return {}; }
|
|
suspend_always final_suspend() noexcept { return {}; }
|
|
void return_void() {}
|
|
void unhandled_exception() {}
|
|
};
|
|
};
|
|
|
|
// CHECK-LABEL: void @_Z38coroutine_matching_promise_constructor28promise_matching_constructorifd(i32 noundef %0, float noundef %1, double noundef %2)
|
|
void coroutine_matching_promise_constructor(promise_matching_constructor, int, float, double) {
|
|
// CHECK: %[[INT:.+]] = load i32, ptr %5, align 4
|
|
// CHECK: %[[FLOAT:.+]] = load float, ptr %6, align 4
|
|
// CHECK: %[[DOUBLE:.+]] = load double, ptr %7, align 8
|
|
// CHECK: invoke void @_ZNSt16coroutine_traitsIJv28promise_matching_constructorifdEE12promise_typeC1ES0_ifd(ptr {{[^,]*}} %__promise, i32 noundef %[[INT]], float noundef %[[FLOAT]], double noundef %[[DOUBLE]])
|
|
co_return;
|
|
}
|
|
|
|
struct some_class;
|
|
|
|
struct method {};
|
|
|
|
template <typename... Args> struct std::coroutine_traits<method, Args...> {
|
|
struct promise_type {
|
|
promise_type(some_class&, float);
|
|
method get_return_object();
|
|
suspend_always initial_suspend();
|
|
suspend_always final_suspend() noexcept;
|
|
void return_void();
|
|
void unhandled_exception();
|
|
};
|
|
};
|
|
|
|
struct some_class {
|
|
method good_coroutine_calls_custom_constructor(float);
|
|
};
|
|
|
|
// CHECK-LABEL: define{{.*}} void @_ZN10some_class39good_coroutine_calls_custom_constructorEf(ptr
|
|
method some_class::good_coroutine_calls_custom_constructor(float) {
|
|
// CHECK: invoke void @_ZNSt16coroutine_traitsIJ6methodR10some_classfEE12promise_typeC1ES2_f(ptr {{[^,]*}} %__promise, ptr noundef nonnull align 1 dereferenceable(1) %{{.+}}, float
|
|
co_return;
|
|
}
|
|
|
|
|
|
struct MSParm {
|
|
int val;
|
|
~MSParm();
|
|
};
|
|
|
|
void consume(int) noexcept;
|
|
|
|
// Similarly to the [[clang::trivial_abi]] parameters, with the MSVC ABI
|
|
// parameters are also destroyed by the callee, and on x86-64 such parameters
|
|
// may get passed in registers. In that case it's again important that the
|
|
// parameter's local alloca does not become part of the coro frame since that
|
|
// may be destroyed before the destructor call.
|
|
void msabi(MSParm p) {
|
|
// MSABI: define{{.*}} void @"?msabi@@YAXUMSParm@@@Z"(i32 %[[Param:.+]])
|
|
|
|
// The parameter's local alloca is marked not part of the frame.
|
|
// MSABI: %[[ParamAlloca:.+]] = alloca %struct.MSParm
|
|
// MSABI-SAME: !coro.outside.frame
|
|
|
|
// MSABI: %[[ParamCopy:.+]] = alloca %struct.MSParm
|
|
|
|
consume(p.val);
|
|
// The parameter's copy is used by the coroutine.
|
|
// MSABI: %[[ValPtr:.+]] = getelementptr inbounds nuw %struct.MSParm, ptr %[[ParamCopy]], i32 0, i32 0
|
|
// MSABI: %[[Val:.+]] = load i32, ptr %[[ValPtr]]
|
|
// MSABI: call void @"?consume@@YAXH@Z"(i32{{.*}} %[[Val]])
|
|
|
|
co_return;
|
|
|
|
// The local alloca is used for the destructor call at the end of the ramp.
|
|
// MSABI: call i1 @llvm.coro.end
|
|
// MSABI: call void @"??1MSParm@@QEAA@XZ"(ptr{{.*}} %[[ParamAlloca]])
|
|
}
|