[clang] [libcxx] [llvm] [Clang][CodeGen][Coroutines] Make coroutine startup exception-safe (CWG2935) (PR #177628)

Yexuan Xiao via cfe-commits cfe-commits at lists.llvm.org
Fri Jan 30 08:16:18 PST 2026


https://github.com/YexuanXiao updated https://github.com/llvm/llvm-project/pull/177628

>From 2c70e5670cee67f1525a90cc906cc13ae5051f25 Mon Sep 17 00:00:00 2001
From: Yexuan Xiao <bizwen at nykz.org>
Date: Sat, 24 Jan 2026 01:38:35 +0800
Subject: [PATCH 1/7] Try fix CWG2935, but it breaks HALO

---
 clang/lib/CodeGen/CGCoroutine.cpp             |  33 ++
 .../coro-suspend-cleanups.cpp                 |   6 +-
 coroutine-cwg2935.cpp                         | 387 ++++++++++++++++++
 3 files changed, 423 insertions(+), 3 deletions(-)
 create mode 100644 coroutine-cwg2935.cpp

diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index c80bb0e004367..d3b064e83a8f9 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -656,10 +656,15 @@ struct GetReturnObjectManager {
   bool DirectEmit = false;
 
   Address GroActiveFlag;
+  // Active flag used for DirectEmit return value cleanup. When the coroutine
+  // return value is directly emitted into the return slot, we need to run its
+  // destructor if an exception is thrown before initial-await-resume.
+  Address DirectGroActiveFlag;
   CodeGenFunction::AutoVarEmission GroEmission;
 
   GetReturnObjectManager(CodeGenFunction &CGF, const CoroutineBodyStmt &S)
       : CGF(CGF), Builder(CGF.Builder), S(S), GroActiveFlag(Address::invalid()),
+        DirectGroActiveFlag(Address::invalid()),
         GroEmission(CodeGenFunction::AutoVarEmission::invalid()) {
     // The call to get_­return_­object is sequenced before the call to
     // initial_­suspend and is invoked at most once, but there are caveats
@@ -767,6 +772,25 @@ struct GetReturnObjectManager {
         CGF.EmitAnyExprToMem(S.getReturnValue(), CGF.ReturnValue,
                              S.getReturnValue()->getType().getQualifiers(),
                              /*IsInit*/ true);
+        // If the return value has a non-trivial destructor, register a
+        // conditional cleanup that will destroy it if an exception is thrown
+        // before initial-await-resume. The cleanup is activated now and will
+        // be deactivated once initial_suspend completes normally.
+        if (QualType::DestructionKind DtorKind =
+                S.getReturnValue()->getType().isDestructedType()) {
+          // Create an active flag (initialize to true) for conditional
+          // cleanup. We are not necessarily in a conditional branch here so
+          // use a simple temp alloca instead of createCleanupActiveFlag().
+          auto ActiveFlag = CGF.CreateTempAlloca(Builder.getInt1Ty(),
+                                                CharUnits::One(),
+                                                "direct.gro.active");
+          Builder.CreateStore(Builder.getTrue(), ActiveFlag);
+          CGF.pushDestroyAndDeferDeactivation(
+              DtorKind, CGF.ReturnValue,
+              S.getReturnValue()->getType());
+          CGF.initFullExprCleanupWithFlag(ActiveFlag);
+          DirectGroActiveFlag = ActiveFlag;
+        }
       }
       return;
     }
@@ -986,6 +1010,15 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
     CurCoro.Data->CurrentAwaitKind = AwaitKind::Init;
     CurCoro.Data->ExceptionHandler = S.getExceptionHandler();
     EmitStmt(S.getInitSuspendStmt());
+
+    // If we emitted the return value directly into the return slot, the
+    // destructor cleanup we registered above should only be active while
+    // initial_suspend is in progress. After initial_suspend completes
+    // normally, deactivate the cleanup so the return value is not
+    // destroyed here.
+    if (GroManager.DirectEmit && GroManager.DirectGroActiveFlag.isValid())
+      Builder.CreateStore(Builder.getFalse(), GroManager.DirectGroActiveFlag);
+
     CurCoro.Data->FinalJD = getJumpDestInCurrentScope(FinalBB);
 
     CurCoro.Data->CurrentAwaitKind = AwaitKind::Normal;
diff --git a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
index d71c2c558996a..5aace3f500e07 100644
--- a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
+++ b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
@@ -47,12 +47,12 @@ coroutine ArrayInitCoro() {
     // CHECK-NEXT:  store ptr %arr.reload.addr, ptr %arrayinit.endOfInit.reload.addr, align 8
     // CHECK-NEXT:  call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) %arr.reload.addr, ptr noundef @.str)
     // CHECK-NEXT:  %arrayinit.element = getelementptr inbounds %struct.Printy, ptr %arr.reload.addr, i64 1
-    // CHECK-NEXT:  %arrayinit.element.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 10
+    // CHECK-NEXT:  %arrayinit.element.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 12
     // CHECK-NEXT:  store ptr %arrayinit.element, ptr %arrayinit.element.spill.addr, align 8
     // CHECK-NEXT:  store ptr %arrayinit.element, ptr %arrayinit.endOfInit.reload.addr, align 8
     co_await Awaiter{}
     // CHECK-NEXT:  @_ZNSt14suspend_always11await_readyEv
-    // CHECK-NEXT:  br i1 %{{.+}}, label %await.ready, label %CoroSave30
+    // CHECK-NEXT:  br i1 %{{.+}}, label %await.ready, label %CoroSave34
   };
   // CHECK:       await.cleanup:                                    ; preds = %AfterCoroSuspend{{.*}}
   // CHECK-NEXT:    br label %cleanup{{.*}}.from.await.cleanup
@@ -61,7 +61,7 @@ coroutine ArrayInitCoro() {
   // CHECK:         br label %cleanup{{.*}}
 
   // CHECK:       await.ready:
-  // CHECK-NEXT:    %arrayinit.element.reload.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 10
+  // CHECK-NEXT:    %arrayinit.element.reload.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 12
   // CHECK-NEXT:    %arrayinit.element.reload = load ptr, ptr %arrayinit.element.reload.addr, align 8
   // CHECK-NEXT:    call void @_ZN7Awaiter12await_resumeEv
   // CHECK-NEXT:    store i1 false, ptr %cleanup.isactive.reload.addr, align 1
diff --git a/coroutine-cwg2935.cpp b/coroutine-cwg2935.cpp
new file mode 100644
index 0000000000000..e0c3b8eb60e81
--- /dev/null
+++ b/coroutine-cwg2935.cpp
@@ -0,0 +1,387 @@
+#include <coroutine>
+#include <array>
+#include <cassert>
+#include <cstdlib>
+
+enum check_points
+{
+	para_copy_ctor,
+	para_dtor,
+	promise_ctor,
+	promise_dtor,
+	get_return_obj,
+	task_ctor,
+	task_dtor,
+	init_suspend,
+	init_a_ready,
+	init_a_suspend,
+	init_a_resume,
+	awaiter_ctor,
+	awaiter_dtor,
+	return_v,
+	unhandled,
+	fin_suspend
+};
+
+using per_test_counts_type = std::array<int, fin_suspend + 1>;
+
+per_test_counts_type per_test_counts{};
+
+void record(check_points cp)
+{
+	// Each checkpoint's executions must be precisely recorded to prevent double free
+	++per_test_counts[cp];
+}
+
+void clear()
+{
+	per_test_counts = per_test_counts_type{};
+}
+
+std::array<bool, fin_suspend + 1> checked_cond{};
+
+// Each test will throw an exception at a designated location. After the coroutine is
+// invoked, the execution status of all checkpoints will be checked, and then switch
+// to the next test. Before throwing an exception, record the execution status first.
+void throw_c(check_points cp)
+{
+	record(cp);
+	// Once that point has been tested, it will not be tested again.
+	if (checked_cond[cp] == false)
+	{
+		checked_cond[cp] = true;
+		throw 0;
+	}
+}
+
+std::size_t allocate_count = 0;
+
+void* operator new(std::size_t size)
+{
+	++allocate_count;
+	// When the coroutine is invoked, memory allocation is the first operation performed
+	if (void* ptr = std::malloc(size)) {
+		return ptr;
+	}
+	std::abort();
+}
+
+void operator delete(void* ptr) noexcept
+{
+	// Deallocation is performed last
+	--allocate_count;
+	std::free(ptr);
+}
+
+struct task
+{
+	task() {
+		throw_c(task_ctor);
+	}
+	~task() {
+		record(task_dtor);
+	}
+	// In this test, the task should be constructed directly, without copy elision
+	task(task const&) = delete;
+	struct promise_type
+	{
+		promise_type()
+		{
+			throw_c(promise_ctor);
+		}
+		~promise_type()
+		{
+			record(promise_dtor);
+		}
+		promise_type(const promise_type&) = delete;
+		task get_return_object()
+		{
+			throw_c(get_return_obj);
+			return {};
+		}
+		auto initial_suspend()
+		{
+			throw_c(init_suspend);
+			struct initial_awaiter {
+				initial_awaiter() {
+					throw_c(awaiter_ctor);
+				}
+				~initial_awaiter() {
+					record(awaiter_dtor);
+				}
+				initial_awaiter(const initial_awaiter&) = delete;
+				bool await_ready()
+				{
+					throw_c(init_a_ready);
+					return false;
+				}
+				bool await_suspend(std::coroutine_handle<void>)
+				{
+					throw_c(init_a_suspend);
+					return false;
+				}
+				void await_resume()
+				{
+					// From this point onward, all exceptions are handled by `unhandled_exception`
+					// Since the defect of exceptions escaping from `unhandled_exception` remains
+					// unresolved (CWG2934), this test only covers the coroutine startup phase.
+					// Once CWG2934 is resolved, further tests can be added based on this one.
+					record(init_a_resume);
+				}
+			};
+
+			return initial_awaiter{};
+		}
+		void return_void()
+		{
+			record(return_v);
+		}
+		void unhandled_exception()
+		{
+			record(unhandled);
+		}
+		// Note that no exceptions may leak after final_suspend is called, otherwise
+		// the behavior is undefined
+		std::suspend_never final_suspend() noexcept
+		{
+			record(fin_suspend);
+			return {};
+		}
+	};
+};
+
+struct copy_observer
+{
+private:
+	copy_observer() = default;
+public:
+	copy_observer(copy_observer const&)
+	{
+		throw_c(para_copy_ctor);
+	}
+	~copy_observer()
+	{
+		record(para_dtor);
+	}
+
+	static copy_observer get() {
+		return {};
+	}
+};
+
+// 
+const auto global_observer = copy_observer::get();
+
+task coro(copy_observer)
+{
+	co_return;
+}
+
+void catch_coro() try
+{
+	coro(global_observer);
+}
+catch (...)
+{
+}
+
+// Currently, the conditions at the eight potential exception-throwing points need
+// to be checked. More checkpoints can be added after CWG2934 is resolved.
+int main()
+{
+	per_test_counts_type e{};
+	allocate_count = 0;
+	catch_coro();
+	e = {
+		1, // para_copy_ctor
+		0, // para_dtor
+		0, // promise_ctor
+		0, // promise_dtor
+		0, // get_return_obj
+		0, // task_ctor
+		0, // task_dtor
+		0, // init_suspend
+		0, // init_a_ready
+		0, // init_a_suspend
+		0, // init_a_resume
+		0, // awaiter_ctor
+		0, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	catch_coro();
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		0, // promise_dtor
+		0, // get_return_obj
+		0, // task_ctor
+		0, // task_dtor
+		0, // init_suspend
+		0, // init_a_ready
+		0, // init_a_suspend
+		0, // init_a_resume
+		0, // awaiter_ctor
+		0, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	catch_coro();
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		1, // promise_dtor
+		1, // get_return_obj
+		0, // task_ctor
+		0, // task_dtor
+		0, // init_suspend
+		0, // init_a_ready
+		0, // init_a_suspend
+		0, // init_a_resume
+		0, // awaiter_ctor
+		0, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	catch_coro();
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		1, // promise_dtor
+		1, // get_return_obj
+		1, // task_ctor
+		0, // task_dtor
+		0, // init_suspend
+		0, // init_a_ready
+		0, // init_a_suspend
+		0, // init_a_resume
+		0, // awaiter_ctor
+		0, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	catch_coro();
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		1, // promise_dtor
+		1, // get_return_obj
+		1, // task_ctor
+		1, // task_dtor
+		1, // init_suspend
+		0, // init_a_ready
+		0, // init_a_suspend
+		0, // init_a_resume
+		0, // awaiter_ctor
+		0, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts); // Clang currently fails starting from this line. If the code you modified causes tests above this line to fail, it indicates that you have broken the correct code and should start over from scratch.
+	clear();
+	catch_coro();
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		1, // promise_dtor
+		1, // get_return_obj
+		1, // task_ctor
+		1, // task_dtor
+		1, // init_suspend
+		0, // init_a_ready
+		0, // init_a_suspend
+		0, // init_a_resume
+		1, // awaiter_ctor
+		0, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	catch_coro();
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		1, // promise_dtor
+		1, // get_return_obj
+		1, // task_ctor
+		1, // task_dtor
+		1, // init_suspend
+		1, // init_a_ready
+		0, // init_a_suspend
+		0, // init_a_resume
+		1, // awaiter_ctor
+		1, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	catch_coro();
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		1, // promise_dtor
+		1, // get_return_obj
+		1, // task_ctor
+		1, // task_dtor
+		1, // init_suspend
+		1, // init_a_ready
+		1, // init_a_suspend
+		0, // init_a_resume
+		1, // awaiter_ctor
+		1, // awaiter_dtor
+		0, // return_v,
+		0, // unhandled,
+		0, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	// Test for execution without exceptions
+	{
+		coro(global_observer);
+	}
+	e = {
+		2, // para_copy_ctor
+		2, // para_dtor
+		1, // promise_ctor
+		1, // promise_dtor
+		1, // get_return_obj
+		1, // task_ctor
+		1, // task_dtor
+		1, // init_suspend
+		1, // init_a_ready
+		1, // init_a_suspend
+		1, // init_a_resume
+		1, // awaiter_ctor
+		1, // awaiter_dtor
+		1, // return_v,
+		0, // unhandled,
+		1, // fin_suspend
+	};
+	assert(e == per_test_counts);
+	clear();
+	assert(allocate_count == 0);
+}
\ No newline at end of file

>From d79b44075ef6d797e8ede37e913032ee36a7f470 Mon Sep 17 00:00:00 2001
From: Yexuan Xiao <bizwen at nykz.org>
Date: Sat, 24 Jan 2026 02:07:35 +0800
Subject: [PATCH 2/7] Fix code formatting

---
 clang/lib/CodeGen/CGCoroutine.cpp |  10 +-
 coroutine-cwg2935.cpp             | 654 ++++++++++++++----------------
 2 files changed, 308 insertions(+), 356 deletions(-)

diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index d3b064e83a8f9..e454fb91c104d 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -781,13 +781,11 @@ struct GetReturnObjectManager {
           // Create an active flag (initialize to true) for conditional
           // cleanup. We are not necessarily in a conditional branch here so
           // use a simple temp alloca instead of createCleanupActiveFlag().
-          auto ActiveFlag = CGF.CreateTempAlloca(Builder.getInt1Ty(),
-                                                CharUnits::One(),
-                                                "direct.gro.active");
+          auto ActiveFlag = CGF.CreateTempAlloca(
+              Builder.getInt1Ty(), CharUnits::One(), "direct.gro.active");
           Builder.CreateStore(Builder.getTrue(), ActiveFlag);
-          CGF.pushDestroyAndDeferDeactivation(
-              DtorKind, CGF.ReturnValue,
-              S.getReturnValue()->getType());
+          CGF.pushDestroyAndDeferDeactivation(DtorKind, CGF.ReturnValue,
+                                              S.getReturnValue()->getType());
           CGF.initFullExprCleanupWithFlag(ActiveFlag);
           DirectGroActiveFlag = ActiveFlag;
         }
diff --git a/coroutine-cwg2935.cpp b/coroutine-cwg2935.cpp
index e0c3b8eb60e81..38a7a560c5d8d 100644
--- a/coroutine-cwg2935.cpp
+++ b/coroutine-cwg2935.cpp
@@ -1,387 +1,341 @@
-#include <coroutine>
 #include <array>
 #include <cassert>
+#include <coroutine>
 #include <cstdlib>
 
-enum check_points
-{
-	para_copy_ctor,
-	para_dtor,
-	promise_ctor,
-	promise_dtor,
-	get_return_obj,
-	task_ctor,
-	task_dtor,
-	init_suspend,
-	init_a_ready,
-	init_a_suspend,
-	init_a_resume,
-	awaiter_ctor,
-	awaiter_dtor,
-	return_v,
-	unhandled,
-	fin_suspend
+enum check_points {
+  para_copy_ctor,
+  para_dtor,
+  promise_ctor,
+  promise_dtor,
+  get_return_obj,
+  task_ctor,
+  task_dtor,
+  init_suspend,
+  init_a_ready,
+  init_a_suspend,
+  init_a_resume,
+  awaiter_ctor,
+  awaiter_dtor,
+  return_v,
+  unhandled,
+  fin_suspend
 };
 
 using per_test_counts_type = std::array<int, fin_suspend + 1>;
 
 per_test_counts_type per_test_counts{};
 
-void record(check_points cp)
-{
-	// Each checkpoint's executions must be precisely recorded to prevent double free
-	++per_test_counts[cp];
+void record(check_points cp) {
+  // Each checkpoint's executions must be precisely recorded to prevent double
+  // free
+  ++per_test_counts[cp];
 }
 
-void clear()
-{
-	per_test_counts = per_test_counts_type{};
-}
+void clear() { per_test_counts = per_test_counts_type{}; }
 
 std::array<bool, fin_suspend + 1> checked_cond{};
 
-// Each test will throw an exception at a designated location. After the coroutine is
-// invoked, the execution status of all checkpoints will be checked, and then switch
-// to the next test. Before throwing an exception, record the execution status first.
-void throw_c(check_points cp)
-{
-	record(cp);
-	// Once that point has been tested, it will not be tested again.
-	if (checked_cond[cp] == false)
-	{
-		checked_cond[cp] = true;
-		throw 0;
-	}
+// Each test will throw an exception at a designated location. After the
+// coroutine is invoked, the execution status of all checkpoints will be
+// checked, and then switch to the next test. Before throwing an exception,
+// record the execution status first.
+void throw_c(check_points cp) {
+  record(cp);
+  // Once that point has been tested, it will not be tested again.
+  if (checked_cond[cp] == false) {
+    checked_cond[cp] = true;
+    throw 0;
+  }
 }
 
 std::size_t allocate_count = 0;
 
-void* operator new(std::size_t size)
-{
-	++allocate_count;
-	// When the coroutine is invoked, memory allocation is the first operation performed
-	if (void* ptr = std::malloc(size)) {
-		return ptr;
-	}
-	std::abort();
+void *operator new(std::size_t size) {
+  ++allocate_count;
+  // When the coroutine is invoked, memory allocation is the first operation
+  // performed
+  if (void *ptr = std::malloc(size)) {
+    return ptr;
+  }
+  std::abort();
 }
 
-void operator delete(void* ptr) noexcept
-{
-	// Deallocation is performed last
-	--allocate_count;
-	std::free(ptr);
+void operator delete(void *ptr) noexcept {
+  // Deallocation is performed last
+  --allocate_count;
+  std::free(ptr);
 }
 
-struct task
-{
-	task() {
-		throw_c(task_ctor);
-	}
-	~task() {
-		record(task_dtor);
-	}
-	// In this test, the task should be constructed directly, without copy elision
-	task(task const&) = delete;
-	struct promise_type
-	{
-		promise_type()
-		{
-			throw_c(promise_ctor);
-		}
-		~promise_type()
-		{
-			record(promise_dtor);
-		}
-		promise_type(const promise_type&) = delete;
-		task get_return_object()
-		{
-			throw_c(get_return_obj);
-			return {};
-		}
-		auto initial_suspend()
-		{
-			throw_c(init_suspend);
-			struct initial_awaiter {
-				initial_awaiter() {
-					throw_c(awaiter_ctor);
-				}
-				~initial_awaiter() {
-					record(awaiter_dtor);
-				}
-				initial_awaiter(const initial_awaiter&) = delete;
-				bool await_ready()
-				{
-					throw_c(init_a_ready);
-					return false;
-				}
-				bool await_suspend(std::coroutine_handle<void>)
-				{
-					throw_c(init_a_suspend);
-					return false;
-				}
-				void await_resume()
-				{
-					// From this point onward, all exceptions are handled by `unhandled_exception`
-					// Since the defect of exceptions escaping from `unhandled_exception` remains
-					// unresolved (CWG2934), this test only covers the coroutine startup phase.
-					// Once CWG2934 is resolved, further tests can be added based on this one.
-					record(init_a_resume);
-				}
-			};
+struct task {
+  task() { throw_c(task_ctor); }
+  ~task() { record(task_dtor); }
+  // In this test, the task should be constructed directly, without copy elision
+  task(task const &) = delete;
+  struct promise_type {
+    promise_type() { throw_c(promise_ctor); }
+    ~promise_type() { record(promise_dtor); }
+    promise_type(const promise_type &) = delete;
+    task get_return_object() {
+      throw_c(get_return_obj);
+      return {};
+    }
+    auto initial_suspend() {
+      throw_c(init_suspend);
+      struct initial_awaiter {
+        initial_awaiter() { throw_c(awaiter_ctor); }
+        ~initial_awaiter() { record(awaiter_dtor); }
+        initial_awaiter(const initial_awaiter &) = delete;
+        bool await_ready() {
+          throw_c(init_a_ready);
+          return false;
+        }
+        bool await_suspend(std::coroutine_handle<void>) {
+          throw_c(init_a_suspend);
+          return false;
+        }
+        void await_resume() {
+          // From this point onward, all exceptions are handled by
+          // `unhandled_exception` Since the defect of exceptions escaping from
+          // `unhandled_exception` remains unresolved (CWG2934), this test only
+          // covers the coroutine startup phase. Once CWG2934 is resolved,
+          // further tests can be added based on this one.
+          record(init_a_resume);
+        }
+      };
 
-			return initial_awaiter{};
-		}
-		void return_void()
-		{
-			record(return_v);
-		}
-		void unhandled_exception()
-		{
-			record(unhandled);
-		}
-		// Note that no exceptions may leak after final_suspend is called, otherwise
-		// the behavior is undefined
-		std::suspend_never final_suspend() noexcept
-		{
-			record(fin_suspend);
-			return {};
-		}
-	};
+      return initial_awaiter{};
+    }
+    void return_void() { record(return_v); }
+    void unhandled_exception() { record(unhandled); }
+    // Note that no exceptions may leak after final_suspend is called, otherwise
+    // the behavior is undefined
+    std::suspend_never final_suspend() noexcept {
+      record(fin_suspend);
+      return {};
+    }
+  };
 };
 
-struct copy_observer
-{
+struct copy_observer {
 private:
-	copy_observer() = default;
+  copy_observer() = default;
+
 public:
-	copy_observer(copy_observer const&)
-	{
-		throw_c(para_copy_ctor);
-	}
-	~copy_observer()
-	{
-		record(para_dtor);
-	}
+  copy_observer(copy_observer const &) { throw_c(para_copy_ctor); }
+  ~copy_observer() { record(para_dtor); }
 
-	static copy_observer get() {
-		return {};
-	}
+  static copy_observer get() { return {}; }
 };
 
-// 
+//
 const auto global_observer = copy_observer::get();
 
-task coro(copy_observer)
-{
-	co_return;
-}
+task coro(copy_observer) { co_return; }
 
-void catch_coro() try
-{
-	coro(global_observer);
-}
-catch (...)
-{
+void catch_coro() try { coro(global_observer); } catch (...) {
 }
 
-// Currently, the conditions at the eight potential exception-throwing points need
-// to be checked. More checkpoints can be added after CWG2934 is resolved.
-int main()
-{
-	per_test_counts_type e{};
-	allocate_count = 0;
-	catch_coro();
-	e = {
-		1, // para_copy_ctor
-		0, // para_dtor
-		0, // promise_ctor
-		0, // promise_dtor
-		0, // get_return_obj
-		0, // task_ctor
-		0, // task_dtor
-		0, // init_suspend
-		0, // init_a_ready
-		0, // init_a_suspend
-		0, // init_a_resume
-		0, // awaiter_ctor
-		0, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	catch_coro();
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		0, // promise_dtor
-		0, // get_return_obj
-		0, // task_ctor
-		0, // task_dtor
-		0, // init_suspend
-		0, // init_a_ready
-		0, // init_a_suspend
-		0, // init_a_resume
-		0, // awaiter_ctor
-		0, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	catch_coro();
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		1, // promise_dtor
-		1, // get_return_obj
-		0, // task_ctor
-		0, // task_dtor
-		0, // init_suspend
-		0, // init_a_ready
-		0, // init_a_suspend
-		0, // init_a_resume
-		0, // awaiter_ctor
-		0, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	catch_coro();
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		1, // promise_dtor
-		1, // get_return_obj
-		1, // task_ctor
-		0, // task_dtor
-		0, // init_suspend
-		0, // init_a_ready
-		0, // init_a_suspend
-		0, // init_a_resume
-		0, // awaiter_ctor
-		0, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	catch_coro();
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		1, // promise_dtor
-		1, // get_return_obj
-		1, // task_ctor
-		1, // task_dtor
-		1, // init_suspend
-		0, // init_a_ready
-		0, // init_a_suspend
-		0, // init_a_resume
-		0, // awaiter_ctor
-		0, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts); // Clang currently fails starting from this line. If the code you modified causes tests above this line to fail, it indicates that you have broken the correct code and should start over from scratch.
-	clear();
-	catch_coro();
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		1, // promise_dtor
-		1, // get_return_obj
-		1, // task_ctor
-		1, // task_dtor
-		1, // init_suspend
-		0, // init_a_ready
-		0, // init_a_suspend
-		0, // init_a_resume
-		1, // awaiter_ctor
-		0, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	catch_coro();
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		1, // promise_dtor
-		1, // get_return_obj
-		1, // task_ctor
-		1, // task_dtor
-		1, // init_suspend
-		1, // init_a_ready
-		0, // init_a_suspend
-		0, // init_a_resume
-		1, // awaiter_ctor
-		1, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	catch_coro();
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		1, // promise_dtor
-		1, // get_return_obj
-		1, // task_ctor
-		1, // task_dtor
-		1, // init_suspend
-		1, // init_a_ready
-		1, // init_a_suspend
-		0, // init_a_resume
-		1, // awaiter_ctor
-		1, // awaiter_dtor
-		0, // return_v,
-		0, // unhandled,
-		0, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	// Test for execution without exceptions
-	{
-		coro(global_observer);
-	}
-	e = {
-		2, // para_copy_ctor
-		2, // para_dtor
-		1, // promise_ctor
-		1, // promise_dtor
-		1, // get_return_obj
-		1, // task_ctor
-		1, // task_dtor
-		1, // init_suspend
-		1, // init_a_ready
-		1, // init_a_suspend
-		1, // init_a_resume
-		1, // awaiter_ctor
-		1, // awaiter_dtor
-		1, // return_v,
-		0, // unhandled,
-		1, // fin_suspend
-	};
-	assert(e == per_test_counts);
-	clear();
-	assert(allocate_count == 0);
+// Currently, the conditions at the eight potential exception-throwing points
+// need to be checked. More checkpoints can be added after CWG2934 is resolved.
+int main() {
+  per_test_counts_type e{};
+  allocate_count = 0;
+  catch_coro();
+  e = {
+      1, // para_copy_ctor
+      0, // para_dtor
+      0, // promise_ctor
+      0, // promise_dtor
+      0, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      0, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      0, // promise_dtor
+      0, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      0, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      0, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      0, // task_dtor
+      0, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      1, // task_dtor
+      1, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e ==
+         per_test_counts); // Clang currently fails starting from this line. If
+                           // the code you modified causes tests above this line
+                           // to fail, it indicates that you have broken the
+                           // correct code and should start over from scratch.
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      1, // task_dtor
+      1, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      1, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      1, // task_dtor
+      1, // init_suspend
+      1, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      1, // awaiter_ctor
+      1, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      1, // task_dtor
+      1, // init_suspend
+      1, // init_a_ready
+      1, // init_a_suspend
+      0, // init_a_resume
+      1, // awaiter_ctor
+      1, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  // Test for execution without exceptions
+  {
+    coro(global_observer);
+  }
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      1, // task_dtor
+      1, // init_suspend
+      1, // init_a_ready
+      1, // init_a_suspend
+      1, // init_a_resume
+      1, // awaiter_ctor
+      1, // awaiter_dtor
+      1, // return_v,
+      0, // unhandled,
+      1, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  assert(allocate_count == 0);
 }
\ No newline at end of file

>From f159a548aa01607dc223f75a3fe985c63f936f4e Mon Sep 17 00:00:00 2001
From: Yexuan Xiao <bizwen at nykz.org>
Date: Fri, 30 Jan 2026 15:49:06 +0800
Subject: [PATCH 3/7] Skip return-value cleanup if await
 promise.initial_suspend() doesn't throw

---
 clang/lib/CodeGen/CGCoroutine.cpp             | 36 ++++++++++++-------
 clang/test/CodeGenCoroutines/coro-halo.cpp    |  2 +-
 .../coro-suspend-cleanups.cpp                 |  6 ++--
 clang/test/CodeGenCoroutines/pr56919.cpp      |  2 +-
 4 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index df59bb2cfa97a..39d6ab1621bb3 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -660,11 +660,17 @@ struct GetReturnObjectManager {
   // Active flag used for DirectEmit return value cleanup. When the coroutine
   // return value is directly emitted into the return slot, we need to run its
   // destructor if an exception is thrown before initial-await-resume.
+  // DirectGroActiveFlag is unused when InitialSuspendCanThrow is false.
+  bool InitialSuspendCanThrow = false;
   Address DirectGroActiveFlag;
+
   CodeGenFunction::AutoVarEmission GroEmission;
 
   GetReturnObjectManager(CodeGenFunction &CGF, const CoroutineBodyStmt &S)
-      : CGF(CGF), Builder(CGF.Builder), S(S), GroActiveFlag(Address::invalid()),
+      : CGF(CGF), Builder(CGF.Builder), S(S),
+
+        GroActiveFlag(Address::invalid()),
+        InitialSuspendCanThrow(StmtCanThrow(S.getInitSuspendStmt())),
         DirectGroActiveFlag(Address::invalid()),
         GroEmission(CodeGenFunction::AutoVarEmission::invalid()) {
     // The call to get_­return_­object is sequenced before the call to
@@ -777,18 +783,21 @@ struct GetReturnObjectManager {
         // conditional cleanup that will destroy it if an exception is thrown
         // before initial-await-resume. The cleanup is activated now and will
         // be deactivated once initial_suspend completes normally.
-        if (QualType::DestructionKind DtorKind =
-                S.getReturnValue()->getType().isDestructedType()) {
-          // Create an active flag (initialize to true) for conditional
-          // cleanup. We are not necessarily in a conditional branch here so
-          // use a simple temp alloca instead of createCleanupActiveFlag().
-          auto ActiveFlag = CGF.CreateTempAlloca(
-              Builder.getInt1Ty(), CharUnits::One(), "direct.gro.active");
-          Builder.CreateStore(Builder.getTrue(), ActiveFlag);
-          CGF.pushDestroyAndDeferDeactivation(DtorKind, CGF.ReturnValue,
-                                              S.getReturnValue()->getType());
-          CGF.initFullExprCleanupWithFlag(ActiveFlag);
-          DirectGroActiveFlag = ActiveFlag;
+        if (InitialSuspendCanThrow) {
+          if (QualType::DestructionKind DtorKind =
+                  S.getReturnValue()->getType().isDestructedType();
+              DtorKind) {
+            // Create an active flag (initialize to true) for conditional
+            // cleanup. We are not necessarily in a conditional branch here so
+            // use a simple temp alloca instead of createCleanupActiveFlag().
+            auto ActiveFlag = CGF.CreateTempAlloca(
+                Builder.getInt1Ty(), CharUnits::One(), "direct.gro.active");
+            Builder.CreateStore(Builder.getTrue(), ActiveFlag);
+            CGF.pushDestroyAndDeferDeactivation(DtorKind, CGF.ReturnValue,
+                                                S.getReturnValue()->getType());
+            CGF.initFullExprCleanupWithFlag(ActiveFlag);
+            DirectGroActiveFlag = ActiveFlag;
+          }
         }
       }
       return;
@@ -804,6 +813,7 @@ struct GetReturnObjectManager {
     CGF.EmitAutoVarInit(GroEmission);
     Builder.CreateStore(Builder.getTrue(), GroActiveFlag);
   }
+
   // The GRO returns either when it is first suspended or when it completes
   // without ever being suspended. The EmitGroConv function evaluates these
   // conditions and perform the conversion if needed.
diff --git a/clang/test/CodeGenCoroutines/coro-halo.cpp b/clang/test/CodeGenCoroutines/coro-halo.cpp
index e75bedaf81fa2..915e0efa9bbea 100644
--- a/clang/test/CodeGenCoroutines/coro-halo.cpp
+++ b/clang/test/CodeGenCoroutines/coro-halo.cpp
@@ -13,7 +13,7 @@ template <typename T> struct generator {
       this->current_value = value;
       return {};
     }
-    std::suspend_always initial_suspend() { return {}; }
+    std::suspend_always initial_suspend() noexcept { return {}; }
     std::suspend_always final_suspend() noexcept { return {}; }
     generator get_return_object() { return generator{this}; };
     void unhandled_exception() {}
diff --git a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
index 5aace3f500e07..d71c2c558996a 100644
--- a/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
+++ b/clang/test/CodeGenCoroutines/coro-suspend-cleanups.cpp
@@ -47,12 +47,12 @@ coroutine ArrayInitCoro() {
     // CHECK-NEXT:  store ptr %arr.reload.addr, ptr %arrayinit.endOfInit.reload.addr, align 8
     // CHECK-NEXT:  call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) %arr.reload.addr, ptr noundef @.str)
     // CHECK-NEXT:  %arrayinit.element = getelementptr inbounds %struct.Printy, ptr %arr.reload.addr, i64 1
-    // CHECK-NEXT:  %arrayinit.element.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 12
+    // CHECK-NEXT:  %arrayinit.element.spill.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 10
     // CHECK-NEXT:  store ptr %arrayinit.element, ptr %arrayinit.element.spill.addr, align 8
     // CHECK-NEXT:  store ptr %arrayinit.element, ptr %arrayinit.endOfInit.reload.addr, align 8
     co_await Awaiter{}
     // CHECK-NEXT:  @_ZNSt14suspend_always11await_readyEv
-    // CHECK-NEXT:  br i1 %{{.+}}, label %await.ready, label %CoroSave34
+    // CHECK-NEXT:  br i1 %{{.+}}, label %await.ready, label %CoroSave30
   };
   // CHECK:       await.cleanup:                                    ; preds = %AfterCoroSuspend{{.*}}
   // CHECK-NEXT:    br label %cleanup{{.*}}.from.await.cleanup
@@ -61,7 +61,7 @@ coroutine ArrayInitCoro() {
   // CHECK:         br label %cleanup{{.*}}
 
   // CHECK:       await.ready:
-  // CHECK-NEXT:    %arrayinit.element.reload.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 12
+  // CHECK-NEXT:    %arrayinit.element.reload.addr = getelementptr inbounds %_Z13ArrayInitCorov.Frame, ptr %0, i32 0, i32 10
   // CHECK-NEXT:    %arrayinit.element.reload = load ptr, ptr %arrayinit.element.reload.addr, align 8
   // CHECK-NEXT:    call void @_ZN7Awaiter12await_resumeEv
   // CHECK-NEXT:    store i1 false, ptr %cleanup.isactive.reload.addr, align 1
diff --git a/clang/test/CodeGenCoroutines/pr56919.cpp b/clang/test/CodeGenCoroutines/pr56919.cpp
index baa8c27ce6649..84a27a302e607 100644
--- a/clang/test/CodeGenCoroutines/pr56919.cpp
+++ b/clang/test/CodeGenCoroutines/pr56919.cpp
@@ -30,7 +30,7 @@ class Task final {
 
     void unhandled_exception() {}
 
-    std::suspend_always initial_suspend() { return {}; }
+    std::suspend_always initial_suspend() noexcept { return {}; }
 
     auto await_transform(Task<void> co) {
       return await_transform(std::move(co.handle_.promise()));

>From 5429064c0260489b17d941f9c2c59a84eefdd776 Mon Sep 17 00:00:00 2001
From: Yexuan Xiao <bizwen at nykz.org>
Date: Fri, 30 Jan 2026 21:52:55 +0800
Subject: [PATCH 4/7] Add a new warning "-Winitial-suspend-throw"

---
 clang/include/clang/Basic/DiagnosticGroups.td | 10 ++-
 .../clang/Basic/DiagnosticSemaKinds.td        |  6 ++
 clang/include/clang/Sema/Sema.h               |  1 +
 clang/lib/CodeGen/CGCoroutine.cpp             |  4 +-
 clang/lib/Sema/SemaCoroutine.cpp              | 40 ++++++++---
 .../test/SemaCXX/addr-label-in-coroutines.cpp |  2 +-
 clang/test/SemaCXX/co_await-range-for.cpp     |  8 +--
 clang/test/SemaCXX/coreturn-eh.cpp            |  4 +-
 clang/test/SemaCXX/coreturn.cpp               | 14 ++--
 clang/test/SemaCXX/coro-lifetimebound.cpp     |  4 +-
 .../SemaCXX/coro-return-type-and-wrapper.cpp  |  2 +-
 clang/test/SemaCXX/coroutine-alloc-2.cpp      |  2 +-
 clang/test/SemaCXX/coroutine-alloc-3.cpp      |  2 +-
 clang/test/SemaCXX/coroutine-alloc-4.cpp      | 12 ++--
 clang/test/SemaCXX/coroutine-allocs.cpp       |  4 +-
 clang/test/SemaCXX/coroutine-dealloc.cpp      |  4 +-
 .../coroutine-final-suspend-noexcept.cpp      | 12 +++-
 clang/test/SemaCXX/coroutine-no-move-ctor.cpp |  2 +-
 .../SemaCXX/coroutine-no-valid-dealloc.cpp    |  2 +-
 clang/test/SemaCXX/coroutine-rvo.cpp          |  4 +-
 clang/test/SemaCXX/coroutine-unevaluate.cpp   |  2 +-
 .../coroutine-unhandled_exception-warning.cpp |  2 +-
 .../SemaCXX/coroutine-unreachable-warning.cpp |  2 +-
 .../coroutine_handle-address-return-type.cpp  |  2 +-
 ...utine_initial_suspend_exception_waning.cpp | 62 ++++++++++++++++
 clang/test/SemaCXX/coroutines.cpp             | 72 +++++++++----------
 .../test/SemaCXX/cxx2b-deducing-this-coro.cpp |  2 +-
 clang/test/SemaCXX/type-aware-coroutines.cpp  | 10 +--
 clang/test/SemaCXX/warn-unsequenced-coro.cpp  |  2 +-
 29 files changed, 195 insertions(+), 100 deletions(-)
 create mode 100644 clang/test/SemaCXX/coroutine_initial_suspend_exception_waning.cpp

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index d36ee57fe7651..1872a26f3cf09 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -69,9 +69,13 @@ def CoroNonAlignedAllocationFunction :
   DiagGroup<"coro-non-aligned-allocation-function">;
 def CoroTypeAwareAllocationFunction :
   DiagGroup<"coro-type-aware-allocation-function">;
-def Coroutine : DiagGroup<"coroutine", [CoroutineMissingUnhandledException, DeprecatedCoroutine,
-                                        AlwaysInlineCoroutine, CoroNonAlignedAllocationFunction,
-                                        CoroTypeAwareAllocationFunction]>;
+def InitialSuspendThrowing : DiagGroup<"initial-suspend-throw">;
+def Coroutine
+    : DiagGroup<"coroutine", [CoroutineMissingUnhandledException,
+                              DeprecatedCoroutine, AlwaysInlineCoroutine,
+                              CoroNonAlignedAllocationFunction,
+                              CoroTypeAwareAllocationFunction,
+                              InitialSuspendThrowing]>;
 def ObjCBoolConstantConversion : DiagGroup<"objc-bool-constant-conversion">;
 def ConstantConversion : DiagGroup<"constant-conversion",
                                    [BitFieldConstantConversion,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 807440c107897..c67fd393ecfe3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12755,6 +12755,12 @@ def warn_coroutine_handle_address_invalid_return_type : Warning <
 def err_coroutine_promise_final_suspend_requires_nothrow : Error<
   "the expression 'co_await __promise.final_suspend()' is required to be non-throwing"
 >;
+def warn_coroutine_promise_initial_suspend_throw
+    : Warning<"a potentially throwing 'co_await __promise.initial_suspend()' "
+              "may disable heap allocation elision; "
+              "if it throws, the coroutine return value and state are "
+              "destroyed in the reverse order of their construction">,
+      InGroup<InitialSuspendThrowing>;
 def note_coroutine_function_declare_noexcept : Note<
   "must be declared with 'noexcept'"
 >;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7b3479bbc3677..3ce8199b85979 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3199,6 +3199,7 @@ class Sema final : public SemaBase {
   /// Check that the expression co_await promise.final_suspend() shall not be
   /// potentially-throwing.
   bool checkFinalSuspendNoThrow(const Stmt *FinalSuspend);
+  void warnInitialSuspendNoThrow(const Stmt *InitialSuspend);
 
   ///@}
 
diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index 39d6ab1621bb3..c1a9e64f8c36d 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -667,9 +667,7 @@ struct GetReturnObjectManager {
   CodeGenFunction::AutoVarEmission GroEmission;
 
   GetReturnObjectManager(CodeGenFunction &CGF, const CoroutineBodyStmt &S)
-      : CGF(CGF), Builder(CGF.Builder), S(S),
-
-        GroActiveFlag(Address::invalid()),
+      : CGF(CGF), Builder(CGF.Builder), S(S), GroActiveFlag(Address::invalid()),
         InitialSuspendCanThrow(StmtCanThrow(S.getInitSuspendStmt())),
         DirectGroActiveFlag(Address::invalid()),
         GroEmission(CodeGenFunction::AutoVarEmission::invalid()) {
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index c0aba832dba94..fdead349bb5b3 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -594,8 +594,12 @@ static FunctionScopeInfo *checkCoroutineContext(Sema &S, SourceLocation Loc,
 /// Recursively check \p E and all its children to see if any call target
 /// (including constructor call) is declared noexcept. Also any value returned
 /// from the call has a noexcept destructor.
+///
+/// \param DiagID The error diagnostic ID to emit if a throwing function is
+/// found.
 static void checkNoThrow(Sema &S, const Stmt *E,
-                         llvm::SmallPtrSetImpl<const Decl *> &ThrowingDecls) {
+                         llvm::SmallPtrSetImpl<const Decl *> &ThrowingDecls,
+                         unsigned DiagID) {
   auto checkDeclNoexcept = [&](const Decl *D, bool IsDtor = false) {
     // In the case of dtor, the call to dtor is implicit and hence we should
     // pass nullptr to canCalleeThrow.
@@ -612,13 +616,12 @@ static void checkNoThrow(Sema &S, const Stmt *E,
           return;
       }
       if (ThrowingDecls.empty()) {
-        // [dcl.fct.def.coroutine]p15
-        //   The expression co_await promise.final_suspend() shall not be
-        //   potentially-throwing ([except.spec]).
+        // [dcl.fct.def.coroutine]p11 (initial_suspend) & p15 (final_suspend)
+        //   The expression co_await promise.initial/final_suspend() shall not
+        //   be potentially-throwing ([except.spec]).
         //
         // First time seeing an error, emit the error message.
-        S.Diag(cast<FunctionDecl>(S.CurContext)->getLocation(),
-               diag::err_coroutine_promise_final_suspend_requires_nothrow);
+        S.Diag(cast<FunctionDecl>(S.CurContext)->getLocation(), DiagID);
       }
       ThrowingDecls.insert(D);
     }
@@ -648,28 +651,41 @@ static void checkNoThrow(Sema &S, const Stmt *E,
     for (const auto *Child : E->children()) {
       if (!Child)
         continue;
-      checkNoThrow(S, Child, ThrowingDecls);
+      checkNoThrow(S, Child, ThrowingDecls, DiagID);
     }
 }
 
-bool Sema::checkFinalSuspendNoThrow(const Stmt *FinalSuspend) {
+static bool checkSuspendNoThrow(Sema &S, const Stmt *SuspendExpr,
+                                unsigned DiagID) {
   llvm::SmallPtrSet<const Decl *, 4> ThrowingDecls;
   // We first collect all declarations that should not throw but not declared
   // with noexcept. We then sort them based on the location before printing.
   // This is to avoid emitting the same note multiple times on the same
   // declaration, and also provide a deterministic order for the messages.
-  checkNoThrow(*this, FinalSuspend, ThrowingDecls);
+  checkNoThrow(S, SuspendExpr, ThrowingDecls, DiagID);
+
   auto SortedDecls = llvm::SmallVector<const Decl *, 4>{ThrowingDecls.begin(),
                                                         ThrowingDecls.end()};
   sort(SortedDecls, [](const Decl *A, const Decl *B) {
     return A->getEndLoc() < B->getEndLoc();
   });
   for (const auto *D : SortedDecls) {
-    Diag(D->getEndLoc(), diag::note_coroutine_function_declare_noexcept);
+    S.Diag(D->getEndLoc(), diag::note_coroutine_function_declare_noexcept);
   }
   return ThrowingDecls.empty();
 }
 
+bool Sema::checkFinalSuspendNoThrow(const Stmt *FinalSuspend) {
+  return checkSuspendNoThrow(
+      *this, FinalSuspend,
+      diag::err_coroutine_promise_final_suspend_requires_nothrow);
+}
+
+void Sema::warnInitialSuspendNoThrow(const Stmt *InitialSuspend) {
+  checkSuspendNoThrow(*this, InitialSuspend,
+                      diag::warn_coroutine_promise_initial_suspend_throw);
+}
+
 // [stmt.return.coroutine]p1:
 //   A coroutine shall not enclose a return statement ([stmt.return]).
 static void checkReturnStmtInCoroutine(Sema &S, FunctionScopeInfo *FSI) {
@@ -731,8 +747,10 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc,
   };
 
   StmtResult InitSuspend = buildSuspends("initial_suspend");
-  if (InitSuspend.isInvalid())
+  if (InitSuspend.isInvalid()) {
     return true;
+  }
+  warnInitialSuspendNoThrow(InitSuspend.get());
 
   StmtResult FinalSuspend = buildSuspends("final_suspend");
   if (FinalSuspend.isInvalid() || !checkFinalSuspendNoThrow(FinalSuspend.get()))
diff --git a/clang/test/SemaCXX/addr-label-in-coroutines.cpp b/clang/test/SemaCXX/addr-label-in-coroutines.cpp
index 65d78636e5cdd..aa354a24c8525 100644
--- a/clang/test/SemaCXX/addr-label-in-coroutines.cpp
+++ b/clang/test/SemaCXX/addr-label-in-coroutines.cpp
@@ -5,7 +5,7 @@
 struct resumable {
   struct promise_type {
     resumable get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
diff --git a/clang/test/SemaCXX/co_await-range-for.cpp b/clang/test/SemaCXX/co_await-range-for.cpp
index 064a35038e1c7..898d042f4729e 100644
--- a/clang/test/SemaCXX/co_await-range-for.cpp
+++ b/clang/test/SemaCXX/co_await-range-for.cpp
@@ -42,7 +42,7 @@ struct MyForLoopArrayAwaiter {
     MyForLoopArrayAwaiter get_return_object() { return {}; }
     void return_void();
     void unhandled_exception();
-    suspend_never initial_suspend();
+    suspend_never initial_suspend() noexcept;
     suspend_never final_suspend() noexcept;
     template <class T>
     Awaiter<T *> await_transform(T *) = delete; // expected-note {{explicitly deleted}}
@@ -60,7 +60,7 @@ struct ForLoopAwaiterBadBeginTransform {
     ForLoopAwaiterBadBeginTransform get_return_object();
     void return_void();
     void unhandled_exception();
-    suspend_never initial_suspend();
+    suspend_never initial_suspend() noexcept;
     suspend_never final_suspend() noexcept;
 
     template <class T>
@@ -94,7 +94,7 @@ struct ForLoopAwaiterBadIncTransform {
     ForLoopAwaiterBadIncTransform get_return_object();
     void return_void();
     void unhandled_exception();
-    suspend_never initial_suspend();
+    suspend_never initial_suspend() noexcept;
     suspend_never final_suspend() noexcept;
 
     template <class T>
@@ -135,7 +135,7 @@ struct ForLoopAwaiterCoawaitLookup {
     ForLoopAwaiterCoawaitLookup get_return_object();
     void return_void();
     void unhandled_exception();
-    suspend_never initial_suspend();
+    suspend_never initial_suspend() noexcept;
     suspend_never final_suspend() noexcept;
     template <class T>
     CoawaitTag<T, false> await_transform(BeginTag<T> e);
diff --git a/clang/test/SemaCXX/coreturn-eh.cpp b/clang/test/SemaCXX/coreturn-eh.cpp
index 0d409b9b99bb6..e844df43a2626 100644
--- a/clang/test/SemaCXX/coreturn-eh.cpp
+++ b/clang/test/SemaCXX/coreturn-eh.cpp
@@ -16,7 +16,7 @@ struct object { ~object() {} };
 
 struct promise_void_return_value {
   void get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   void return_value(object);
@@ -25,7 +25,7 @@ struct promise_void_return_value {
 struct VoidTagReturnValue {
   struct promise_type {
     VoidTagReturnValue get_return_object();
-    suspend_always initial_suspend();
+    suspend_always initial_suspend() noexcept;
     suspend_always final_suspend() noexcept;
     void unhandled_exception();
     void return_value(object);
diff --git a/clang/test/SemaCXX/coreturn.cpp b/clang/test/SemaCXX/coreturn.cpp
index 7069a1040db23..1a9be6c58e72e 100644
--- a/clang/test/SemaCXX/coreturn.cpp
+++ b/clang/test/SemaCXX/coreturn.cpp
@@ -12,7 +12,7 @@ struct awaitable {
 
 struct promise_void {
   void get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -20,7 +20,7 @@ struct promise_void {
 
 struct promise_void_return_value {
   void get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   void return_value(int);
@@ -29,7 +29,7 @@ struct promise_void_return_value {
 struct VoidTagNoReturn {
   struct promise_type {
     VoidTagNoReturn get_return_object();
-    suspend_always initial_suspend();
+    suspend_always initial_suspend() noexcept;
     suspend_always final_suspend() noexcept;
     void unhandled_exception();
   };
@@ -38,7 +38,7 @@ struct VoidTagNoReturn {
 struct VoidTagReturnValue {
   struct promise_type {
     VoidTagReturnValue get_return_object();
-    suspend_always initial_suspend();
+    suspend_always initial_suspend() noexcept;
     suspend_always final_suspend() noexcept;
     void unhandled_exception();
     void return_value(int);
@@ -48,7 +48,7 @@ struct VoidTagReturnValue {
 struct VoidTagReturnVoid {
   struct promise_type {
     VoidTagReturnVoid get_return_object();
-    suspend_always initial_suspend();
+    suspend_always initial_suspend() noexcept;
     suspend_always final_suspend() noexcept;
     void unhandled_exception();
     void return_void();
@@ -57,7 +57,7 @@ struct VoidTagReturnVoid {
 
 struct promise_float {
   float get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -65,7 +65,7 @@ struct promise_float {
 
 struct promise_int {
   int get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_value(int);
   void unhandled_exception();
diff --git a/clang/test/SemaCXX/coro-lifetimebound.cpp b/clang/test/SemaCXX/coro-lifetimebound.cpp
index 9e96a296562a0..897dd8140050d 100644
--- a/clang/test/SemaCXX/coro-lifetimebound.cpp
+++ b/clang/test/SemaCXX/coro-lifetimebound.cpp
@@ -10,7 +10,7 @@ template <typename T> struct [[clang::coro_lifetimebound, clang::coro_return_typ
     Co<T> get_return_object() {
       return {};
     }
-    suspend_always initial_suspend();
+    suspend_always initial_suspend() noexcept;
     suspend_always final_suspend() noexcept;
     void unhandled_exception();
     void return_value(const T &t);
@@ -151,7 +151,7 @@ template <typename T> struct [[clang::coro_lifetimebound]] CoNoCRT {
     CoNoCRT<T> get_return_object() {
       return {};
     }
-    suspend_always initial_suspend();
+    suspend_always initial_suspend() noexcept;
     suspend_always final_suspend() noexcept;
     void unhandled_exception();
     void return_value(const T &t);
diff --git a/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp b/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp
index b08e1c9c065a0..c111906f3c5c9 100644
--- a/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp
+++ b/clang/test/SemaCXX/coro-return-type-and-wrapper.cpp
@@ -22,7 +22,7 @@ template <typename T> struct [[clang::coro_return_type]] Gen {
     static Gen<T> get_return_object_on_allocation_failure() {
       return {};
     }
-    suspend_always initial_suspend();
+    suspend_always initial_suspend() noexcept;
     suspend_always final_suspend() noexcept;
     void unhandled_exception();
     void return_value(T t);
diff --git a/clang/test/SemaCXX/coroutine-alloc-2.cpp b/clang/test/SemaCXX/coroutine-alloc-2.cpp
index bcebadc0a1f53..b8697cbad9437 100644
--- a/clang/test/SemaCXX/coroutine-alloc-2.cpp
+++ b/clang/test/SemaCXX/coroutine-alloc-2.cpp
@@ -38,7 +38,7 @@ template <>
 struct std::coroutine_traits<int, promise_on_alloc_failure_tag> {
   struct promise_type {
     int get_return_object() { return 0; }
-    std::suspend_always initial_suspend() { return {}; }
+    std::suspend_always initial_suspend() noexcept { return {}; }
     std::suspend_always final_suspend() noexcept { return {}; }
     void return_void() {}
     void unhandled_exception() {}
diff --git a/clang/test/SemaCXX/coroutine-alloc-3.cpp b/clang/test/SemaCXX/coroutine-alloc-3.cpp
index 629ac88a3df8e..0eb5f94cd274e 100644
--- a/clang/test/SemaCXX/coroutine-alloc-3.cpp
+++ b/clang/test/SemaCXX/coroutine-alloc-3.cpp
@@ -38,7 +38,7 @@ template <>
 struct std::coroutine_traits<int, promise_on_alloc_failure_tag> {
   struct promise_type {
     int get_return_object() { return 0; }
-    std::suspend_always initial_suspend() { return {}; }
+    std::suspend_always initial_suspend() noexcept { return {}; }
     std::suspend_always final_suspend() noexcept { return {}; }
     void return_void() {}
     void unhandled_exception() {}
diff --git a/clang/test/SemaCXX/coroutine-alloc-4.cpp b/clang/test/SemaCXX/coroutine-alloc-4.cpp
index 262c163fb1789..84383b99ed1c7 100644
--- a/clang/test/SemaCXX/coroutine-alloc-4.cpp
+++ b/clang/test/SemaCXX/coroutine-alloc-4.cpp
@@ -10,7 +10,7 @@ namespace std {
 
 struct task {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task{}; }
     void unhandled_exception() {}
@@ -25,7 +25,7 @@ task f() {
 
 struct task2 {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task2{}; }
     void unhandled_exception() {}
@@ -41,7 +41,7 @@ task2 f1() {
 
 struct task3 {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task3{}; }
     void unhandled_exception() {}
@@ -59,7 +59,7 @@ task3 f2() {
 
 struct task4 {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task4{}; }
     void unhandled_exception() {}
@@ -75,7 +75,7 @@ task4 f3(int, double, int) {
 
 struct task5 {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task5{}; }
     void unhandled_exception() {}
@@ -96,7 +96,7 @@ namespace std {
 
 struct task6 {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task6{}; }
     void unhandled_exception() {}
diff --git a/clang/test/SemaCXX/coroutine-allocs.cpp b/clang/test/SemaCXX/coroutine-allocs.cpp
index e6b086bd1c720..c267367baff66 100644
--- a/clang/test/SemaCXX/coroutine-allocs.cpp
+++ b/clang/test/SemaCXX/coroutine-allocs.cpp
@@ -12,7 +12,7 @@ struct resumable {
     void *operator new(std::size_t sz, Allocator &);
 
     resumable get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
@@ -71,7 +71,7 @@ struct promise_base2 {
 struct resumable2 {
   struct promise_type : public promise_base1, public promise_base2 {
     resumable2 get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
diff --git a/clang/test/SemaCXX/coroutine-dealloc.cpp b/clang/test/SemaCXX/coroutine-dealloc.cpp
index 762a14465b297..b538041781f34 100644
--- a/clang/test/SemaCXX/coroutine-dealloc.cpp
+++ b/clang/test/SemaCXX/coroutine-dealloc.cpp
@@ -11,7 +11,7 @@ namespace std {
 
 struct task {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task{}; }
     void unhandled_exception() {}
@@ -30,7 +30,7 @@ task f() {
 struct generator {
     struct promise_type {
         generator get_return_object();
-        std::suspend_always initial_suspend();
+        std::suspend_always initial_suspend() noexcept;
         std::suspend_always final_suspend() noexcept;
         void return_void();
         [[noreturn]] void unhandled_exception();
diff --git a/clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp b/clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp
index 35c00b84ea398..744baec43eeea 100644
--- a/clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp
+++ b/clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp
@@ -20,6 +20,12 @@ struct coroutine_handle<void> {
   void *address() const noexcept;
 };
 
+struct suspend_always_noexcept {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(coroutine_handle<>) noexcept {}
+  void await_resume() noexcept {}
+};
+
 struct suspend_always {
   bool await_ready() { return false; }      // expected-note 2 {{must be declared with 'noexcept'}}
   void await_suspend(coroutine_handle<>) {} // expected-note 2 {{must be declared with 'noexcept'}}
@@ -41,7 +47,7 @@ struct A {
 struct coro_t {
   struct promise_type {
     coro_t get_return_object();
-    suspend_always initial_suspend();
+    suspend_always_noexcept initial_suspend() noexcept;
     suspend_always final_suspend(); // expected-note 2 {{must be declared with 'noexcept'}}
     void return_void();
     static void unhandled_exception();
@@ -72,7 +78,7 @@ struct PositiveFinalSuspend {
 struct correct_coro {
   struct promise_type {
     correct_coro get_return_object();
-    suspend_always initial_suspend();
+    suspend_always_noexcept initial_suspend() noexcept;
     PositiveFinalSuspend final_suspend() noexcept;
     void return_void();
     static void unhandled_exception();
@@ -93,7 +99,7 @@ struct NegativeFinalSuspend {
 struct incorrect_coro {
   struct promise_type {
     incorrect_coro get_return_object();
-    suspend_always initial_suspend();
+    suspend_always_noexcept initial_suspend() noexcept;
     NegativeFinalSuspend final_suspend() noexcept;
     void return_void();
     static void unhandled_exception();
diff --git a/clang/test/SemaCXX/coroutine-no-move-ctor.cpp b/clang/test/SemaCXX/coroutine-no-move-ctor.cpp
index 824dea375ebde..813585e45b43b 100644
--- a/clang/test/SemaCXX/coroutine-no-move-ctor.cpp
+++ b/clang/test/SemaCXX/coroutine-no-move-ctor.cpp
@@ -8,7 +8,7 @@ class invoker {
   class invoker_promise {
   public:
     invoker get_return_object() { return invoker{}; }
-    auto initial_suspend() { return std::suspend_never{}; }
+    auto initial_suspend() noexcept { return std::suspend_never{}; }
     auto final_suspend() noexcept { return std::suspend_never{}; }
     void return_void() {}
     void unhandled_exception() {}
diff --git a/clang/test/SemaCXX/coroutine-no-valid-dealloc.cpp b/clang/test/SemaCXX/coroutine-no-valid-dealloc.cpp
index a5727d6f003bf..e393a47bc3a1e 100644
--- a/clang/test/SemaCXX/coroutine-no-valid-dealloc.cpp
+++ b/clang/test/SemaCXX/coroutine-no-valid-dealloc.cpp
@@ -11,7 +11,7 @@ namespace std {
 
 struct task {
   struct promise_type {
-    auto initial_suspend() { return std::suspend_always{}; }
+    auto initial_suspend() noexcept { return std::suspend_always{}; }
     auto final_suspend() noexcept { return std::suspend_always{}; }
     auto get_return_object() { return task{}; }
     void unhandled_exception() {}
diff --git a/clang/test/SemaCXX/coroutine-rvo.cpp b/clang/test/SemaCXX/coroutine-rvo.cpp
index 6bf1dee67557c..e31839175b2a1 100644
--- a/clang/test/SemaCXX/coroutine-rvo.cpp
+++ b/clang/test/SemaCXX/coroutine-rvo.cpp
@@ -52,7 +52,7 @@ struct NoCopyNoMove {
 template <typename T>
 struct task {
   struct promise_type {
-    auto initial_suspend() { return suspend_never{}; }
+    auto initial_suspend() noexcept { return suspend_never{}; }
     auto final_suspend() noexcept { return suspend_never{}; }
     auto get_return_object() { return task{}; }
     static void unhandled_exception() {}
@@ -128,7 +128,7 @@ struct is_same<T, T> { static constexpr bool value = true; };
 template <typename T>
 struct generic_task {
   struct promise_type {
-    auto initial_suspend() { return suspend_never{}; }
+    auto initial_suspend() noexcept { return suspend_never{}; }
     auto final_suspend() noexcept { return suspend_never{}; }
     auto get_return_object() { return generic_task{}; }
     static void unhandled_exception();
diff --git a/clang/test/SemaCXX/coroutine-unevaluate.cpp b/clang/test/SemaCXX/coroutine-unevaluate.cpp
index 164caed2836a1..61d65dc610974 100644
--- a/clang/test/SemaCXX/coroutine-unevaluate.cpp
+++ b/clang/test/SemaCXX/coroutine-unevaluate.cpp
@@ -4,7 +4,7 @@
 struct MyTask{
   struct promise_type {
     MyTask get_return_object();
-    std::suspend_always initial_suspend() { return {}; }
+    std::suspend_always initial_suspend() noexcept { return {}; }
 
     void unhandled_exception();
     void return_void();
diff --git a/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp b/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp
index 5ea1e5d672442..83b33bbcaf208 100644
--- a/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp
+++ b/clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp
@@ -22,7 +22,7 @@ struct promise_void { // expected-note {{defined here}}
 struct promise_void {
 #endif
   void get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
 };
diff --git a/clang/test/SemaCXX/coroutine-unreachable-warning.cpp b/clang/test/SemaCXX/coroutine-unreachable-warning.cpp
index 35890ee83a1be..f7e87fb35db77 100644
--- a/clang/test/SemaCXX/coroutine-unreachable-warning.cpp
+++ b/clang/test/SemaCXX/coroutine-unreachable-warning.cpp
@@ -6,7 +6,7 @@ extern void abort(void) __attribute__((__noreturn__));
 
 struct task {
   struct promise_type {
-    std::suspend_always initial_suspend();
+    std::suspend_always initial_suspend() noexcept;
     std::suspend_always final_suspend() noexcept;
     void return_void();
     std::suspend_always yield_value(int) { return {}; }
diff --git a/clang/test/SemaCXX/coroutine_handle-address-return-type.cpp b/clang/test/SemaCXX/coroutine_handle-address-return-type.cpp
index 884ff3680e1a1..f90769de7de8c 100644
--- a/clang/test/SemaCXX/coroutine_handle-address-return-type.cpp
+++ b/clang/test/SemaCXX/coroutine_handle-address-return-type.cpp
@@ -42,7 +42,7 @@ struct suspend_never {
 
 struct task {
   struct promise_type {
-    auto initial_suspend() { return suspend_never{}; }
+    auto initial_suspend() noexcept { return suspend_never{}; }
     auto final_suspend() noexcept { return suspend_never{}; }
     auto get_return_object() { return task{}; }
     static void unhandled_exception() {}
diff --git a/clang/test/SemaCXX/coroutine_initial_suspend_exception_waning.cpp b/clang/test/SemaCXX/coroutine_initial_suspend_exception_waning.cpp
new file mode 100644
index 0000000000000..62ecdf53dd7fb
--- /dev/null
+++ b/clang/test/SemaCXX/coroutine_initial_suspend_exception_waning.cpp
@@ -0,0 +1,62 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s -fcxx-exceptions -fexceptions -Wunused-result
+
+namespace std {
+
+template <class Ret, typename... T>
+struct coroutine_traits { using promise_type = typename Ret::promise_type; };
+
+template <class Promise = void>
+struct coroutine_handle {
+  static coroutine_handle from_address(void *);
+  void *address() const noexcept;
+};
+template <>
+struct coroutine_handle<void> {
+  template <class PromiseType>
+  coroutine_handle(coroutine_handle<PromiseType>);
+  void *address() const noexcept;
+};
+
+struct suspend_always {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(coroutine_handle<>) noexcept {}
+  void await_resume() noexcept {}
+};
+
+struct suspend_always_throws {
+  bool await_ready() { return false; } // expected-note 1 {{must be declared with 'noexcept'}}
+  void await_suspend(coroutine_handle<>) {} // expected-note 1 {{must be declared with 'noexcept'}}
+  void await_resume() {} // expected-note 1 {{must be declared with 'noexcept'}}
+};
+
+} // namespace std
+
+using namespace std;
+
+struct coro_t_1 {
+  struct promise_type {
+    coro_t_1 get_return_object();
+    suspend_always initial_suspend();  // expected-note 1 {{must be declared with 'noexcept'}}
+    suspend_always final_suspend() noexcept;
+    void return_void();
+    static void unhandled_exception();
+  };
+};
+
+coro_t_1 f1() { // expected-warning {{a potentially throwing 'co_await __promise.initial_suspend()' may disable heap allocation elision; if it throws, the coroutine return value and state are destroyed in the reverse order of their construction}}
+  co_return;
+}
+
+struct coro_t_2 {
+  struct promise_type {
+    coro_t_2 get_return_object();
+    suspend_always_throws initial_suspend() noexcept;
+    suspend_always final_suspend() noexcept;
+    void return_void();
+    static void unhandled_exception();
+  };
+};
+
+coro_t_2 f2() { // expected-warning {{a potentially throwing 'co_await __promise.initial_suspend()' may disable heap allocation elision; if it throws, the coroutine return value and state are destroyed in the reverse order of their construction}}
+  co_return;
+}
diff --git a/clang/test/SemaCXX/coroutines.cpp b/clang/test/SemaCXX/coroutines.cpp
index 098c1c21a5962..4fec0e7fd99f5 100644
--- a/clang/test/SemaCXX/coroutines.cpp
+++ b/clang/test/SemaCXX/coroutines.cpp
@@ -128,7 +128,7 @@ struct not_awaitable {};
 
 struct promise {
   void get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   awaitable yield_value(int); // expected-note 2{{candidate}}
   awaitable yield_value(yielded_thing); // expected-note 2{{candidate}}
@@ -139,7 +139,7 @@ struct promise {
 
 struct promise_void {
   void get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -511,9 +511,9 @@ namespace dependent_operator_co_await_lookup {
   struct transform_promise {
     typedef transform_awaitable await_arg;
     coro<transform_promise> get_return_object();
-    transformed initial_suspend();
+    transformed initial_suspend() noexcept;
     ::adl_ns::coawait_arg_type final_suspend() noexcept;
-    transformed await_transform(transform_awaitable);
+    transformed await_transform(transform_awaitable) noexcept;
     void unhandled_exception();
     void return_void();
   };
@@ -521,7 +521,7 @@ namespace dependent_operator_co_await_lookup {
   struct basic_promise {
     typedef AwaitArg await_arg;
     coro<basic_promise> get_return_object();
-    awaitable initial_suspend();
+    awaitable initial_suspend() noexcept;
     awaitable final_suspend() noexcept;
     void unhandled_exception();
     void return_void();
@@ -576,7 +576,7 @@ namespace dependent_operator_co_await_lookup {
   }
 
   void operator co_await(transform_awaitable) = delete;
-  awaitable operator co_await(transformed);
+  awaitable operator co_await(transformed) noexcept;
 
   template coro<transform_promise>
       dependent_member<long>::dep_mem_fn<transform_promise>(transform_awaitable);
@@ -606,7 +606,7 @@ struct std::coroutine_traits<void, yield_fn_tag> {
     awaitable yield_value(int());
     void return_value(int());
 
-    suspend_never initial_suspend();
+    suspend_never initial_suspend() noexcept;
     suspend_never final_suspend() noexcept;
     void get_return_object();
     void unhandled_exception();
@@ -640,7 +640,7 @@ namespace placeholder {
 }
 
 struct bad_promise_1 {
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   void return_void();
@@ -662,7 +662,7 @@ coro<bad_promise_2> missing_initial_suspend() { // expected-error {{no member na
 
 struct bad_promise_3 {
   coro<bad_promise_3> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   void unhandled_exception();
   void return_void();
 };
@@ -672,7 +672,7 @@ coro<bad_promise_3> missing_final_suspend() noexcept { // expected-error {{no me
 
 struct bad_promise_4 {
   coro<bad_promise_4> get_return_object();
-  not_awaitable initial_suspend();
+  not_awaitable initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
 };
@@ -684,7 +684,7 @@ coro<bad_promise_4> bad_initial_suspend() { // expected-error {{no member named
 
 struct bad_promise_5 {
   coro<bad_promise_5> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   not_awaitable final_suspend() noexcept;
   void return_void();
 };
@@ -696,7 +696,7 @@ coro<bad_promise_5> bad_final_suspend() { // expected-error {{no member named 'a
 
 struct bad_promise_6 {
   coro<bad_promise_6> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   void return_void();           // expected-note 2 {{member 'return_void' first declared here}}
@@ -715,7 +715,7 @@ template coro<bad_promise_6> bad_implicit_return_dependent(bad_promise_6); // ex
 
 struct bad_promise_7 { // expected-note 2 {{defined here}}
   coro<bad_promise_7> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
 };
@@ -735,7 +735,7 @@ struct bad_promise_base {
 };
 struct bad_promise_8 : bad_promise_base {
   coro<bad_promise_8> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception() __attribute__((unavailable)); // expected-note 2 {{marked unavailable here}}
   void unhandled_exception() const;
@@ -757,7 +757,7 @@ template coro<bad_promise_8> calls_unhandled_exception_dependent(bad_promise_8);
 
 struct bad_promise_9 {
   coro<bad_promise_9> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void await_transform(void *);
   awaitable await_transform(int) __attribute__((unavailable)); // expected-note {{explicitly marked unavailable}}
@@ -770,7 +770,7 @@ coro<bad_promise_9> calls_await_transform() {
 
 struct bad_promise_10 {
   coro<bad_promise_10> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   int await_transform;
   void return_void();
@@ -789,7 +789,7 @@ struct call_operator {
 void ret_void();
 struct good_promise_1 {
   coro<good_promise_1> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   static const call_operator await_transform;
@@ -826,7 +826,7 @@ int main(int, const char**) {
 
 struct good_promise_2 {
   float get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -858,7 +858,7 @@ template <>
 struct std::coroutine_traits<int, promise_on_alloc_failure_tag> {
   struct promise_type {
     int get_return_object() {}
-    suspend_always initial_suspend() { return {}; }
+    suspend_always initial_suspend() noexcept { return {}; }
     suspend_always final_suspend() noexcept { return {}; }
     void return_void() {}
     int get_return_object_on_allocation_failure(); // expected-error{{'promise_type': 'get_return_object_on_allocation_failure()' must be a static member function}}
@@ -872,7 +872,7 @@ extern "C" int f(promise_on_alloc_failure_tag) {
 
 struct bad_promise_11 {
   coro<bad_promise_11> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   void return_void();
@@ -895,7 +895,7 @@ template coro<bad_promise_11> dependent_private_alloc_failure_handler(bad_promis
 
 struct bad_promise_12 {
   coro<bad_promise_12> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   void return_void();
@@ -917,7 +917,7 @@ template coro<bad_promise_12> dependent_throwing_in_class_new(bad_promise_12); /
 
 struct good_promise_13 {
   coro<good_promise_13> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
   void return_void();
@@ -935,7 +935,7 @@ 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 initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -951,7 +951,7 @@ 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 initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -961,7 +961,7 @@ struct good_promise_nonstatic_member_custom_new_operator {
 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 initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -978,7 +978,7 @@ template <>
 struct std::coroutine_traits<int, mismatch_gro_type_tag1> {
   struct promise_type {
     void get_return_object() {} //expected-note {{member 'get_return_object' declared here}}
-    suspend_always initial_suspend() { return {}; }
+    suspend_always initial_suspend() noexcept { return {}; }
     suspend_always final_suspend() noexcept { return {}; }
     void return_void() {}
     void unhandled_exception();
@@ -995,7 +995,7 @@ template <>
 struct std::coroutine_traits<int, mismatch_gro_type_tag2> {
   struct promise_type {
     void *get_return_object() {} //expected-note {{member 'get_return_object' declared here}}
-    suspend_always initial_suspend() { return {}; }
+    suspend_always initial_suspend() noexcept { return {}; }
     suspend_always final_suspend() noexcept { return {}; }
     void return_void() {}
     void unhandled_exception();
@@ -1014,7 +1014,7 @@ struct std::coroutine_traits<int, mismatch_gro_type_tag3> {
   struct promise_type {
     int get_return_object() {}
     static void get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared here}}
-    suspend_always initial_suspend() { return {}; }
+    suspend_always initial_suspend() noexcept { return {}; }
     suspend_always final_suspend() noexcept { return {}; }
     void return_void() {}
     void unhandled_exception();
@@ -1033,7 +1033,7 @@ struct std::coroutine_traits<int, mismatch_gro_type_tag4> {
   struct promise_type {
     int get_return_object() {}
     static char *get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared}}
-    suspend_always initial_suspend() { return {}; }
+    suspend_always initial_suspend() noexcept { return {}; }
     suspend_always final_suspend() noexcept { return {}; }
     void return_void() {}
     void unhandled_exception();
@@ -1047,7 +1047,7 @@ extern "C" int f(mismatch_gro_type_tag4) {
 
 struct promise_no_return_func {
   coro<promise_no_return_func> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void unhandled_exception();
 };
@@ -1177,7 +1177,7 @@ struct CoroMemberPromise {
   using AwaitTestT = AwaitReturnsType<TypeTestT>;
 
   CoroMemberTag get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
 
   AwaitTestT yield_value(int);
@@ -1388,7 +1388,7 @@ struct bad_promise_deleted_constructor {
   // expected-note at +1 {{'bad_promise_deleted_constructor' has been explicitly marked deleted here}}
   bad_promise_deleted_constructor() = delete;
   coro<bad_promise_deleted_constructor> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -1410,7 +1410,7 @@ struct good_promise_default_constructor {
   good_promise_default_constructor(double, float, int);
   good_promise_default_constructor() = default;
   coro<good_promise_default_constructor> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -1428,7 +1428,7 @@ struct good_promise_custom_constructor {
   good_promise_custom_constructor(double, float, int);
   good_promise_custom_constructor() = delete;
   coro<good_promise_custom_constructor> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -1455,7 +1455,7 @@ struct bad_promise_no_matching_constructor {
   // expected-note at +1 2 {{'bad_promise_no_matching_constructor' has been explicitly marked deleted here}}
   bad_promise_no_matching_constructor() = delete;
   coro<bad_promise_no_matching_constructor> get_return_object();
-  suspend_always initial_suspend();
+  suspend_always initial_suspend() noexcept;
   suspend_always final_suspend() noexcept;
   void return_void();
   void unhandled_exception();
@@ -1497,7 +1497,7 @@ class awaitable_unused_warn {
 template <class Await>
 struct check_warning_promise {
   coro<check_warning_promise> get_return_object();
-  Await initial_suspend();
+  Await initial_suspend() noexcept;
   Await final_suspend() noexcept;
   Await yield_value(int);
   void return_void();
diff --git a/clang/test/SemaCXX/cxx2b-deducing-this-coro.cpp b/clang/test/SemaCXX/cxx2b-deducing-this-coro.cpp
index dfa50cb75acfa..a5de082c70e8b 100644
--- a/clang/test/SemaCXX/cxx2b-deducing-this-coro.cpp
+++ b/clang/test/SemaCXX/cxx2b-deducing-this-coro.cpp
@@ -12,7 +12,7 @@ class coro_test {
         promise_type(const promise_type&) = delete; // #copy-ctr
         promise_type(T);  // #candidate
         coro_test get_return_object();
-        std::suspend_never initial_suspend();
+        std::suspend_never initial_suspend() noexcept;
 	    std::suspend_never final_suspend() noexcept;
 	    void return_void();
         void unhandled_exception();
diff --git a/clang/test/SemaCXX/type-aware-coroutines.cpp b/clang/test/SemaCXX/type-aware-coroutines.cpp
index e41d07b9bccf5..1f2da2cdeb8dc 100644
--- a/clang/test/SemaCXX/type-aware-coroutines.cpp
+++ b/clang/test/SemaCXX/type-aware-coroutines.cpp
@@ -21,7 +21,7 @@ struct resumable {
     template <typename T> void operator delete(std::type_identity<T>, void *, std::size_t sz, std::align_val_t) = delete; // #resumable_tad2
 
     resumable get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
@@ -35,7 +35,7 @@ struct resumable2 {
     void operator delete(std::type_identity<promise_type>, void *, std::size_t sz, std::align_val_t); // #resumable2_tad2
 
     resumable2 get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
@@ -53,7 +53,7 @@ struct resumable3 {
     void operator delete(void *);
 
     resumable3 get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
@@ -68,7 +68,7 @@ struct resumable4 {
     template <typename T> void operator delete(std::type_identity<T>, void *, std::size_t, std::align_val_t); // #resumable4_tad
 
     resumable4 get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
@@ -84,7 +84,7 @@ struct resumable5 {
     template <typename T> void operator delete(std::type_identity<T>, void *, std::size_t, std::align_val_t); // #resumable5_tad
 
     resumable5 get_return_object() { return {}; }
-    auto initial_suspend() { return std::suspend_always(); }
+    auto initial_suspend() noexcept { return std::suspend_always(); }
     auto final_suspend() noexcept { return std::suspend_always(); }
     void unhandled_exception() {}
     void return_void(){};
diff --git a/clang/test/SemaCXX/warn-unsequenced-coro.cpp b/clang/test/SemaCXX/warn-unsequenced-coro.cpp
index 56d2edcf30155..e3544795deb47 100644
--- a/clang/test/SemaCXX/warn-unsequenced-coro.cpp
+++ b/clang/test/SemaCXX/warn-unsequenced-coro.cpp
@@ -44,7 +44,7 @@ class generator
   struct Promise
   {
     auto get_return_object() { return generator{*this}; }
-    auto initial_suspend() { return suspend_never{}; }
+    auto initial_suspend() noexcept { return suspend_never{}; }
     auto final_suspend() noexcept { return suspend_always{}; }
     void unhandled_exception() {}
     void return_void() {}

>From f124be932d1fc9da6a1192346c4c2b7dc4838b88 Mon Sep 17 00:00:00 2001
From: Yexuan Xiao <bizwen at nykz.org>
Date: Fri, 30 Jan 2026 21:54:23 +0800
Subject: [PATCH 5/7] Add a test for CGCoroutines-cwg2935

---
 clang/test/CodeGenCoroutines/cwg2935.cpp | 32 ++++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 clang/test/CodeGenCoroutines/cwg2935.cpp

diff --git a/clang/test/CodeGenCoroutines/cwg2935.cpp b/clang/test/CodeGenCoroutines/cwg2935.cpp
new file mode 100644
index 0000000000000..6a0d06524720e
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/cwg2935.cpp
@@ -0,0 +1,32 @@
+// Verifies lifetime of __gro local variable
+// Verify that coroutine promise and allocated memory are freed up on exception.
+// RUN: %clang_cc1 -std=c++20 -Wno-initial-suspend-throw -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes | FileCheck %s
+
+#include "Inputs/coroutine.h"
+
+using namespace std;
+
+struct task {
+  struct promise_type {
+    task get_return_object();
+    suspend_always initial_suspend();
+    suspend_always final_suspend() noexcept;
+    void return_void();
+    void unhandled_exception();
+  };
+  ~task();
+};
+
+task f() {
+  co_return;
+}
+
+// CHECK:       coro.init:                                        ; preds = %coro.alloc, %entry
+// CHECK-NEXT:    %3 = phi ptr [ null, %entry ], [ %call, %coro.alloc ]
+// CHECK-NEXT:    %4 = call ptr @llvm.coro.begin(token %0, ptr %3)
+// CHECK-NEXT:    call void @llvm.lifetime.start.p{{.*}}(ptr %__promise) #2
+// CHECK-NEXT:    call void @_ZN4task12promise_type17get_return_objectEv(ptr dead_on_unwind writable sret(%struct.task) align 1 %agg.result, ptr noundef nonnull align 1 dereferenceable(1) %__promise)
+// CHECK-NEXT:    store i1 true, ptr %direct.gro.active, align 1
+// CHECK:       cleanup.action:                                   ; preds = %cleanup{{.*}}
+// CHECK-NEXT:    call void @_ZN4taskD1Ev(ptr noundef nonnull align 1 dereferenceable(1) %agg.result) #2
+// CHECK-NEXT:    br label %cleanup.done
\ No newline at end of file

>From 43fa20dc2c4c8015febbea55ab69b1b08492de62 Mon Sep 17 00:00:00 2001
From: Yexuan Xiao <bizwen at nykz.org>
Date: Fri, 30 Jan 2026 22:56:52 +0800
Subject: [PATCH 6/7] add tests for non-direct-emit

---
 coroutine-cwg2935.cpp | 309 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 290 insertions(+), 19 deletions(-)

diff --git a/coroutine-cwg2935.cpp b/coroutine-cwg2935.cpp
index 38a7a560c5d8d..6f18c7dae1a28 100644
--- a/coroutine-cwg2935.cpp
+++ b/coroutine-cwg2935.cpp
@@ -67,6 +67,21 @@ void operator delete(void *ptr) noexcept {
   std::free(ptr);
 }
 
+struct copy_observer {
+private:
+  copy_observer() = default;
+
+public:
+  copy_observer(copy_observer const &) { throw_c(para_copy_ctor); }
+  ~copy_observer() { record(para_dtor); }
+
+  static copy_observer get() { return {}; }
+};
+
+const auto global_observer = copy_observer::get();
+
+namespace direct_emit {
+
 struct task {
   task() { throw_c(task_ctor); }
   ~task() { record(task_dtor); }
@@ -117,20 +132,6 @@ struct task {
   };
 };
 
-struct copy_observer {
-private:
-  copy_observer() = default;
-
-public:
-  copy_observer(copy_observer const &) { throw_c(para_copy_ctor); }
-  ~copy_observer() { record(para_dtor); }
-
-  static copy_observer get() { return {}; }
-};
-
-//
-const auto global_observer = copy_observer::get();
-
 task coro(copy_observer) { co_return; }
 
 void catch_coro() try { coro(global_observer); } catch (...) {
@@ -138,7 +139,7 @@ void catch_coro() try { coro(global_observer); } catch (...) {
 
 // Currently, the conditions at the eight potential exception-throwing points
 // need to be checked. More checkpoints can be added after CWG2934 is resolved.
-int main() {
+void test() {
   per_test_counts_type e{};
   allocate_count = 0;
   catch_coro();
@@ -246,9 +247,9 @@ int main() {
   };
   assert(e ==
          per_test_counts); // Clang currently fails starting from this line. If
-                           // the code you modified causes tests above this line
-                           // to fail, it indicates that you have broken the
-                           // correct code and should start over from scratch.
+  // the code you modified causes tests above this line
+  // to fail, it indicates that you have broken the
+  // correct code and should start over from scratch.
   clear();
   catch_coro();
   e = {
@@ -338,4 +339,274 @@ int main() {
   assert(e == per_test_counts);
   clear();
   assert(allocate_count == 0);
-}
\ No newline at end of file
+}
+
+} // namespace direct_emit
+
+namespace no_direct_emit {
+
+struct gro_tag_t {};
+
+struct task {
+  task(gro_tag_t) { throw_c(task_ctor); }
+  ~task() { record(task_dtor); }
+  // In this test, the task should be constructed directly, without copy elision
+  task(task const &) = delete;
+  struct promise_type {
+    promise_type() { throw_c(promise_ctor); }
+    ~promise_type() { record(promise_dtor); }
+    promise_type(const promise_type &) = delete;
+    gro_tag_t get_return_object() {
+      throw_c(get_return_obj);
+      return {};
+    }
+    auto initial_suspend() {
+      throw_c(init_suspend);
+      struct initial_awaiter {
+        initial_awaiter() { throw_c(awaiter_ctor); }
+        ~initial_awaiter() { record(awaiter_dtor); }
+        initial_awaiter(const initial_awaiter &) = delete;
+        bool await_ready() {
+          throw_c(init_a_ready);
+          return false;
+        }
+        bool await_suspend(std::coroutine_handle<void>) {
+          throw_c(init_a_suspend);
+          return false;
+        }
+        void await_resume() {
+          // From this point onward, all exceptions are handled by
+          // `unhandled_exception` Since the defect of exceptions escaping from
+          // `unhandled_exception` remains unresolved (CWG2934), this test only
+          // covers the coroutine startup phase. Once CWG2934 is resolved,
+          // further tests can be added based on this one.
+          record(init_a_resume);
+        }
+      };
+
+      return initial_awaiter{};
+    }
+    void return_void() { record(return_v); }
+    void unhandled_exception() { record(unhandled); }
+    // Note that no exceptions may leak after final_suspend is called, otherwise
+    // the behavior is undefined
+    std::suspend_never final_suspend() noexcept {
+      record(fin_suspend);
+      return {};
+    }
+  };
+};
+
+task coro(copy_observer) { co_return; }
+
+void catch_coro() try { coro(global_observer); } catch (...) {
+}
+
+// Currently, the conditions at the eight potential exception-throwing points
+// need to be checked. More checkpoints can be added after CWG2934 is resolved.
+void test() {
+  per_test_counts_type e{};
+  allocate_count = 0;
+  catch_coro();
+  e = {
+      1, // para_copy_ctor
+      0, // para_dtor
+      0, // promise_ctor
+      0, // promise_dtor
+      0, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      0, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      0, // promise_dtor
+      0, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      0, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      0, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      1, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      0, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      1, // init_suspend
+      0, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      1, // awaiter_ctor
+      0, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      1, // init_suspend
+      1, // init_a_ready
+      0, // init_a_suspend
+      0, // init_a_resume
+      1, // awaiter_ctor
+      1, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      0, // task_ctor
+      0, // task_dtor
+      1, // init_suspend
+      1, // init_a_ready
+      1, // init_a_suspend
+      0, // init_a_resume
+      1, // awaiter_ctor
+      1, // awaiter_dtor
+      0, // return_v,
+      0, // unhandled,
+      0, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  catch_coro();
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      0, // task_dtor
+      1, // init_suspend
+      1, // init_a_ready
+      1, // init_a_suspend
+      1, // init_a_resume
+      1, // awaiter_ctor
+      1, // awaiter_dtor
+      1, // return_v,
+      0, // unhandled,
+      1, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  // Test for execution without exceptions
+  {
+    coro(global_observer);
+  }
+  e = {
+      2, // para_copy_ctor
+      2, // para_dtor
+      1, // promise_ctor
+      1, // promise_dtor
+      1, // get_return_obj
+      1, // task_ctor
+      1, // task_dtor
+      1, // init_suspend
+      1, // init_a_ready
+      1, // init_a_suspend
+      1, // init_a_resume
+      1, // awaiter_ctor
+      1, // awaiter_dtor
+      1, // return_v,
+      0, // unhandled,
+      1, // fin_suspend
+  };
+  assert(e == per_test_counts);
+  clear();
+  assert(allocate_count == 0);
+}
+
+} // namespace no_direct_emit
+
+int main() {
+  direct_emit::test();
+  // clear the global state that records the already thrown points
+  checked_cond = {};
+  no_direct_emit::test();
+}

>From 45f1c48bcd4cebbd8cf4215efa94c6f193af34a2 Mon Sep 17 00:00:00 2001
From: Yexuan Xiao <bizwen at nykz.org>
Date: Sat, 31 Jan 2026 00:15:58 +0800
Subject: [PATCH 7/7] Suppress warnings in libc++ tests

---
 .../coroutine.handle/coroutine.handle.prom/promise.pass.cpp     | 2 +-
 .../support.coroutines/end.to.end/await_result.pass.cpp         | 2 +-
 .../support.coroutines/end.to.end/bool_await_suspend.pass.cpp   | 2 +-
 .../support.coroutines/end.to.end/expected.pass.cpp             | 2 +-
 .../support.coroutines/end.to.end/fullexpr-dtor.pass.cpp        | 2 +-
 .../support.coroutines/end.to.end/generator.pass.cpp            | 2 +-
 .../language.support/support.coroutines/end.to.end/go.pass.cpp  | 2 +-
 7 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/libcxx/test/std/language.support/support.coroutines/coroutine.handle/coroutine.handle.prom/promise.pass.cpp b/libcxx/test/std/language.support/support.coroutines/coroutine.handle/coroutine.handle.prom/promise.pass.cpp
index 402d196d95a50..d244cf6f5dda8 100644
--- a/libcxx/test/std/language.support/support.coroutines/coroutine.handle/coroutine.handle.prom/promise.pass.cpp
+++ b/libcxx/test/std/language.support/support.coroutines/coroutine.handle/coroutine.handle.prom/promise.pass.cpp
@@ -28,7 +28,7 @@ struct MyCoro {
   struct promise_type {
     void unhandled_exception() {}
     void return_void() {}
-    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never initial_suspend() noexcept { return {}; }
     std::suspend_never final_suspend() noexcept { return {}; }
     MyCoro get_return_object() {
       do_runtime_test();
diff --git a/libcxx/test/std/language.support/support.coroutines/end.to.end/await_result.pass.cpp b/libcxx/test/std/language.support/support.coroutines/end.to.end/await_result.pass.cpp
index de6d09011f528..a3782fbc73100 100644
--- a/libcxx/test/std/language.support/support.coroutines/end.to.end/await_result.pass.cpp
+++ b/libcxx/test/std/language.support/support.coroutines/end.to.end/await_result.pass.cpp
@@ -19,7 +19,7 @@ struct coro_t {
       std::coroutine_handle<promise_type>{};
       return {};
     }
-    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never initial_suspend() noexcept { return {}; }
     std::suspend_never final_suspend() noexcept { return {}; }
     void return_void() {}
     static void unhandled_exception() {}
diff --git a/libcxx/test/std/language.support/support.coroutines/end.to.end/bool_await_suspend.pass.cpp b/libcxx/test/std/language.support/support.coroutines/end.to.end/bool_await_suspend.pass.cpp
index 56622341dfc55..df43bc584a8a2 100644
--- a/libcxx/test/std/language.support/support.coroutines/end.to.end/bool_await_suspend.pass.cpp
+++ b/libcxx/test/std/language.support/support.coroutines/end.to.end/bool_await_suspend.pass.cpp
@@ -21,7 +21,7 @@ struct coro_t {
     coro_t get_return_object() {
       return std::coroutine_handle<promise_type>::from_promise(*this);
     }
-    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never initial_suspend() noexcept { return {}; }
     std::suspend_never final_suspend() noexcept { return {}; }
     void return_void() {}
     void unhandled_exception() {}
diff --git a/libcxx/test/std/language.support/support.coroutines/end.to.end/expected.pass.cpp b/libcxx/test/std/language.support/support.coroutines/end.to.end/expected.pass.cpp
index 3fa746fbae0e1..aa89294bd3653 100644
--- a/libcxx/test/std/language.support/support.coroutines/end.to.end/expected.pass.cpp
+++ b/libcxx/test/std/language.support/support.coroutines/end.to.end/expected.pass.cpp
@@ -34,7 +34,7 @@ struct expected {
   struct promise_type {
     std::shared_ptr<Data> data;
     expected get_return_object() { data = std::make_shared<Data>(); return {data}; }
-    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never initial_suspend() noexcept { return {}; }
     std::suspend_never final_suspend() noexcept { return {}; }
     void return_value(T v) { data->val = v; data->error = {}; }
     void unhandled_exception() {}
diff --git a/libcxx/test/std/language.support/support.coroutines/end.to.end/fullexpr-dtor.pass.cpp b/libcxx/test/std/language.support/support.coroutines/end.to.end/fullexpr-dtor.pass.cpp
index 93308e31512db..8fcd92c11820c 100644
--- a/libcxx/test/std/language.support/support.coroutines/end.to.end/fullexpr-dtor.pass.cpp
+++ b/libcxx/test/std/language.support/support.coroutines/end.to.end/fullexpr-dtor.pass.cpp
@@ -35,7 +35,7 @@ struct Bug {
 };
 struct coro2 {
   struct promise_type {
-    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never initial_suspend() noexcept { return {}; }
     std::suspend_never final_suspend() noexcept { return {}; }
     coro2 get_return_object() { return {}; }
     void return_void() {}
diff --git a/libcxx/test/std/language.support/support.coroutines/end.to.end/generator.pass.cpp b/libcxx/test/std/language.support/support.coroutines/end.to.end/generator.pass.cpp
index f3d400aa93269..f74e5cd1bb264 100644
--- a/libcxx/test/std/language.support/support.coroutines/end.to.end/generator.pass.cpp
+++ b/libcxx/test/std/language.support/support.coroutines/end.to.end/generator.pass.cpp
@@ -24,7 +24,7 @@ template <typename Ty> struct generator {
       this->current_value = value;
       return {};
     }
-    std::suspend_always initial_suspend() { return {}; }
+    std::suspend_always initial_suspend() noexcept { return {}; }
     std::suspend_always final_suspend() noexcept { return {}; }
     generator get_return_object() { return generator{this}; };
     void return_void() {}
diff --git a/libcxx/test/std/language.support/support.coroutines/end.to.end/go.pass.cpp b/libcxx/test/std/language.support/support.coroutines/end.to.end/go.pass.cpp
index 434858d87bb4f..9616149ff64f3 100644
--- a/libcxx/test/std/language.support/support.coroutines/end.to.end/go.pass.cpp
+++ b/libcxx/test/std/language.support/support.coroutines/end.to.end/go.pass.cpp
@@ -42,7 +42,7 @@ struct goroutine
 
   struct promise_type
   {
-    std::suspend_never initial_suspend() {
+    std::suspend_never initial_suspend() noexcept {
       return {};
     }
     std::suspend_never final_suspend() noexcept { return {}; }



More information about the cfe-commits mailing list