[Coroutines] Use allocator overload when available
Summary:
Depends on https://reviews.llvm.org/D42605.
An implementation of the behavior described in `[dcl.fct.def.coroutine]/7`:
when a promise type overloads `operator new` using a "placement new"
that takes the same argument types as the coroutine function, that
overload is used when allocating the coroutine frame.
Simply passing references to the coroutine function parameters directly
to `operator new` results in invariant violations in LLVM's coroutine
splitting pass, so this implementation modifies Clang codegen to
produce allocator-specific alloc/store/loads for each parameter being
forwarded to the allocator.
Test Plan: `check-clang`
Reviewers: rsmith, GorNishanov, eric_niebler
Reviewed By: GorNishanov
Subscribers: lewissbaker, EricWF, cfe-commits
Differential Revision: https://reviews.llvm.org/D42606
llvm-svn: 325291
diff --git a/clang/test/CodeGenCoroutines/coro-alloc.cpp b/clang/test/CodeGenCoroutines/coro-alloc.cpp
index e66561a..1cf9b8c 100644
--- a/clang/test/CodeGenCoroutines/coro-alloc.cpp
+++ b/clang/test/CodeGenCoroutines/coro-alloc.cpp
@@ -106,6 +106,34 @@
co_return;
}
+struct promise_matching_placement_new_tag {};
+
+template<>
+struct std::experimental::coroutine_traits<void, promise_matching_placement_new_tag, int, float, double> {
+ struct promise_type {
+ void *operator new(unsigned long, promise_matching_placement_new_tag,
+ int, float, double);
+ void get_return_object() {}
+ suspend_always initial_suspend() { return {}; }
+ suspend_always final_suspend() { return {}; }
+ void return_void() {}
+ };
+};
+
+// CHECK-LABEL: f1a(
+extern "C" void f1a(promise_matching_placement_new_tag, int x, float y , double z) {
+ // CHECK: store i32 %x, i32* %x.addr, align 4
+ // CHECK: store float %y, float* %y.addr, align 4
+ // CHECK: store double %z, double* %z.addr, align 8
+ // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
+ // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
+ // CHECK: %[[INT:.+]] = load i32, i32* %x.addr, align 4
+ // CHECK: %[[FLOAT:.+]] = load float, float* %y.addr, align 4
+ // CHECK: %[[DOUBLE:.+]] = load double, double* %z.addr, align 8
+ // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv34promise_matching_placement_new_tagifdEE12promise_typenwEmS1_ifd(i64 %[[SIZE]], i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]])
+ co_return;
+}
+
struct promise_delete_tag {};
template<>
diff --git a/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp b/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp
index 45c5f67..48931cb 100644
--- a/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp
+++ b/clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp
@@ -41,7 +41,7 @@
// CHECK: {{.*}}[[CoroInit]]:
// CHECK: store i1 false, i1* %gro.active
-// CHECK-NEXT: call void @{{.*get_return_objectEv}}(%struct.coro* sret %agg.result
+// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro* sret %agg.result
// CHECK-NEXT: store i1 true, i1* %gro.active
co_return;
}
@@ -78,7 +78,7 @@
// CHECK: {{.*}}[[InitOnSuccess]]:
// CHECK: store i1 false, i1* %gro.active
-// CHECK-NEXT: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret %agg.result
+// CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret %agg.result
// CHECK-NEXT: store i1 true, i1* %gro.active
// CHECK: [[RetLabel]]:
diff --git a/clang/test/CodeGenCoroutines/coro-params.cpp b/clang/test/CodeGenCoroutines/coro-params.cpp
index 45b8aef..078d7ee 100644
--- a/clang/test/CodeGenCoroutines/coro-params.cpp
+++ b/clang/test/CodeGenCoroutines/coro-params.cpp
@@ -69,12 +69,12 @@
// CHECK: store i32 %val, i32* %[[ValAddr:.+]]
// CHECK: call i8* @llvm.coro.begin(
- // CHECK-NEXT: call void @_ZN8MoveOnlyC1EOS_(%struct.MoveOnly* %[[MoCopy]], %struct.MoveOnly* dereferenceable(4) %[[MoParam]])
+ // CHECK: call void @_ZN8MoveOnlyC1EOS_(%struct.MoveOnly* %[[MoCopy]], %struct.MoveOnly* dereferenceable(4) %[[MoParam]])
// CHECK-NEXT: call void @_ZN11MoveAndCopyC1EOS_(%struct.MoveAndCopy* %[[McCopy]], %struct.MoveAndCopy* dereferenceable(4) %[[McParam]]) #
// CHECK-NEXT: invoke void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeC1Ev(
// CHECK: call void @_ZN14suspend_always12await_resumeEv(
- // CHECK: %[[IntParam:.+]] = load i32, i32* %val.addr
+ // CHECK: %[[IntParam:.+]] = load i32, i32* %val1
// CHECK: %[[MoGep:.+]] = getelementptr inbounds %struct.MoveOnly, %struct.MoveOnly* %[[MoCopy]], i32 0, i32 0
// CHECK: %[[MoVal:.+]] = load i32, i32* %[[MoGep]]
// CHECK: %[[McGep:.+]] = getelementptr inbounds %struct.MoveAndCopy, %struct.MoveAndCopy* %[[McCopy]], i32 0, i32 0
@@ -150,9 +150,9 @@
// CHECK-LABEL: void @_Z38coroutine_matching_promise_constructor28promise_matching_constructorifd(i32, float, double)
void coroutine_matching_promise_constructor(promise_matching_constructor, int, float, double) {
- // CHECK: %[[INT:.+]] = load i32, i32* %.addr, align 4
- // CHECK: %[[FLOAT:.+]] = load float, float* %.addr1, align 4
- // CHECK: %[[DOUBLE:.+]] = load double, double* %.addr2, align 8
+ // CHECK: %[[INT:.+]] = load i32, i32* %5, align 4
+ // CHECK: %[[FLOAT:.+]] = load float, float* %6, align 4
+ // CHECK: %[[DOUBLE:.+]] = load double, double* %7, align 8
// CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJv28promise_matching_constructorifdEE12promise_typeC1ES1_ifd(%"struct.std::experimental::coroutine_traits<void, promise_matching_constructor, int, float, double>::promise_type"* %__promise, i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]])
co_return;
}
diff --git a/clang/test/SemaCXX/coroutines.cpp b/clang/test/SemaCXX/coroutines.cpp
index 4a92403..9bc0fa3 100644
--- a/clang/test/SemaCXX/coroutines.cpp
+++ b/clang/test/SemaCXX/coroutines.cpp
@@ -781,6 +781,102 @@
}
template coro<good_promise_13> dependent_uses_nothrow_new(good_promise_13);
+struct good_promise_custom_new_operator {
+ coro<good_promise_custom_new_operator> get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ void unhandled_exception();
+ void *operator new(unsigned long, double, float, int);
+};
+
+coro<good_promise_custom_new_operator>
+good_coroutine_calls_custom_new_operator(double, float, int) {
+ co_return;
+}
+
+struct coroutine_nonstatic_member_struct;
+
+struct good_promise_nonstatic_member_custom_new_operator {
+ coro<good_promise_nonstatic_member_custom_new_operator> get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ void unhandled_exception();
+ void *operator new(unsigned long, coroutine_nonstatic_member_struct &, double);
+};
+
+struct bad_promise_nonstatic_member_mismatched_custom_new_operator {
+ coro<bad_promise_nonstatic_member_mismatched_custom_new_operator> get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ void unhandled_exception();
+ // expected-note@+1 {{candidate function not viable: requires 2 arguments, but 1 was provided}}
+ void *operator new(unsigned long, double);
+};
+
+struct coroutine_nonstatic_member_struct {
+ coro<good_promise_nonstatic_member_custom_new_operator>
+ good_coroutine_calls_nonstatic_member_custom_new_operator(double) {
+ co_return;
+ }
+
+ coro<bad_promise_nonstatic_member_mismatched_custom_new_operator>
+ bad_coroutine_calls_nonstatic_member_mistmatched_custom_new_operator(double) {
+ // expected-error@-1 {{no matching function for call to 'operator new'}}
+ co_return;
+ }
+};
+
+struct bad_promise_mismatched_custom_new_operator {
+ coro<bad_promise_mismatched_custom_new_operator> get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ void unhandled_exception();
+ // expected-note@+1 {{candidate function not viable: requires 4 arguments, but 1 was provided}}
+ void *operator new(unsigned long, double, float, int);
+};
+
+coro<bad_promise_mismatched_custom_new_operator>
+bad_coroutine_calls_mismatched_custom_new_operator(double) {
+ // expected-error@-1 {{no matching function for call to 'operator new'}}
+ co_return;
+}
+
+struct bad_promise_throwing_custom_new_operator {
+ static coro<bad_promise_throwing_custom_new_operator> get_return_object_on_allocation_failure();
+ coro<bad_promise_throwing_custom_new_operator> get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ void unhandled_exception();
+ // expected-error@+1 {{'operator new' is required to have a non-throwing noexcept specification when the promise type declares 'get_return_object_on_allocation_failure()'}}
+ void *operator new(unsigned long, double, float, int);
+};
+
+coro<bad_promise_throwing_custom_new_operator>
+bad_coroutine_calls_throwing_custom_new_operator(double, float, int) {
+ // expected-note@-1 {{call to 'operator new' implicitly required by coroutine function here}}
+ co_return;
+}
+
+struct good_promise_noexcept_custom_new_operator {
+ static coro<good_promise_noexcept_custom_new_operator> get_return_object_on_allocation_failure();
+ coro<good_promise_noexcept_custom_new_operator> get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ void unhandled_exception();
+ void *operator new(unsigned long, double, float, int) noexcept;
+};
+
+coro<good_promise_noexcept_custom_new_operator>
+good_coroutine_calls_noexcept_custom_new_operator(double, float, int) {
+ co_return;
+}
+
struct mismatch_gro_type_tag1 {};
template<>
struct std::experimental::coroutine_traits<int, mismatch_gro_type_tag1> {