[clang] 230d6f9 - [Coroutines] Remove lifetime intrinsics for spliied allocas in coroutine frames

Chuanqi Xu via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 4 23:53:15 PDT 2022


Author: Chuanqi Xu
Date: 2022-08-05T14:50:43+08:00
New Revision: 230d6f93aaabae985e9fafd071bb3737533c6083

URL: https://github.com/llvm/llvm-project/commit/230d6f93aaabae985e9fafd071bb3737533c6083
DIFF: https://github.com/llvm/llvm-project/commit/230d6f93aaabae985e9fafd071bb3737533c6083.diff

LOG: [Coroutines] Remove lifetime intrinsics for spliied allocas in coroutine frames

Closing https://github.com/llvm/llvm-project/issues/56919

It is meaningless to preserve the lifetime markers for the spilled
allocas in the coroutine frames and it would block some optimizations
too.

Added: 
    clang/test/CodeGenCoroutines/pr56919.cpp
    llvm/test/Transforms/Coroutines/coro-split-no-lieftime.ll

Modified: 
    llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Removed: 
    


################################################################################
diff  --git a/clang/test/CodeGenCoroutines/pr56919.cpp b/clang/test/CodeGenCoroutines/pr56919.cpp
new file mode 100644
index 000000000000..ecd9515acb81
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/pr56919.cpp
@@ -0,0 +1,119 @@
+// Test for PR56919. Tests the destroy function contains the call to delete function only.
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 %s -O3 -S -o - | FileCheck %s
+
+#include "Inputs/coroutine.h"
+
+namespace std {
+
+template <typename T> struct remove_reference { using type = T; };
+template <typename T> struct remove_reference<T &> { using type = T; };
+template <typename T> struct remove_reference<T &&> { using type = T; };
+
+template <typename T>
+constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
+  return static_cast<typename std::remove_reference<T>::type &&>(t);
+}
+
+}
+
+template <typename T>
+class Task final {
+ public:
+  using value_type = T;
+
+  class promise_type final {
+   public: 
+    Task<void> get_return_object() { return Task<void>(std::coroutine_handle<promise_type>::from_promise(*this)); }
+
+    void unhandled_exception();
+
+    std::suspend_always initial_suspend() { return {}; }
+
+    auto await_transform(Task<void> co) {
+      return await_transform(std::move(co.handle_.promise()));
+    }
+
+    auto await_transform(promise_type&& awaited) {
+      struct Awaitable {
+        promise_type&& awaited;
+
+        bool await_ready() { return false; }
+
+        std::coroutine_handle<> await_suspend(
+            const std::coroutine_handle<> handle) {
+          // Register our handle to be resumed once the awaited promise's coroutine
+          // finishes, and then resume that coroutine.
+          awaited.registered_handle_ = handle;
+          return std::coroutine_handle<promise_type>::from_promise(awaited);
+        }
+
+        void await_resume() {}
+
+       private:
+      };
+
+      return Awaitable{std::move(awaited)};
+    }
+
+    void return_void() {}
+
+    // At final suspend resume our registered handle.
+    auto final_suspend() noexcept {
+      struct FinalSuspendAwaitable final {
+        bool await_ready() noexcept { return false; }
+
+        std::coroutine_handle<> await_suspend(
+            std::coroutine_handle<> h) noexcept {
+          return to_resume;
+        }
+
+        void await_resume() noexcept {}
+
+        std::coroutine_handle<> to_resume;
+      };
+
+      return FinalSuspendAwaitable{registered_handle_};
+    }
+
+   private:
+    std::coroutine_handle<promise_type> my_handle() {
+      return std::coroutine_handle<promise_type>::from_promise(*this);
+    }
+
+    std::coroutine_handle<> registered_handle_;
+  };
+
+  ~Task() {
+    // Teach llvm that we are only ever destroyed when the coroutine body is done,
+    // so there is no need for the jump table in the destroy function. Our coroutine
+    // library doesn't expose handles to the user, so we know this constraint isn't
+    // violated.
+    if (!handle_.done()) {
+      __builtin_unreachable();
+    }
+
+    handle_.destroy();
+  }
+
+ private:
+  explicit Task(const std::coroutine_handle<promise_type> handle)
+      : handle_(handle) {}
+
+  const std::coroutine_handle<promise_type> handle_;
+};
+
+Task<void> Qux() { co_return; }
+Task<void> Baz() { co_await Qux(); }
+Task<void> Bar() { co_await Baz(); }
+
+// CHECK: _Z3Quxv.destroy:{{.*}}
+// CHECK-NEXT: #
+// CHECK-NEXT: jmp	_ZdlPv
+
+// CHECK: _Z3Bazv.destroy:{{.*}}
+// CHECK-NEXT: #
+// CHECK-NEXT: jmp	_ZdlPv
+
+// CHECK: _Z3Barv.destroy:{{.*}}
+// CHECK-NEXT: #
+// CHECK-NEXT: jmp	_ZdlPv

diff  --git a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
index 51eb8ebf0369..627886316730 100644
--- a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
@@ -1777,8 +1777,15 @@ static void insertSpills(const FrameDataInfo &FrameData, coro::Shape &Shape) {
     for (auto *DVI : DIs)
       DVI->replaceUsesOfWith(Alloca, G);
 
-    for (Instruction *I : UsersToUpdate)
+    for (Instruction *I : UsersToUpdate) {
+      // It is meaningless to remain the lifetime intrinsics refer for the
+      // member of coroutine frames and the meaningless lifetime intrinsics
+      // are possible to block further optimizations.
+      if (I->isLifetimeStartOrEnd())
+        continue;
+
       I->replaceUsesOfWith(Alloca, G);
+    }
   }
   Builder.SetInsertPoint(Shape.getInsertPtAfterFramePtr());
   for (const auto &A : FrameData.Allocas) {

diff  --git a/llvm/test/Transforms/Coroutines/coro-split-no-lieftime.ll b/llvm/test/Transforms/Coroutines/coro-split-no-lieftime.ll
new file mode 100644
index 000000000000..bdd3747ecad6
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-split-no-lieftime.ll
@@ -0,0 +1,62 @@
+; Tests that the meaningless lifetime intrinsics could be removed after corosplit.
+; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
+
+define ptr @f(i1 %n) presplitcoroutine {
+entry:
+  %x = alloca i64
+  %y = alloca i64
+  %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
+  %size = call i32 @llvm.coro.size.i32()
+  %alloc = call ptr @malloc(i32 %size)
+  %hdl = call ptr @llvm.coro.begin(token %id, ptr %alloc)
+  br i1 %n, label %flag_true, label %flag_false
+
+flag_true:
+  call void @llvm.lifetime.start.p0(i64 8, ptr %x)
+  br label %merge
+
+flag_false:
+  call void @llvm.lifetime.start.p0(i64 8, ptr %y)
+  br label %merge
+
+merge:
+  %phi = phi ptr [ %x, %flag_true ], [ %y, %flag_false ]
+  store i8 1, ptr %phi
+  %sp1 = call i8 @llvm.coro.suspend(token none, i1 false)
+  switch i8 %sp1, label %suspend [i8 0, label %resume
+                                  i8 1, label %cleanup]
+resume:
+  call void @print(ptr %phi)
+  call void @llvm.lifetime.end.p0(i64 8, ptr %x)
+  call void @llvm.lifetime.end.p0(i64 8, ptr %y)
+  br label %cleanup
+
+cleanup:
+  %mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
+  call void @free(ptr %mem)
+  br label %suspend
+
+suspend:
+  call i1 @llvm.coro.end(ptr %hdl, i1 0)
+  ret ptr %hdl
+}
+
+; CHECK-NOT: call{{.*}}@llvm.lifetime
+
+declare ptr @llvm.coro.free(token, ptr)
+declare i32 @llvm.coro.size.i32()
+declare i8  @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(ptr)
+declare void @llvm.coro.destroy(ptr)
+
+declare token @llvm.coro.id(i32, ptr, ptr, ptr)
+declare i1 @llvm.coro.alloc(token)
+declare ptr @llvm.coro.begin(token, ptr)
+declare i1 @llvm.coro.end(ptr, i1)
+
+declare void @llvm.lifetime.start.p0(i64, ptr nocapture)
+declare void @llvm.lifetime.end.p0(i64, ptr nocapture)
+
+declare void @print(ptr)
+declare noalias ptr @malloc(i32)
+declare void @free(ptr)


        


More information about the cfe-commits mailing list