From 230d6f93aaabae985e9fafd071bb3737533c6083 Mon Sep 17 00:00:00 2001 From: Chuanqi Xu Date: Fri, 5 Aug 2022 14:47:59 +0800 Subject: [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. --- clang/test/CodeGenCoroutines/pr56919.cpp | 119 +++++++++++++++++++++ llvm/lib/Transforms/Coroutines/CoroFrame.cpp | 9 +- .../Coroutines/coro-split-no-lieftime.ll | 62 +++++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 clang/test/CodeGenCoroutines/pr56919.cpp create mode 100644 llvm/test/Transforms/Coroutines/coro-split-no-lieftime.ll 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 struct remove_reference { using type = T; }; +template struct remove_reference { using type = T; }; +template struct remove_reference { using type = T; }; + +template +constexpr typename std::remove_reference::type&& move(T &&t) noexcept { + return static_cast::type &&>(t); +} + +} + +template +class Task final { + public: + using value_type = T; + + class promise_type final { + public: + Task get_return_object() { return Task(std::coroutine_handle::from_promise(*this)); } + + void unhandled_exception(); + + std::suspend_always initial_suspend() { return {}; } + + auto await_transform(Task 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::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 my_handle() { + return std::coroutine_handle::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 handle) + : handle_(handle) {} + + const std::coroutine_handle handle_; +}; + +Task Qux() { co_return; } +Task Baz() { co_await Qux(); } +Task 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) -- cgit v1.2.3