Skip to content

Commit 230d6f9

Browse files
committed
[Coroutines] Remove lifetime intrinsics for spliied allocas in coroutine frames
Closing #56919 It is meaningless to preserve the lifetime markers for the spilled allocas in the coroutine frames and it would block some optimizations too.
1 parent 38c2366 commit 230d6f9

File tree

3 files changed

+189
-1
lines changed

3 files changed

+189
-1
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Test for PR56919. Tests the destroy function contains the call to delete function only.
2+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 %s -O3 -S -o - | FileCheck %s
3+
4+
#include "Inputs/coroutine.h"
5+
6+
namespace std {
7+
8+
template <typename T> struct remove_reference { using type = T; };
9+
template <typename T> struct remove_reference<T &> { using type = T; };
10+
template <typename T> struct remove_reference<T &&> { using type = T; };
11+
12+
template <typename T>
13+
constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
14+
return static_cast<typename std::remove_reference<T>::type &&>(t);
15+
}
16+
17+
}
18+
19+
template <typename T>
20+
class Task final {
21+
public:
22+
using value_type = T;
23+
24+
class promise_type final {
25+
public:
26+
Task<void> get_return_object() { return Task<void>(std::coroutine_handle<promise_type>::from_promise(*this)); }
27+
28+
void unhandled_exception();
29+
30+
std::suspend_always initial_suspend() { return {}; }
31+
32+
auto await_transform(Task<void> co) {
33+
return await_transform(std::move(co.handle_.promise()));
34+
}
35+
36+
auto await_transform(promise_type&& awaited) {
37+
struct Awaitable {
38+
promise_type&& awaited;
39+
40+
bool await_ready() { return false; }
41+
42+
std::coroutine_handle<> await_suspend(
43+
const std::coroutine_handle<> handle) {
44+
// Register our handle to be resumed once the awaited promise's coroutine
45+
// finishes, and then resume that coroutine.
46+
awaited.registered_handle_ = handle;
47+
return std::coroutine_handle<promise_type>::from_promise(awaited);
48+
}
49+
50+
void await_resume() {}
51+
52+
private:
53+
};
54+
55+
return Awaitable{std::move(awaited)};
56+
}
57+
58+
void return_void() {}
59+
60+
// At final suspend resume our registered handle.
61+
auto final_suspend() noexcept {
62+
struct FinalSuspendAwaitable final {
63+
bool await_ready() noexcept { return false; }
64+
65+
std::coroutine_handle<> await_suspend(
66+
std::coroutine_handle<> h) noexcept {
67+
return to_resume;
68+
}
69+
70+
void await_resume() noexcept {}
71+
72+
std::coroutine_handle<> to_resume;
73+
};
74+
75+
return FinalSuspendAwaitable{registered_handle_};
76+
}
77+
78+
private:
79+
std::coroutine_handle<promise_type> my_handle() {
80+
return std::coroutine_handle<promise_type>::from_promise(*this);
81+
}
82+
83+
std::coroutine_handle<> registered_handle_;
84+
};
85+
86+
~Task() {
87+
// Teach llvm that we are only ever destroyed when the coroutine body is done,
88+
// so there is no need for the jump table in the destroy function. Our coroutine
89+
// library doesn't expose handles to the user, so we know this constraint isn't
90+
// violated.
91+
if (!handle_.done()) {
92+
__builtin_unreachable();
93+
}
94+
95+
handle_.destroy();
96+
}
97+
98+
private:
99+
explicit Task(const std::coroutine_handle<promise_type> handle)
100+
: handle_(handle) {}
101+
102+
const std::coroutine_handle<promise_type> handle_;
103+
};
104+
105+
Task<void> Qux() { co_return; }
106+
Task<void> Baz() { co_await Qux(); }
107+
Task<void> Bar() { co_await Baz(); }
108+
109+
// CHECK: _Z3Quxv.destroy:{{.*}}
110+
// CHECK-NEXT: #
111+
// CHECK-NEXT: jmp _ZdlPv
112+
113+
// CHECK: _Z3Bazv.destroy:{{.*}}
114+
// CHECK-NEXT: #
115+
// CHECK-NEXT: jmp _ZdlPv
116+
117+
// CHECK: _Z3Barv.destroy:{{.*}}
118+
// CHECK-NEXT: #
119+
// CHECK-NEXT: jmp _ZdlPv

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1777,8 +1777,15 @@ static void insertSpills(const FrameDataInfo &FrameData, coro::Shape &Shape) {
17771777
for (auto *DVI : DIs)
17781778
DVI->replaceUsesOfWith(Alloca, G);
17791779

1780-
for (Instruction *I : UsersToUpdate)
1780+
for (Instruction *I : UsersToUpdate) {
1781+
// It is meaningless to remain the lifetime intrinsics refer for the
1782+
// member of coroutine frames and the meaningless lifetime intrinsics
1783+
// are possible to block further optimizations.
1784+
if (I->isLifetimeStartOrEnd())
1785+
continue;
1786+
17811787
I->replaceUsesOfWith(Alloca, G);
1788+
}
17821789
}
17831790
Builder.SetInsertPoint(Shape.getInsertPtAfterFramePtr());
17841791
for (const auto &A : FrameData.Allocas) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
; Tests that the meaningless lifetime intrinsics could be removed after corosplit.
2+
; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
3+
4+
define ptr @f(i1 %n) presplitcoroutine {
5+
entry:
6+
%x = alloca i64
7+
%y = alloca i64
8+
%id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
9+
%size = call i32 @llvm.coro.size.i32()
10+
%alloc = call ptr @malloc(i32 %size)
11+
%hdl = call ptr @llvm.coro.begin(token %id, ptr %alloc)
12+
br i1 %n, label %flag_true, label %flag_false
13+
14+
flag_true:
15+
call void @llvm.lifetime.start.p0(i64 8, ptr %x)
16+
br label %merge
17+
18+
flag_false:
19+
call void @llvm.lifetime.start.p0(i64 8, ptr %y)
20+
br label %merge
21+
22+
merge:
23+
%phi = phi ptr [ %x, %flag_true ], [ %y, %flag_false ]
24+
store i8 1, ptr %phi
25+
%sp1 = call i8 @llvm.coro.suspend(token none, i1 false)
26+
switch i8 %sp1, label %suspend [i8 0, label %resume
27+
i8 1, label %cleanup]
28+
resume:
29+
call void @print(ptr %phi)
30+
call void @llvm.lifetime.end.p0(i64 8, ptr %x)
31+
call void @llvm.lifetime.end.p0(i64 8, ptr %y)
32+
br label %cleanup
33+
34+
cleanup:
35+
%mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
36+
call void @free(ptr %mem)
37+
br label %suspend
38+
39+
suspend:
40+
call i1 @llvm.coro.end(ptr %hdl, i1 0)
41+
ret ptr %hdl
42+
}
43+
44+
; CHECK-NOT: call{{.*}}@llvm.lifetime
45+
46+
declare ptr @llvm.coro.free(token, ptr)
47+
declare i32 @llvm.coro.size.i32()
48+
declare i8 @llvm.coro.suspend(token, i1)
49+
declare void @llvm.coro.resume(ptr)
50+
declare void @llvm.coro.destroy(ptr)
51+
52+
declare token @llvm.coro.id(i32, ptr, ptr, ptr)
53+
declare i1 @llvm.coro.alloc(token)
54+
declare ptr @llvm.coro.begin(token, ptr)
55+
declare i1 @llvm.coro.end(ptr, i1)
56+
57+
declare void @llvm.lifetime.start.p0(i64, ptr nocapture)
58+
declare void @llvm.lifetime.end.p0(i64, ptr nocapture)
59+
60+
declare void @print(ptr)
61+
declare noalias ptr @malloc(i32)
62+
declare void @free(ptr)

0 commit comments

Comments
 (0)