[llvm] [Coroutines] Implement elision for noop coroutines (PR #174353)

Weibo He via llvm-commits llvm-commits at lists.llvm.org
Sun Jan 11 18:05:36 PST 2026


https://github.com/NewSigma updated https://github.com/llvm/llvm-project/pull/174353

>From 9e87f3fdef37c5a70301c7522f1df741563b10ae Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Mon, 5 Jan 2026 11:09:39 +0800
Subject: [PATCH 1/3] [Coroutines] Implement elision for noop coroutines

---
 llvm/docs/Coroutines.rst                      |   4 +-
 .../lib/Transforms/Coroutines/CoroCleanup.cpp |  75 +++++++-
 llvm/lib/Transforms/Coroutines/CoroEarly.cpp  | 172 ++++++------------
 llvm/lib/Transforms/Coroutines/CoroElide.cpp  | 105 +++++++----
 .../AddressSanitizer/skip-coro.ll             |   3 +-
 .../Transforms/Coroutines/coro-elide-noop.ll  |  13 ++
 .../Transforms/Coroutines/coro-noop-pacbti.ll |   2 +-
 llvm/test/Transforms/Coroutines/coro-noop.ll  |   4 +-
 8 files changed, 212 insertions(+), 166 deletions(-)
 create mode 100644 llvm/test/Transforms/Coroutines/coro-elide-noop.ll

diff --git a/llvm/docs/Coroutines.rst b/llvm/docs/Coroutines.rst
index 0e6b49c84acee..e6759f4edf353 100644
--- a/llvm/docs/Coroutines.rst
+++ b/llvm/docs/Coroutines.rst
@@ -2174,7 +2174,9 @@ allocation elision optimization. If so, it replaces
 `coro.begin` intrinsic with an address of a coroutine frame placed on its caller
 and replaces `coro.alloc` and `coro.free` intrinsics with `false` and `null`
 respectively to remove the deallocation code.
-This pass also replaces `coro.resume` and `coro.destroy` intrinsics with direct
+This pass also eliminates the resume and destroy operation on noop coroutines and
+attempts to erase unused `coro.noop` instructions.
+Finally, this pass replaces `coro.resume` and `coro.destroy` intrinsics with direct
 calls to resume and destroy functions for a particular coroutine where possible.
 
 CoroCleanup
diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index 81efca9dfd209..40aa820bf50c2 100644
--- a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/Transforms/Coroutines/CoroCleanup.h"
 #include "CoroInternal.h"
+#include "llvm/IR/DIBuilder.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/InstIterator.h"
@@ -23,8 +24,13 @@ namespace {
 // Created on demand if CoroCleanup pass has work to do.
 struct Lowerer : coro::LowererBase {
   IRBuilder<> Builder;
+  Constant *NoopCoro = nullptr;
+
   Lowerer(Module &M) : LowererBase(M), Builder(Context) {}
   bool lower(Function &F);
+
+private:
+  void lowerCoroNoop(IntrinsicInst *II);
 };
 }
 
@@ -43,6 +49,25 @@ static void lowerSubFn(IRBuilder<> &Builder, CoroSubFnInst *SubFn) {
   SubFn->replaceAllUsesWith(Load);
 }
 
+static void buildDebugInfoForNoopResumeDestroyFunc(Function *NoopFn) {
+  Module &M = *NoopFn->getParent();
+  if (M.debug_compile_units().empty())
+    return;
+
+  DICompileUnit *CU = *M.debug_compile_units_begin();
+  DIBuilder DB(M, /*AllowUnresolved*/ false, CU);
+  std::array<Metadata *, 2> Params{nullptr, nullptr};
+  auto *SubroutineType =
+      DB.createSubroutineType(DB.getOrCreateTypeArray(Params));
+  StringRef Name = NoopFn->getName();
+  auto *SP = DB.createFunction(
+      CU, /*Name=*/Name, /*LinkageName=*/Name, /*File=*/CU->getFile(),
+      /*LineNo=*/0, SubroutineType, /*ScopeLine=*/0, DINode::FlagArtificial,
+      DISubprogram::SPFlagDefinition);
+  NoopFn->setSubprogram(SP);
+  DB.finalize();
+}
+
 bool Lowerer::lower(Function &F) {
   bool IsPrivateAndUnprocessed = F.isPresplitCoroutine() && F.hasLocalLinkage();
   bool Changed = false;
@@ -72,6 +97,9 @@ bool Lowerer::lower(Function &F) {
       case Intrinsic::coro_id_async:
         II->replaceAllUsesWith(ConstantTokenNone::get(Context));
         break;
+      case Intrinsic::coro_noop:
+        lowerCoroNoop(II);
+        break;
       case Intrinsic::coro_subfn_addr:
         lowerSubFn(Builder, cast<CoroSubFnInst>(II));
         break;
@@ -108,13 +136,50 @@ bool Lowerer::lower(Function &F) {
   return Changed;
 }
 
+void Lowerer::lowerCoroNoop(IntrinsicInst *II) {
+  if (!NoopCoro) {
+    LLVMContext &C = Builder.getContext();
+    Module &M = *II->getModule();
+
+    // Create a noop.frame struct type.
+    auto *FnTy = FunctionType::get(Type::getVoidTy(C), Builder.getPtrTy(0),
+                                   /*isVarArg=*/false);
+    auto *FnPtrTy = Builder.getPtrTy(0);
+    StructType *FrameTy =
+        StructType::create({FnPtrTy, FnPtrTy}, "NoopCoro.Frame");
+
+    // Create a Noop function that does nothing.
+    Function *NoopFn = Function::createWithDefaultAttr(
+        FnTy, GlobalValue::LinkageTypes::InternalLinkage,
+        M.getDataLayout().getProgramAddressSpace(), "__NoopCoro_ResumeDestroy",
+        &M);
+    NoopFn->setCallingConv(CallingConv::Fast);
+    buildDebugInfoForNoopResumeDestroyFunc(NoopFn);
+    auto *Entry = BasicBlock::Create(C, "entry", NoopFn);
+    ReturnInst::Create(C, Entry);
+
+    // Create a constant struct for the frame.
+    Constant *Values[] = {NoopFn, NoopFn};
+    Constant *NoopCoroConst = ConstantStruct::get(FrameTy, Values);
+    NoopCoro = new GlobalVariable(
+        M, NoopCoroConst->getType(), /*isConstant=*/true,
+        GlobalVariable::PrivateLinkage, NoopCoroConst, "NoopCoro.Frame.Const");
+    cast<GlobalVariable>(NoopCoro)->setNoSanitizeMetadata();
+  }
+
+  Builder.SetInsertPoint(II);
+  auto *NoopCoroVoidPtr = Builder.CreateBitCast(NoopCoro, Int8Ptr);
+  II->replaceAllUsesWith(NoopCoroVoidPtr);
+}
+
 static bool declaresCoroCleanupIntrinsics(const Module &M) {
   return coro::declaresIntrinsics(
-      M, {Intrinsic::coro_alloc, Intrinsic::coro_begin,
-          Intrinsic::coro_subfn_addr, Intrinsic::coro_free, Intrinsic::coro_id,
-          Intrinsic::coro_id_retcon, Intrinsic::coro_id_async,
-          Intrinsic::coro_id_retcon_once, Intrinsic::coro_async_size_replace,
-          Intrinsic::coro_async_resume, Intrinsic::coro_begin_custom_abi});
+      M,
+      {Intrinsic::coro_alloc, Intrinsic::coro_begin, Intrinsic::coro_subfn_addr,
+       Intrinsic::coro_free, Intrinsic::coro_id, Intrinsic::coro_id_retcon,
+       Intrinsic::coro_id_async, Intrinsic::coro_id_retcon_once,
+       Intrinsic::coro_noop, Intrinsic::coro_async_size_replace,
+       Intrinsic::coro_async_resume, Intrinsic::coro_begin_custom_abi});
 }
 
 PreservedAnalyses CoroCleanupPass::run(Module &M,
diff --git a/llvm/lib/Transforms/Coroutines/CoroEarly.cpp b/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
index cdb58523d1e0e..39ee53f111d28 100644
--- a/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
@@ -8,7 +8,6 @@
 
 #include "llvm/Transforms/Coroutines/CoroEarly.h"
 #include "CoroInternal.h"
-#include "llvm/IR/DIBuilder.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/InstIterator.h"
@@ -24,12 +23,10 @@ namespace {
 class Lowerer : public coro::LowererBase {
   IRBuilder<> Builder;
   PointerType *const AnyResumeFnPtrTy;
-  Constant *NoopCoro = nullptr;
 
   void lowerResumeOrDestroy(CallBase &CB, CoroSubFnInst::ResumeKind);
   void lowerCoroPromise(CoroPromiseInst *Intrin);
   void lowerCoroDone(IntrinsicInst *II);
-  void lowerCoroNoop(IntrinsicInst *II);
   void hidePromiseAlloca(CoroIdInst *CoroId, CoroBeginInst *CoroBegin);
 
 public:
@@ -99,62 +96,6 @@ void Lowerer::lowerCoroDone(IntrinsicInst *II) {
   II->eraseFromParent();
 }
 
-static void buildDebugInfoForNoopResumeDestroyFunc(Function *NoopFn) {
-  Module &M = *NoopFn->getParent();
-  if (M.debug_compile_units().empty())
-     return;
-
-  DICompileUnit *CU = *M.debug_compile_units_begin();
-  DIBuilder DB(M, /*AllowUnresolved*/ false, CU);
-  std::array<Metadata *, 2> Params{nullptr, nullptr};
-  auto *SubroutineType =
-      DB.createSubroutineType(DB.getOrCreateTypeArray(Params));
-  StringRef Name = NoopFn->getName();
-  auto *SP = DB.createFunction(
-      CU, /*Name=*/Name, /*LinkageName=*/Name, /*File=*/ CU->getFile(),
-      /*LineNo=*/0, SubroutineType, /*ScopeLine=*/0, DINode::FlagArtificial,
-      DISubprogram::SPFlagDefinition);
-  NoopFn->setSubprogram(SP);
-  DB.finalize();
-}
-
-void Lowerer::lowerCoroNoop(IntrinsicInst *II) {
-  if (!NoopCoro) {
-    LLVMContext &C = Builder.getContext();
-    Module &M = *II->getModule();
-
-    // Create a noop.frame struct type.
-    auto *FnTy = FunctionType::get(Type::getVoidTy(C), Builder.getPtrTy(0),
-                                   /*isVarArg=*/false);
-    auto *FnPtrTy = Builder.getPtrTy(0);
-    StructType *FrameTy =
-        StructType::create({FnPtrTy, FnPtrTy}, "NoopCoro.Frame");
-
-    // Create a Noop function that does nothing.
-    Function *NoopFn = Function::createWithDefaultAttr(
-        FnTy, GlobalValue::LinkageTypes::InternalLinkage,
-        M.getDataLayout().getProgramAddressSpace(), "__NoopCoro_ResumeDestroy",
-        &M);
-    NoopFn->setCallingConv(CallingConv::Fast);
-    buildDebugInfoForNoopResumeDestroyFunc(NoopFn);
-    auto *Entry = BasicBlock::Create(C, "entry", NoopFn);
-    ReturnInst::Create(C, Entry);
-
-    // Create a constant struct for the frame.
-    Constant* Values[] = {NoopFn, NoopFn};
-    Constant* NoopCoroConst = ConstantStruct::get(FrameTy, Values);
-    NoopCoro = new GlobalVariable(M, NoopCoroConst->getType(), /*isConstant=*/true,
-                                GlobalVariable::PrivateLinkage, NoopCoroConst,
-                                "NoopCoro.Frame.Const");
-    cast<GlobalVariable>(NoopCoro)->setNoSanitizeMetadata();
-  }
-
-  Builder.SetInsertPoint(II);
-  auto *NoopCoroVoidPtr = Builder.CreateBitCast(NoopCoro, Int8Ptr);
-  II->replaceAllUsesWith(NoopCoroVoidPtr);
-  II->eraseFromParent();
-}
-
 // Later middle-end passes will assume promise alloca dead after coroutine
 // suspend, leading to misoptimizations. We hide promise alloca using
 // coro.promise and will lower it back to alloca at CoroSplit.
@@ -204,64 +145,61 @@ void Lowerer::lowerEarlyIntrinsics(Function &F) {
       continue;
 
     switch (CB->getIntrinsicID()) {
-      default:
-        continue;
-      case Intrinsic::coro_begin:
-      case Intrinsic::coro_begin_custom_abi:
-        if (CoroBegin)
-          report_fatal_error(
-              "coroutine should have exactly one defining @llvm.coro.begin");
-        CoroBegin = cast<CoroBeginInst>(&I);
-        break;
-      case Intrinsic::coro_free:
-        CoroFrees.push_back(cast<CoroFreeInst>(&I));
-        break;
-      case Intrinsic::coro_suspend:
-        // Make sure that final suspend point is not duplicated as CoroSplit
-        // pass expects that there is at most one final suspend point.
-        if (cast<CoroSuspendInst>(&I)->isFinal())
-          CB->setCannotDuplicate();
-        HasCoroSuspend = true;
-        break;
-      case Intrinsic::coro_end_async:
-      case Intrinsic::coro_end:
-        // Make sure that fallthrough coro.end is not duplicated as CoroSplit
-        // pass expects that there is at most one fallthrough coro.end.
-        if (cast<AnyCoroEndInst>(&I)->isFallthrough())
-          CB->setCannotDuplicate();
-        break;
-      case Intrinsic::coro_noop:
-        lowerCoroNoop(cast<IntrinsicInst>(&I));
-        break;
-      case Intrinsic::coro_id:
-        if (auto *CII = cast<CoroIdInst>(&I)) {
-          if (CII->getInfo().isPreSplit()) {
-            assert(F.isPresplitCoroutine() &&
-                   "The frontend uses Switch-Resumed ABI should emit "
-                   "\"presplitcoroutine\" attribute for the coroutine.");
-            setCannotDuplicate(CII);
-            CII->setCoroutineSelf();
-            CoroId = cast<CoroIdInst>(&I);
-          }
+    default:
+      continue;
+    case Intrinsic::coro_begin:
+    case Intrinsic::coro_begin_custom_abi:
+      if (CoroBegin)
+        report_fatal_error(
+            "coroutine should have exactly one defining @llvm.coro.begin");
+      CoroBegin = cast<CoroBeginInst>(&I);
+      break;
+    case Intrinsic::coro_free:
+      CoroFrees.push_back(cast<CoroFreeInst>(&I));
+      break;
+    case Intrinsic::coro_suspend:
+      // Make sure that final suspend point is not duplicated as CoroSplit
+      // pass expects that there is at most one final suspend point.
+      if (cast<CoroSuspendInst>(&I)->isFinal())
+        CB->setCannotDuplicate();
+      HasCoroSuspend = true;
+      break;
+    case Intrinsic::coro_end_async:
+    case Intrinsic::coro_end:
+      // Make sure that fallthrough coro.end is not duplicated as CoroSplit
+      // pass expects that there is at most one fallthrough coro.end.
+      if (cast<AnyCoroEndInst>(&I)->isFallthrough())
+        CB->setCannotDuplicate();
+      break;
+    case Intrinsic::coro_id:
+      if (auto *CII = cast<CoroIdInst>(&I)) {
+        if (CII->getInfo().isPreSplit()) {
+          assert(F.isPresplitCoroutine() &&
+                "The frontend uses Switch-Resumed ABI should emit "
+                "\"presplitcoroutine\" attribute for the coroutine.");
+          setCannotDuplicate(CII);
+          CII->setCoroutineSelf();
+          CoroId = cast<CoroIdInst>(&I);
         }
-        break;
-      case Intrinsic::coro_id_retcon:
-      case Intrinsic::coro_id_retcon_once:
-      case Intrinsic::coro_id_async:
-        F.setPresplitCoroutine();
-        break;
-      case Intrinsic::coro_resume:
-        lowerResumeOrDestroy(*CB, CoroSubFnInst::ResumeIndex);
-        break;
-      case Intrinsic::coro_destroy:
-        lowerResumeOrDestroy(*CB, CoroSubFnInst::DestroyIndex);
-        break;
-      case Intrinsic::coro_promise:
-        lowerCoroPromise(cast<CoroPromiseInst>(&I));
-        break;
-      case Intrinsic::coro_done:
-        lowerCoroDone(cast<IntrinsicInst>(&I));
-        break;
+      }
+      break;
+    case Intrinsic::coro_id_retcon:
+    case Intrinsic::coro_id_retcon_once:
+    case Intrinsic::coro_id_async:
+      F.setPresplitCoroutine();
+      break;
+    case Intrinsic::coro_resume:
+      lowerResumeOrDestroy(*CB, CoroSubFnInst::ResumeIndex);
+      break;
+    case Intrinsic::coro_destroy:
+      lowerResumeOrDestroy(*CB, CoroSubFnInst::DestroyIndex);
+      break;
+    case Intrinsic::coro_promise:
+      lowerCoroPromise(cast<CoroPromiseInst>(&I));
+      break;
+    case Intrinsic::coro_done:
+      lowerCoroDone(cast<IntrinsicInst>(&I));
+      break;
     }
   }
 
@@ -290,7 +228,7 @@ static bool declaresCoroEarlyIntrinsics(const Module &M) {
       M, {Intrinsic::coro_id, Intrinsic::coro_id_retcon,
           Intrinsic::coro_id_retcon_once, Intrinsic::coro_id_async,
           Intrinsic::coro_destroy, Intrinsic::coro_done, Intrinsic::coro_end,
-          Intrinsic::coro_end_async, Intrinsic::coro_noop, Intrinsic::coro_free,
+          Intrinsic::coro_end_async, Intrinsic::coro_free,
           Intrinsic::coro_promise, Intrinsic::coro_resume});
 }
 
diff --git a/llvm/lib/Transforms/Coroutines/CoroElide.cpp b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
index 1c8d4a8592d60..169ae360e862f 100644
--- a/llvm/lib/Transforms/Coroutines/CoroElide.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
@@ -35,10 +35,9 @@ namespace {
 // Created on demand if the coro-elide pass has work to do.
 class FunctionElideInfo {
 public:
-  FunctionElideInfo(Function *F) : ContainingFunction(F) {
-    this->collectPostSplitCoroIds();
-  }
+  FunctionElideInfo(Function *F);
 
+  bool elideNoopCoro();
   bool hasCoroIds() const { return !CoroIds.empty(); }
 
   const SmallVectorImpl<CoroIdInst *> &getCoroIds() const { return CoroIds; }
@@ -46,10 +45,10 @@ class FunctionElideInfo {
 private:
   Function *ContainingFunction;
   SmallVector<CoroIdInst *, 4> CoroIds;
+  SmallVector<IntrinsicInst *, 1> CoroNoops;
   // Used in canCoroBeginEscape to distinguish coro.suspend switchs.
   SmallPtrSet<const SwitchInst *, 4> CoroSuspendSwitches;
 
-  void collectPostSplitCoroIds();
   friend class CoroIdElider;
 };
 
@@ -77,6 +76,35 @@ class CoroIdElider {
 };
 } // end anonymous namespace
 
+FunctionElideInfo::FunctionElideInfo(Function *F) : ContainingFunction(F) {
+  for (auto &I : instructions(F)) {
+    auto *II = dyn_cast<IntrinsicInst>(&I);
+    if (!II)
+      continue;
+
+    if (II->getIntrinsicID() == Intrinsic::coro_noop)
+      CoroNoops.push_back(II);
+
+    if (auto *CII = dyn_cast<CoroIdInst>(&I))
+      if (CII->getInfo().isPostSplit())
+        // If it is the coroutine itself, don't touch it.
+        if (CII->getCoroutine() != CII->getFunction())
+          CoroIds.push_back(CII);
+
+    // Consider case like:
+    // %0 = call i8 @llvm.coro.suspend(...)
+    // switch i8 %0, label %suspend [i8 0, label %resume
+    //                              i8 1, label %cleanup]
+    // and collect the SwitchInsts which are used by escape analysis later.
+    if (auto *CSI = dyn_cast<CoroSuspendInst>(&I))
+      if (CSI->hasOneUse() && isa<SwitchInst>(CSI->use_begin()->getUser())) {
+        SwitchInst *SWI = cast<SwitchInst>(CSI->use_begin()->getUser());
+        if (SWI->getNumCases() == 2)
+          CoroSuspendSwitches.insert(SWI);
+      }
+  }
+}
+
 // Go through the list of coro.subfn.addr intrinsics and replace them with the
 // provided constant.
 static void replaceWithConstant(Constant *Value,
@@ -85,6 +113,29 @@ static void replaceWithConstant(Constant *Value,
     replaceAndRecursivelySimplify(I, Value);
 }
 
+bool FunctionElideInfo::elideNoopCoro() {
+  if (CoroNoops.empty())
+    return false;
+
+  bool Changed = false;
+  for (auto *Noop : CoroNoops) {
+    for (User *U : make_early_inc_range(Noop->users())) {
+      if (auto *II = dyn_cast<CoroSubFnInst>(U)) {
+        auto *Call = cast<CallInst>(II->getUniqueUndroppableUser());
+        Call->eraseFromParent();
+        II->eraseFromParent();
+        Changed = true;
+      }
+    }
+
+    if (Noop->user_empty()) {
+      Noop->eraseFromParent();
+      Changed = true;
+    }
+  }
+  return Changed;
+}
+
 // See if any operand of the call instruction references the coroutine frame.
 static bool operandReferences(CallInst *CI, AllocaInst *Frame, AAResults &AA) {
   for (Value *Op : CI->operand_values())
@@ -143,28 +194,6 @@ static std::unique_ptr<raw_fd_ostream> getOrCreateLogFile() {
 }
 #endif
 
-void FunctionElideInfo::collectPostSplitCoroIds() {
-  for (auto &I : instructions(this->ContainingFunction)) {
-    if (auto *CII = dyn_cast<CoroIdInst>(&I))
-      if (CII->getInfo().isPostSplit())
-        // If it is the coroutine itself, don't touch it.
-        if (CII->getCoroutine() != CII->getFunction())
-          CoroIds.push_back(CII);
-
-    // Consider case like:
-    // %0 = call i8 @llvm.coro.suspend(...)
-    // switch i8 %0, label %suspend [i8 0, label %resume
-    //                              i8 1, label %cleanup]
-    // and collect the SwitchInsts which are used by escape analysis later.
-    if (auto *CSI = dyn_cast<CoroSuspendInst>(&I))
-      if (CSI->hasOneUse() && isa<SwitchInst>(CSI->use_begin()->getUser())) {
-        SwitchInst *SWI = cast<SwitchInst>(CSI->use_begin()->getUser());
-        if (SWI->getNumCases() == 2)
-          CoroSuspendSwitches.insert(SWI);
-      }
-  }
-}
-
 CoroIdElider::CoroIdElider(CoroIdInst *CoroId, FunctionElideInfo &FEI,
                            AAResults &AA, DominatorTree &DT,
                            OptimizationRemarkEmitter &ORE)
@@ -450,23 +479,21 @@ bool CoroIdElider::attemptElide() {
 
 PreservedAnalyses CoroElidePass::run(Function &F, FunctionAnalysisManager &AM) {
   auto &M = *F.getParent();
-  if (!coro::declaresIntrinsics(M, Intrinsic::coro_id))
+  if (!coro::declaresIntrinsics(M, {Intrinsic::coro_id, Intrinsic::coro_noop}))
     return PreservedAnalyses::all();
 
   FunctionElideInfo FEI{&F};
+  bool Changed = FEI.elideNoopCoro();
   // Elide is not necessary if there's no coro.id within the function.
-  if (!FEI.hasCoroIds())
-    return PreservedAnalyses::all();
-
-  AAResults &AA = AM.getResult<AAManager>(F);
-  DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
-  auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
-
-  bool Changed = false;
-  for (auto *CII : FEI.getCoroIds()) {
-    CoroIdElider CIE(CII, FEI, AA, DT, ORE);
-    Changed |= CIE.attemptElide();
+  if (FEI.hasCoroIds()) {
+    AAResults &AA = AM.getResult<AAManager>(F);
+    DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
+    auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
+
+    for (auto *CII : FEI.getCoroIds()) {
+      CoroIdElider CIE(CII, FEI, AA, DT, ORE);
+      Changed |= CIE.attemptElide();
+    }
   }
-
   return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
 }
diff --git a/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll b/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
index 65b27ee2241dd..387cc2a292f97 100644
--- a/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
+++ b/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
@@ -1,5 +1,6 @@
 ; Tests that asan skips pre-split coroutine and NoopCoro.Frame
-; RUN: opt < %s -S -passes=coro-early,asan | FileCheck %s
+; RUN: opt < %s -S -O0 | FileCheck %s
+; RUN: opt < %s -S -O1 | FileCheck %s
 
 ; CHECK: %NoopCoro.Frame = type { ptr, ptr }
 ; CHECK: @NoopCoro.Frame.Const = private constant %NoopCoro.Frame { ptr @__NoopCoro_ResumeDestroy, ptr @__NoopCoro_ResumeDestroy }
diff --git a/llvm/test/Transforms/Coroutines/coro-elide-noop.ll b/llvm/test/Transforms/Coroutines/coro-elide-noop.ll
new file mode 100644
index 0000000000000..2e59cb6330d8c
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-elide-noop.ll
@@ -0,0 +1,13 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; Tests that noop coroutine can be elided
+; RUN: opt < %s -S -passes='coro-elide' | FileCheck %s
+
+define void @fn() {
+; CHECK-LABEL: define void @fn() {
+; CHECK-NEXT:    ret void
+;
+  %noop.frame = tail call noundef ptr @llvm.coro.noop()
+  %resume = tail call ptr @llvm.coro.subfn.addr(ptr %noop.frame, i8 0)
+  tail call fastcc void %resume(ptr %noop.frame)
+  ret void
+}
diff --git a/llvm/test/Transforms/Coroutines/coro-noop-pacbti.ll b/llvm/test/Transforms/Coroutines/coro-noop-pacbti.ll
index 41a01bea48369..910b6b07e1628 100644
--- a/llvm/test/Transforms/Coroutines/coro-noop-pacbti.ll
+++ b/llvm/test/Transforms/Coroutines/coro-noop-pacbti.ll
@@ -1,5 +1,5 @@
 
-; RUN: opt < %s -S -passes=coro-early | FileCheck %s
+; RUN: opt < %s -S -passes=coro-cleanup | FileCheck %s
 
 ; CHECK:      define internal fastcc void @__NoopCoro_ResumeDestroy(ptr %0) #1 {
 ; CHECK-NEXT: entry:
diff --git a/llvm/test/Transforms/Coroutines/coro-noop.ll b/llvm/test/Transforms/Coroutines/coro-noop.ll
index 7156835e5b2d5..de92df6b9f90b 100644
--- a/llvm/test/Transforms/Coroutines/coro-noop.ll
+++ b/llvm/test/Transforms/Coroutines/coro-noop.ll
@@ -1,5 +1,5 @@
-; Tests that CoroEarly pass correctly lowers coro.noop
-; RUN: opt < %s -S -passes=coro-early | FileCheck %s
+; Tests that CoroCleanup pass correctly lowers coro.noop
+; RUN: opt < %s -S -passes=coro-cleanup | FileCheck %s
 
 ; CHECK: %NoopCoro.Frame = type { ptr, ptr }
 ; CHECK: @NoopCoro.Frame.Const = private constant %NoopCoro.Frame { ptr @__NoopCoro_ResumeDestroy, ptr @__NoopCoro_ResumeDestroy }

>From 83222b7aa75ea413bb97be71acbd82453018e300 Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Mon, 5 Jan 2026 11:24:24 +0800
Subject: [PATCH 2/3] Revert accident formatting

---
 llvm/lib/Transforms/Coroutines/CoroEarly.cpp | 108 +++++++++----------
 1 file changed, 54 insertions(+), 54 deletions(-)

diff --git a/llvm/lib/Transforms/Coroutines/CoroEarly.cpp b/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
index 39ee53f111d28..3f0c3b6353733 100644
--- a/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
@@ -145,61 +145,61 @@ void Lowerer::lowerEarlyIntrinsics(Function &F) {
       continue;
 
     switch (CB->getIntrinsicID()) {
-    default:
-      continue;
-    case Intrinsic::coro_begin:
-    case Intrinsic::coro_begin_custom_abi:
-      if (CoroBegin)
-        report_fatal_error(
-            "coroutine should have exactly one defining @llvm.coro.begin");
-      CoroBegin = cast<CoroBeginInst>(&I);
-      break;
-    case Intrinsic::coro_free:
-      CoroFrees.push_back(cast<CoroFreeInst>(&I));
-      break;
-    case Intrinsic::coro_suspend:
-      // Make sure that final suspend point is not duplicated as CoroSplit
-      // pass expects that there is at most one final suspend point.
-      if (cast<CoroSuspendInst>(&I)->isFinal())
-        CB->setCannotDuplicate();
-      HasCoroSuspend = true;
-      break;
-    case Intrinsic::coro_end_async:
-    case Intrinsic::coro_end:
-      // Make sure that fallthrough coro.end is not duplicated as CoroSplit
-      // pass expects that there is at most one fallthrough coro.end.
-      if (cast<AnyCoroEndInst>(&I)->isFallthrough())
-        CB->setCannotDuplicate();
-      break;
-    case Intrinsic::coro_id:
-      if (auto *CII = cast<CoroIdInst>(&I)) {
-        if (CII->getInfo().isPreSplit()) {
-          assert(F.isPresplitCoroutine() &&
-                "The frontend uses Switch-Resumed ABI should emit "
-                "\"presplitcoroutine\" attribute for the coroutine.");
-          setCannotDuplicate(CII);
-          CII->setCoroutineSelf();
-          CoroId = cast<CoroIdInst>(&I);
+      default:
+        continue;
+      case Intrinsic::coro_begin:
+      case Intrinsic::coro_begin_custom_abi:
+        if (CoroBegin)
+          report_fatal_error(
+              "coroutine should have exactly one defining @llvm.coro.begin");
+        CoroBegin = cast<CoroBeginInst>(&I);
+        break;
+      case Intrinsic::coro_free:
+        CoroFrees.push_back(cast<CoroFreeInst>(&I));
+        break;
+      case Intrinsic::coro_suspend:
+        // Make sure that final suspend point is not duplicated as CoroSplit
+        // pass expects that there is at most one final suspend point.
+        if (cast<CoroSuspendInst>(&I)->isFinal())
+          CB->setCannotDuplicate();
+        HasCoroSuspend = true;
+        break;
+      case Intrinsic::coro_end_async:
+      case Intrinsic::coro_end:
+        // Make sure that fallthrough coro.end is not duplicated as CoroSplit
+        // pass expects that there is at most one fallthrough coro.end.
+        if (cast<AnyCoroEndInst>(&I)->isFallthrough())
+          CB->setCannotDuplicate();
+        break;
+      case Intrinsic::coro_id:
+        if (auto *CII = cast<CoroIdInst>(&I)) {
+          if (CII->getInfo().isPreSplit()) {
+            assert(F.isPresplitCoroutine() &&
+                   "The frontend uses Switch-Resumed ABI should emit "
+                   "\"presplitcoroutine\" attribute for the coroutine.");
+            setCannotDuplicate(CII);
+            CII->setCoroutineSelf();
+            CoroId = cast<CoroIdInst>(&I);
+          }
         }
-      }
-      break;
-    case Intrinsic::coro_id_retcon:
-    case Intrinsic::coro_id_retcon_once:
-    case Intrinsic::coro_id_async:
-      F.setPresplitCoroutine();
-      break;
-    case Intrinsic::coro_resume:
-      lowerResumeOrDestroy(*CB, CoroSubFnInst::ResumeIndex);
-      break;
-    case Intrinsic::coro_destroy:
-      lowerResumeOrDestroy(*CB, CoroSubFnInst::DestroyIndex);
-      break;
-    case Intrinsic::coro_promise:
-      lowerCoroPromise(cast<CoroPromiseInst>(&I));
-      break;
-    case Intrinsic::coro_done:
-      lowerCoroDone(cast<IntrinsicInst>(&I));
-      break;
+        break;
+      case Intrinsic::coro_id_retcon:
+      case Intrinsic::coro_id_retcon_once:
+      case Intrinsic::coro_id_async:
+        F.setPresplitCoroutine();
+        break;
+      case Intrinsic::coro_resume:
+        lowerResumeOrDestroy(*CB, CoroSubFnInst::ResumeIndex);
+        break;
+      case Intrinsic::coro_destroy:
+        lowerResumeOrDestroy(*CB, CoroSubFnInst::DestroyIndex);
+        break;
+      case Intrinsic::coro_promise:
+        lowerCoroPromise(cast<CoroPromiseInst>(&I));
+        break;
+      case Intrinsic::coro_done:
+        lowerCoroDone(cast<IntrinsicInst>(&I));
+        break;
     }
   }
 

>From aa8563f24a5ca3e01a95f6432f412cdd4d7cc637 Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Sun, 11 Jan 2026 19:23:53 +0800
Subject: [PATCH 3/3] Move noop coro erasing from CoroElide to CoroCleanup

---
 llvm/docs/Coroutines.rst                      |   4 +-
 .../lib/Transforms/Coroutines/CoroCleanup.cpp |  34 +++++-
 llvm/lib/Transforms/Coroutines/CoroElide.cpp  | 105 +++++++-----------
 .../AddressSanitizer/skip-coro.ll             |   3 +-
 .../Coroutines/coro-cleanup-noop-erase.ll     |  24 ++++
 .../Transforms/Coroutines/coro-elide-noop.ll  |  13 ---
 6 files changed, 96 insertions(+), 87 deletions(-)
 create mode 100644 llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll
 delete mode 100644 llvm/test/Transforms/Coroutines/coro-elide-noop.ll

diff --git a/llvm/docs/Coroutines.rst b/llvm/docs/Coroutines.rst
index e6759f4edf353..0e6b49c84acee 100644
--- a/llvm/docs/Coroutines.rst
+++ b/llvm/docs/Coroutines.rst
@@ -2174,9 +2174,7 @@ allocation elision optimization. If so, it replaces
 `coro.begin` intrinsic with an address of a coroutine frame placed on its caller
 and replaces `coro.alloc` and `coro.free` intrinsics with `false` and `null`
 respectively to remove the deallocation code.
-This pass also eliminates the resume and destroy operation on noop coroutines and
-attempts to erase unused `coro.noop` instructions.
-Finally, this pass replaces `coro.resume` and `coro.destroy` intrinsics with direct
+This pass also replaces `coro.resume` and `coro.destroy` intrinsics with direct
 calls to resume and destroy functions for a particular coroutine where possible.
 
 CoroCleanup
diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index 40aa820bf50c2..6b68cf5bc2c20 100644
--- a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
@@ -30,6 +30,7 @@ struct Lowerer : coro::LowererBase {
   bool lower(Function &F);
 
 private:
+  void elideCoroNoop(IntrinsicInst *II);
   void lowerCoroNoop(IntrinsicInst *II);
 };
 }
@@ -72,7 +73,8 @@ bool Lowerer::lower(Function &F) {
   bool IsPrivateAndUnprocessed = F.isPresplitCoroutine() && F.hasLocalLinkage();
   bool Changed = false;
 
-  for (Instruction &I : llvm::make_early_inc_range(instructions(F))) {
+  SmallPtrSet<Instruction *, 8> DeadInsts{};
+  for (Instruction &I : instructions(F)) {
     if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
       switch (II->getIntrinsicID()) {
       default:
@@ -98,7 +100,9 @@ bool Lowerer::lower(Function &F) {
         II->replaceAllUsesWith(ConstantTokenNone::get(Context));
         break;
       case Intrinsic::coro_noop:
-        lowerCoroNoop(II);
+        elideCoroNoop(II);
+        if (!II->user_empty())
+          lowerCoroNoop(II);
         break;
       case Intrinsic::coro_subfn_addr:
         lowerSubFn(Builder, cast<CoroSubFnInst>(II));
@@ -128,14 +132,38 @@ bool Lowerer::lower(Function &F) {
         Target->replaceAllUsesWith(NewFuncPtrStruct);
         break;
       }
-      II->eraseFromParent();
+      DeadInsts.insert(II);
       Changed = true;
     }
   }
 
+  for (auto *I : DeadInsts)
+    I->eraseFromParent();
   return Changed;
 }
 
+void Lowerer::elideCoroNoop(IntrinsicInst *II) {
+  for (User *U : make_early_inc_range(II->users())) {
+    auto *Fn = dyn_cast<CoroSubFnInst>(U);
+    if (Fn == nullptr)
+      continue;
+
+    auto *User = Fn->getUniqueUndroppableUser();
+    if (auto *Call = dyn_cast<CallInst>(User)) {
+      Call->eraseFromParent();
+      Fn->eraseFromParent();
+      continue;
+    }
+
+    if (auto *I = dyn_cast<InvokeInst>(User)) {
+      Builder.SetInsertPoint(I);
+      Builder.CreateBr(I->getNormalDest());
+      I->eraseFromParent();
+      Fn->eraseFromParent();
+    }
+  }
+}
+
 void Lowerer::lowerCoroNoop(IntrinsicInst *II) {
   if (!NoopCoro) {
     LLVMContext &C = Builder.getContext();
diff --git a/llvm/lib/Transforms/Coroutines/CoroElide.cpp b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
index 169ae360e862f..1c8d4a8592d60 100644
--- a/llvm/lib/Transforms/Coroutines/CoroElide.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
@@ -35,9 +35,10 @@ namespace {
 // Created on demand if the coro-elide pass has work to do.
 class FunctionElideInfo {
 public:
-  FunctionElideInfo(Function *F);
+  FunctionElideInfo(Function *F) : ContainingFunction(F) {
+    this->collectPostSplitCoroIds();
+  }
 
-  bool elideNoopCoro();
   bool hasCoroIds() const { return !CoroIds.empty(); }
 
   const SmallVectorImpl<CoroIdInst *> &getCoroIds() const { return CoroIds; }
@@ -45,10 +46,10 @@ class FunctionElideInfo {
 private:
   Function *ContainingFunction;
   SmallVector<CoroIdInst *, 4> CoroIds;
-  SmallVector<IntrinsicInst *, 1> CoroNoops;
   // Used in canCoroBeginEscape to distinguish coro.suspend switchs.
   SmallPtrSet<const SwitchInst *, 4> CoroSuspendSwitches;
 
+  void collectPostSplitCoroIds();
   friend class CoroIdElider;
 };
 
@@ -76,35 +77,6 @@ class CoroIdElider {
 };
 } // end anonymous namespace
 
-FunctionElideInfo::FunctionElideInfo(Function *F) : ContainingFunction(F) {
-  for (auto &I : instructions(F)) {
-    auto *II = dyn_cast<IntrinsicInst>(&I);
-    if (!II)
-      continue;
-
-    if (II->getIntrinsicID() == Intrinsic::coro_noop)
-      CoroNoops.push_back(II);
-
-    if (auto *CII = dyn_cast<CoroIdInst>(&I))
-      if (CII->getInfo().isPostSplit())
-        // If it is the coroutine itself, don't touch it.
-        if (CII->getCoroutine() != CII->getFunction())
-          CoroIds.push_back(CII);
-
-    // Consider case like:
-    // %0 = call i8 @llvm.coro.suspend(...)
-    // switch i8 %0, label %suspend [i8 0, label %resume
-    //                              i8 1, label %cleanup]
-    // and collect the SwitchInsts which are used by escape analysis later.
-    if (auto *CSI = dyn_cast<CoroSuspendInst>(&I))
-      if (CSI->hasOneUse() && isa<SwitchInst>(CSI->use_begin()->getUser())) {
-        SwitchInst *SWI = cast<SwitchInst>(CSI->use_begin()->getUser());
-        if (SWI->getNumCases() == 2)
-          CoroSuspendSwitches.insert(SWI);
-      }
-  }
-}
-
 // Go through the list of coro.subfn.addr intrinsics and replace them with the
 // provided constant.
 static void replaceWithConstant(Constant *Value,
@@ -113,29 +85,6 @@ static void replaceWithConstant(Constant *Value,
     replaceAndRecursivelySimplify(I, Value);
 }
 
-bool FunctionElideInfo::elideNoopCoro() {
-  if (CoroNoops.empty())
-    return false;
-
-  bool Changed = false;
-  for (auto *Noop : CoroNoops) {
-    for (User *U : make_early_inc_range(Noop->users())) {
-      if (auto *II = dyn_cast<CoroSubFnInst>(U)) {
-        auto *Call = cast<CallInst>(II->getUniqueUndroppableUser());
-        Call->eraseFromParent();
-        II->eraseFromParent();
-        Changed = true;
-      }
-    }
-
-    if (Noop->user_empty()) {
-      Noop->eraseFromParent();
-      Changed = true;
-    }
-  }
-  return Changed;
-}
-
 // See if any operand of the call instruction references the coroutine frame.
 static bool operandReferences(CallInst *CI, AllocaInst *Frame, AAResults &AA) {
   for (Value *Op : CI->operand_values())
@@ -194,6 +143,28 @@ static std::unique_ptr<raw_fd_ostream> getOrCreateLogFile() {
 }
 #endif
 
+void FunctionElideInfo::collectPostSplitCoroIds() {
+  for (auto &I : instructions(this->ContainingFunction)) {
+    if (auto *CII = dyn_cast<CoroIdInst>(&I))
+      if (CII->getInfo().isPostSplit())
+        // If it is the coroutine itself, don't touch it.
+        if (CII->getCoroutine() != CII->getFunction())
+          CoroIds.push_back(CII);
+
+    // Consider case like:
+    // %0 = call i8 @llvm.coro.suspend(...)
+    // switch i8 %0, label %suspend [i8 0, label %resume
+    //                              i8 1, label %cleanup]
+    // and collect the SwitchInsts which are used by escape analysis later.
+    if (auto *CSI = dyn_cast<CoroSuspendInst>(&I))
+      if (CSI->hasOneUse() && isa<SwitchInst>(CSI->use_begin()->getUser())) {
+        SwitchInst *SWI = cast<SwitchInst>(CSI->use_begin()->getUser());
+        if (SWI->getNumCases() == 2)
+          CoroSuspendSwitches.insert(SWI);
+      }
+  }
+}
+
 CoroIdElider::CoroIdElider(CoroIdInst *CoroId, FunctionElideInfo &FEI,
                            AAResults &AA, DominatorTree &DT,
                            OptimizationRemarkEmitter &ORE)
@@ -479,21 +450,23 @@ bool CoroIdElider::attemptElide() {
 
 PreservedAnalyses CoroElidePass::run(Function &F, FunctionAnalysisManager &AM) {
   auto &M = *F.getParent();
-  if (!coro::declaresIntrinsics(M, {Intrinsic::coro_id, Intrinsic::coro_noop}))
+  if (!coro::declaresIntrinsics(M, Intrinsic::coro_id))
     return PreservedAnalyses::all();
 
   FunctionElideInfo FEI{&F};
-  bool Changed = FEI.elideNoopCoro();
   // Elide is not necessary if there's no coro.id within the function.
-  if (FEI.hasCoroIds()) {
-    AAResults &AA = AM.getResult<AAManager>(F);
-    DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
-    auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
-
-    for (auto *CII : FEI.getCoroIds()) {
-      CoroIdElider CIE(CII, FEI, AA, DT, ORE);
-      Changed |= CIE.attemptElide();
-    }
+  if (!FEI.hasCoroIds())
+    return PreservedAnalyses::all();
+
+  AAResults &AA = AM.getResult<AAManager>(F);
+  DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
+  auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
+
+  bool Changed = false;
+  for (auto *CII : FEI.getCoroIds()) {
+    CoroIdElider CIE(CII, FEI, AA, DT, ORE);
+    Changed |= CIE.attemptElide();
   }
+
   return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
 }
diff --git a/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll b/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
index 387cc2a292f97..3a385eac82042 100644
--- a/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
+++ b/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
@@ -1,6 +1,5 @@
 ; Tests that asan skips pre-split coroutine and NoopCoro.Frame
-; RUN: opt < %s -S -O0 | FileCheck %s
-; RUN: opt < %s -S -O1 | FileCheck %s
+; RUN: opt < %s -S -passes=coro-cleanup,asan | FileCheck %s
 
 ; CHECK: %NoopCoro.Frame = type { ptr, ptr }
 ; CHECK: @NoopCoro.Frame.Const = private constant %NoopCoro.Frame { ptr @__NoopCoro_ResumeDestroy, ptr @__NoopCoro_ResumeDestroy }
diff --git a/llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll b/llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll
new file mode 100644
index 0000000000000..7fd9dc900ddb2
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll
@@ -0,0 +1,24 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; Tests that resume or destroy a no-op coroutine can be erased; Finally, erase coro.noop if it has no users.
+; RUN: opt < %s -S -passes='coro-cleanup' | FileCheck %s
+
+define void @fn() personality i32 0 {
+; CHECK-LABEL: define void @fn() personality i32 0 {
+; CHECK-NEXT:  [[DONE:.*:]]
+; CHECK-NEXT:    ret void
+;
+  %frame = call noundef ptr @llvm.coro.noop()
+  %resume = call ptr @llvm.coro.subfn.addr(ptr %frame, i8 0)
+  call fastcc void %resume(ptr %frame)
+  %destroy = call ptr @llvm.coro.subfn.addr(ptr %frame, i8 1)
+  invoke fastcc void %destroy(ptr %frame)
+  to label %done unwind label %unwind
+
+done:
+  ret void
+
+unwind:
+  %pad = landingpad { ptr, i32 }
+  catch ptr null
+  unreachable
+}
diff --git a/llvm/test/Transforms/Coroutines/coro-elide-noop.ll b/llvm/test/Transforms/Coroutines/coro-elide-noop.ll
deleted file mode 100644
index 2e59cb6330d8c..0000000000000
--- a/llvm/test/Transforms/Coroutines/coro-elide-noop.ll
+++ /dev/null
@@ -1,13 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; Tests that noop coroutine can be elided
-; RUN: opt < %s -S -passes='coro-elide' | FileCheck %s
-
-define void @fn() {
-; CHECK-LABEL: define void @fn() {
-; CHECK-NEXT:    ret void
-;
-  %noop.frame = tail call noundef ptr @llvm.coro.noop()
-  %resume = tail call ptr @llvm.coro.subfn.addr(ptr %noop.frame, i8 0)
-  tail call fastcc void %resume(ptr %noop.frame)
-  ret void
-}



More information about the llvm-commits mailing list