Skip to content

Commit

Permalink
[Coroutines] Set presplit attribute in Clang instead of CoroEarly pass
Browse files Browse the repository at this point in the history
Presplit coroutines cannot be inlined. During AlwaysInliner we check if a function is a presplit coroutine, if so we skip inlining.
The presplit coroutine attributes are set in CoroEarly pass.
However in O0 pipeline, AlwaysInliner runs before CoroEarly, so the attribute isn't set yet and will still inline the coroutine.
This causes Clang to crash: https://bugs.llvm.org/show_bug.cgi?id=49920

To fix this, we set the attributes in the Clang front-end instead of in CoroEarly pass.

Reviewed By: rjmccall, ChuanqiXu

Differential Revision: https://reviews.llvm.org/D100282
  • Loading branch information
lxfind committed Apr 18, 2021
1 parent c0211e8 commit fa6b54c
Show file tree
Hide file tree
Showing 16 changed files with 132 additions and 63 deletions.
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CGCoroutine.cpp
Expand Up @@ -558,6 +558,8 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
CurCoro.Data->SuspendBB = RetBB;
assert(ShouldEmitLifetimeMarkers &&
"Must emit lifetime intrinsics for coroutines");
// CORO_PRESPLIT_ATTR = UNPREPARED_FOR_SPLIT
CurFn->addFnAttr("coroutine.presplit", "0");

// Backend is allowed to elide memory allocations, to help it, emit
// auto mem = coro.alloc() ? 0 : ... allocation code ...;
Expand Down
54 changes: 54 additions & 0 deletions clang/test/CodeGenCoroutines/coro-always-inline-resume.cpp
@@ -0,0 +1,54 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -fexperimental-new-pass-manager -O0 %s -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -fexperimental-new-pass-manager -fno-inline -O0 %s -o - | FileCheck %s

// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -O0 %s -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -fno-inline -O0 %s -o - | FileCheck %s

namespace std {
namespace experimental {

struct handle {};

struct awaitable {
bool await_ready() noexcept { return true; }
// CHECK-NOT: await_suspend
inline void __attribute__((__always_inline__)) await_suspend(handle) noexcept {}
bool await_resume() noexcept { return true; }
};

template <typename T>
struct coroutine_handle {
static handle from_address(void *address) noexcept { return {}; }
};

template <typename T = void>
struct coroutine_traits {
struct promise_type {
awaitable initial_suspend() { return {}; }
awaitable final_suspend() noexcept { return {}; }
void return_void() {}
T get_return_object() { return T(); }
void unhandled_exception() {}
};
};
} // namespace experimental
} // namespace std

// CHECK-LABEL: @_Z3foov
// CHECK-LABEL: entry:
// CHECK-NEXT: %this.addr.i{{[0-9]*}} = alloca %"struct.std::experimental::awaitable"*, align 8
// CHECK-NEXT: %this.addr.i{{[0-9]*}} = alloca %"struct.std::experimental::awaitable"*, align 8
// CHECK: [[CAST0:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[CAST0]])
// CHECK: [[CAST1:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[CAST1]])

// CHECK: [[CAST2:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[CAST2]])
// CHECK: [[CAST3:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[CAST3]])
void foo() { co_return; }
114 changes: 64 additions & 50 deletions clang/test/CodeGenCoroutines/coro-always-inline.cpp
@@ -1,54 +1,68 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -fexperimental-new-pass-manager -O0 %s -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -fexperimental-new-pass-manager -fno-inline -O0 %s -o - | FileCheck %s

// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -O0 %s -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \
// RUN: -fno-inline -O0 %s -o - | FileCheck %s

namespace std {
namespace experimental {

struct handle {};

struct awaitable {
bool await_ready() noexcept { return true; }
// CHECK-NOT: await_suspend
inline void __attribute__((__always_inline__)) await_suspend(handle) noexcept {}
bool await_resume() noexcept { return true; }
};
// RUN: %clang -cc1 -triple x86_64-unknown-linux-gnu -std=c++2a %s -emit-llvm -disable-llvm-passes -o - | FileCheck %s
// RUN: %clang -cc1 -triple x86_64-unknown-linux-gnu -std=c++2a %s -emit-llvm -disable-llvm-passes -o - | opt -always-inline -S | FileCheck --check-prefix=INLINE %s

template <typename T>
struct coroutine_handle {
static handle from_address(void *address) noexcept { return {}; }
};
#include "Inputs/coroutine.h"

namespace coro = std::experimental::coroutines_v1;

class task {
public:
class promise_type {
public:
task get_return_object() noexcept;
coro::suspend_always initial_suspend() noexcept;
void return_void() noexcept;
void unhandled_exception() noexcept;

struct final_awaiter {
bool await_ready() noexcept;
void await_suspend(coro::coroutine_handle<promise_type> h) noexcept;
void await_resume() noexcept;
};

template <typename T = void>
struct coroutine_traits {
struct promise_type {
awaitable initial_suspend() { return {}; }
awaitable final_suspend() noexcept { return {}; }
void return_void() {}
T get_return_object() { return T(); }
void unhandled_exception() {}
final_awaiter final_suspend() noexcept;

coro::coroutine_handle<> continuation;
};

task(task &&t) noexcept;
~task();

class awaiter {
public:
bool await_ready() noexcept;
void await_suspend(coro::coroutine_handle<> continuation) noexcept;
void await_resume() noexcept;

private:
friend task;
explicit awaiter(coro::coroutine_handle<promise_type> h) noexcept;
coro::coroutine_handle<promise_type> coro_;
};

awaiter operator co_await() &&noexcept;

private:
explicit task(coro::coroutine_handle<promise_type> h) noexcept;
coro::coroutine_handle<promise_type> coro_;
};
} // namespace experimental
} // namespace std

// CHECK-LABEL: @_Z3foov
// CHECK-LABEL: entry:
// CHECK-NEXT: %this.addr.i{{[0-9]*}} = alloca %"struct.std::experimental::awaitable"*, align 8
// CHECK-NEXT: %this.addr.i{{[0-9]*}} = alloca %"struct.std::experimental::awaitable"*, align 8
// CHECK: [[CAST0:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[CAST0]])
// CHECK: [[CAST1:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[CAST1]])

// CHECK: [[CAST2:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[CAST2]])
// CHECK: [[CAST3:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"** %this.addr.i{{[0-9]*}} to i8*
// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[CAST3]])
void foo() { co_return; }

task cee();

__attribute__((always_inline)) inline task bar() {
co_await cee();
co_return;
}

task foo() {
co_await bar();
co_return;
}

// check that Clang front-end will tag bar with both alwaysinline and coroutine presplit
// CHECK: define linkonce_odr void @_Z3barv({{.*}}) #[[ATTR:[0-9]+]] {{.*}}
// CHECK: attributes #[[ATTR]] = { alwaysinline {{.*}} "coroutine.presplit"="0" {{.*}}}

// check that bar is not inlined even it's marked as always_inline
// INLINE-LABEL: define dso_local void @_Z3foov(
// INLINE: call void @_Z3barv(
1 change: 0 additions & 1 deletion llvm/lib/Transforms/Coroutines/CoroEarly.cpp
Expand Up @@ -179,7 +179,6 @@ bool Lowerer::lowerEarlyIntrinsics(Function &F) {
// with a coroutine attribute.
if (auto *CII = cast<CoroIdInst>(&I)) {
if (CII->getInfo().isPreSplit()) {
F.addFnAttr(CORO_PRESPLIT_ATTR, UNPREPARED_FOR_SPLIT);
setCannotDuplicate(CII);
CII->setCoroutineSelf();
CoroId = cast<CoroIdInst>(&I);
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/coro-debug-O2.ll
Expand Up @@ -9,7 +9,7 @@
; CHECK: ![[PROMISEVAR_RESUME]] = !DILocalVariable(name: "__promise"
%promise_type = type { i32, i32, double }

define void @f() !dbg !8 {
define void @f() "coroutine.presplit"="0" !dbg !8 {
entry:
%__promise = alloca %promise_type, align 8
%0 = bitcast %promise_type* %__promise to i8*
Expand Down
Expand Up @@ -63,7 +63,7 @@
; CHECK: ![[IVAR_RESUME]] = !DILocalVariable(name: "i"
; CHECK: ![[JVAR_RESUME]] = !DILocalVariable(name: "j"
; CHECK: ![[JDBGLOC_RESUME]] = !DILocation(line: 32, column: 7, scope: ![[RESUME_SCOPE]])
define void @f() {
define void @f() "coroutine.presplit"="0" {
entry:
%__promise = alloca i8, align 8
%i = alloca i32, align 4
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/coro-split-01.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -S -enable-coroutines -O2 | FileCheck %s
; RUN: opt < %s -S -enable-coroutines -passes='default<O2>' | FileCheck %s

define i8* @f() {
define i8* @f() "coroutine.presplit"="0" {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/coro-split-recursive.ll
Expand Up @@ -13,7 +13,7 @@ declare i8 @llvm.coro.suspend(token, i1)
; CHECK: call void @foo()
; CHECK-LABEL: define {{.*}}void @foo.destroy(

define void @foo() {
define void @foo() "coroutine.presplit"="0" {
entry:
%__promise = alloca i32, align 8
%0 = bitcast i32* %__promise to i8*
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/ex0.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -enable-coroutines -O2 -preserve-alignment-assumptions-during-inlining=false -S | FileCheck %s
; RUN: opt < %s -enable-coroutines -aa-pipeline=basic-aa -passes='default<O2>' -preserve-alignment-assumptions-during-inlining=false -S | FileCheck %s

define i8* @f(i32 %n) {
define i8* @f(i32 %n) "coroutine.presplit"="0" {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32()
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/ex1.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -O2 -enable-coroutines -preserve-alignment-assumptions-during-inlining=false -S | FileCheck %s
; RUN: opt < %s -aa-pipeline=basic-aa -passes='default<O2>' -enable-coroutines -preserve-alignment-assumptions-during-inlining=false -S | FileCheck %s

define i8* @f(i32 %n) {
define i8* @f(i32 %n) "coroutine.presplit"="0" {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32()
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/ex2.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
; RUN: opt < %s -passes='default<O2>' -enable-coroutines -S | FileCheck %s

define i8* @f(i32 %n) {
define i8* @f(i32 %n) "coroutine.presplit"="0" {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/ex3.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
; RUN: opt < %s -aa-pipeline=basic-aa -passes='default<O2>' -enable-coroutines -S | FileCheck %s

define i8* @f(i32 %n) {
define i8* @f(i32 %n) "coroutine.presplit"="0" {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32()
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/ex4.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
; RUN: opt < %s -passes='default<O2>' -enable-coroutines -S | FileCheck %s

define i8* @f(i32 %n) {
define i8* @f(i32 %n) "coroutine.presplit"="0" {
entry:
%promise = alloca i32
%pv = bitcast i32* %promise to i8*
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/ex5.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -O2 -enable-coroutines -preserve-alignment-assumptions-during-inlining=false -S | FileCheck %s
; RUN: opt < %s -aa-pipeline=basic-aa -passes='default<O2>' -enable-coroutines -preserve-alignment-assumptions-during-inlining=false -S | FileCheck %s

define i8* @f(i32 %n) {
define i8* @f(i32 %n) "coroutine.presplit"="0" {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32()
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/phi-coro-end.ll
Expand Up @@ -2,7 +2,7 @@
; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
; RUN: opt < %s -aa-pipeline=basic-aa -passes='default<O2>' -enable-coroutines -S | FileCheck %s

define i8* @f(i32 %n) {
define i8* @f(i32 %n) "coroutine.presplit"="0" {
entry:
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32()
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Coroutines/restart-trigger.ll
Expand Up @@ -12,7 +12,7 @@
; CHECK: CoroSplit: Processing coroutine 'f' state: 0
; CHECK-NEXT: CoroSplit: Processing coroutine 'f' state: 1

define void @f() {
define void @f() "coroutine.presplit"="0" {
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
%size = call i32 @llvm.coro.size.i32()
%alloc = call i8* @malloc(i32 %size)
Expand Down

0 comments on commit fa6b54c

Please sign in to comment.