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) _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits