[llvm] [CoroCleanup] Noop coroutine elision for load-and-call pattern (PR #179154)

Weibo He via llvm-commits llvm-commits at lists.llvm.org
Mon Feb 2 05:08:20 PST 2026


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

>From 6dcfa386852c5d38b8617ecee6fcb2e00cd7182b Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Sat, 31 Jan 2026 21:57:00 +0800
Subject: [PATCH 1/3] [CoroCleanup] Noop coroutine elision for load-and-call
 pattern

---
 .../lib/Transforms/Coroutines/CoroCleanup.cpp | 86 +++++++++++++------
 .../Coroutines/coro-cleanup-noop-elide.ll     | 51 +++++++++++
 .../Coroutines/coro-cleanup-noop-erase.ll     | 24 ------
 3 files changed, 113 insertions(+), 48 deletions(-)
 create mode 100644 llvm/test/Transforms/Coroutines/coro-cleanup-noop-elide.ll
 delete mode 100644 llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll

diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index 6b68cf5bc2c20..52386aa749bc9 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/Analysis/PtrUseVisitor.h"
 #include "llvm/IR/DIBuilder.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/IRBuilder.h"
@@ -15,6 +16,7 @@
 #include "llvm/IR/Module.h"
 #include "llvm/IR/PassManager.h"
 #include "llvm/Transforms/Scalar/SimplifyCFG.h"
+#include "llvm/Transforms/Utils/Local.h"
 
 using namespace llvm;
 
@@ -30,9 +32,25 @@ struct Lowerer : coro::LowererBase {
   bool lower(Function &F);
 
 private:
-  void elideCoroNoop(IntrinsicInst *II);
   void lowerCoroNoop(IntrinsicInst *II);
 };
+
+// Recursively walk and eliminate resume/destroy call on noop coro
+class NoopCoroElider : public PtrUseVisitor<NoopCoroElider> {
+  using Base = PtrUseVisitor<NoopCoroElider>;
+
+  IRBuilder<> Builder;
+public:
+  NoopCoroElider(const DataLayout &DL, LLVMContext &C) : Base(DL), Builder(C) {}
+
+  void run(IntrinsicInst *II);
+
+  void visitLoadInst(LoadInst &I) { enqueueUsers(I); }
+  void visitCallBase(CallBase &CB);
+  void visitIntrinsicInst(IntrinsicInst &II);
+private:
+  bool tryEraseCallInvoke(Instruction *I);
+};
 }
 
 static void lowerSubFn(IRBuilder<> &Builder, CoroSubFnInst *SubFn) {
@@ -73,6 +91,7 @@ bool Lowerer::lower(Function &F) {
   bool IsPrivateAndUnprocessed = F.isPresplitCoroutine() && F.hasLocalLinkage();
   bool Changed = false;
 
+  NoopCoroElider NCE(F.getDataLayout(), F.getContext());
   SmallPtrSet<Instruction *, 8> DeadInsts{};
   for (Instruction &I : instructions(F)) {
     if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
@@ -100,7 +119,7 @@ bool Lowerer::lower(Function &F) {
         II->replaceAllUsesWith(ConstantTokenNone::get(Context));
         break;
       case Intrinsic::coro_noop:
-        elideCoroNoop(II);
+        NCE.run(II);
         if (!II->user_empty())
           lowerCoroNoop(II);
         break;
@@ -142,28 +161,6 @@ bool Lowerer::lower(Function &F) {
   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();
@@ -200,6 +197,47 @@ void Lowerer::lowerCoroNoop(IntrinsicInst *II) {
   II->replaceAllUsesWith(NoopCoroVoidPtr);
 }
 
+void NoopCoroElider::run(IntrinsicInst *II) {
+  visitPtr(*II);
+
+  Worklist.clear();
+  VisitedUses.clear();
+}
+
+void NoopCoroElider::visitCallBase(CallBase &CB) {
+  auto *V = U->get();
+  bool ResumeOrDestroy = V == CB.getCalledOperand();
+  if (ResumeOrDestroy) {
+    bool Success = tryEraseCallInvoke(&CB);
+    assert(Success && "Unexpected CallBase");
+    RecursivelyDeleteTriviallyDeadInstructions(V);
+  }
+}
+
+void NoopCoroElider::visitIntrinsicInst(IntrinsicInst &II) {
+  if (auto *SubFn = dyn_cast<CoroSubFnInst>(&II)) {
+    auto *User = SubFn->getUniqueUndroppableUser();
+    if (!tryEraseCallInvoke(cast<Instruction>(User)))
+      return;
+    SubFn->eraseFromParent();
+  }
+}
+
+bool NoopCoroElider::tryEraseCallInvoke(Instruction *I) {
+  if (auto *Call = dyn_cast<CallInst>(I)) {
+    Call->eraseFromParent();
+    return true;
+  }
+
+  if (auto *II = dyn_cast<InvokeInst>(I)) {
+    Builder.SetInsertPoint(II);
+    Builder.CreateBr(II->getNormalDest());
+    II->eraseFromParent();
+    return true;
+  }
+  return false;
+}
+
 static bool declaresCoroCleanupIntrinsics(const Module &M) {
   return coro::declaresIntrinsics(
       M,
diff --git a/llvm/test/Transforms/Coroutines/coro-cleanup-noop-elide.ll b/llvm/test/Transforms/Coroutines/coro-cleanup-noop-elide.ll
new file mode 100644
index 0000000000000..6d9dd654b914a
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-cleanup-noop-elide.ll
@@ -0,0 +1,51 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -S -passes='coro-cleanup' | FileCheck %s
+
+; Tests that resume or destroy a no-op coroutine can be erased; Finally, erase coro.noop if it has no users.
+define void @erase() personality i32 0 {
+; CHECK-LABEL: define void @erase() 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
+  call void @terminate()
+  unreachable
+}
+
+; Tests the load-and-call pattern despite mismatched calling conventions. Prevent instcombine from breaking code.
+define void @load() personality i32 0 {
+; CHECK-LABEL: define void @load() personality i32 0 {
+; CHECK-NEXT:  [[DONE:.*:]]
+; CHECK-NEXT:    ret void
+;
+  %frame = call noundef ptr @llvm.coro.noop()
+  %resume = load ptr, ptr %frame, align 8
+  call void %resume(ptr %frame)
+  %destroy.addr = getelementptr inbounds nuw i8, ptr %frame, i64 8
+  %destroy = load ptr, ptr %destroy.addr, align 8
+  invoke void %destroy(ptr %frame)
+  to label %done unwind label %unwind
+
+done:
+  ret void
+
+unwind:
+  %pad = landingpad { ptr, i32 }
+  catch ptr null
+  call void @terminate()
+  unreachable
+}
+
+declare void @terminate() noreturn
diff --git a/llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll b/llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll
deleted file mode 100644
index 7fd9dc900ddb2..0000000000000
--- a/llvm/test/Transforms/Coroutines/coro-cleanup-noop-erase.ll
+++ /dev/null
@@ -1,24 +0,0 @@
-; 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
-}

>From 4bf86d34d4fa9e647fc6fb14a61236b643b25fb6 Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Mon, 2 Feb 2026 10:51:31 +0800
Subject: [PATCH 2/3] Apply clang-format(NFC)

---
 llvm/lib/Transforms/Coroutines/CoroCleanup.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index 52386aa749bc9..deabc3de3140b 100644
--- a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
@@ -40,6 +40,7 @@ class NoopCoroElider : public PtrUseVisitor<NoopCoroElider> {
   using Base = PtrUseVisitor<NoopCoroElider>;
 
   IRBuilder<> Builder;
+
 public:
   NoopCoroElider(const DataLayout &DL, LLVMContext &C) : Base(DL), Builder(C) {}
 
@@ -48,6 +49,7 @@ class NoopCoroElider : public PtrUseVisitor<NoopCoroElider> {
   void visitLoadInst(LoadInst &I) { enqueueUsers(I); }
   void visitCallBase(CallBase &CB);
   void visitIntrinsicInst(IntrinsicInst &II);
+
 private:
   bool tryEraseCallInvoke(Instruction *I);
 };

>From 8db2d86dd0531b7a8a09c064fcc354fe7ea11cd2 Mon Sep 17 00:00:00 2001
From: NewSigma <NewSigma at 163.com>
Date: Mon, 2 Feb 2026 21:07:04 +0800
Subject: [PATCH 3/3] Silent warning(NFC)

---
 llvm/lib/Transforms/Coroutines/CoroCleanup.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index deabc3de3140b..fa342fd4717ea 100644
--- a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
@@ -210,7 +210,7 @@ void NoopCoroElider::visitCallBase(CallBase &CB) {
   auto *V = U->get();
   bool ResumeOrDestroy = V == CB.getCalledOperand();
   if (ResumeOrDestroy) {
-    bool Success = tryEraseCallInvoke(&CB);
+    [[maybe_unused]] bool Success = tryEraseCallInvoke(&CB);
     assert(Success && "Unexpected CallBase");
     RecursivelyDeleteTriviallyDeadInstructions(V);
   }



More information about the llvm-commits mailing list