[coroutines] Adding builtins for coroutine intrinsics and backendutil support.

Summary:
With this commit simple coroutines can be created in plain C using coroutine builtins.

Reviewers: rnk, EricWF, rsmith

Subscribers: modocache, mgorny, mehdi_amini, beanz, cfe-commits

Differential Revision: https://reviews.llvm.org/D24373

llvm-svn: 283155
This commit is contained in:
Gor Nishanov 2016-10-03 22:44:48 +00:00
parent e040533ece
commit 97e3b6d895
9 changed files with 310 additions and 0 deletions

View File

@ -1865,6 +1865,82 @@ The types ``T`` currently supported are:
Note that the compiler does not guarantee that non-temporal loads or stores
will be used.
C++ Coroutines support builtins
--------------------------------
.. warning::
This is a work in progress. Compatibility across Clang/LLVM releases is not
guaranteed.
Clang provides experimental builtins to support C++ Coroutines as defined by
http://wg21.link/P0057. The following four are intended to be used by the
standard library to implement `std::experimental::coroutine_handle` type.
**Syntax**:
.. code-block:: c
void __builtin_coro_resume(void *addr);
void __builtin_coro_destroy(void *addr);
bool __builtin_coro_done(void *addr);
void *__builtin_coro_promise(void *addr, int alignment, bool from_promise)
**Example of use**:
.. code-block:: c++
template <> struct coroutine_handle<void> {
void resume() const { __builtin_coro_resume(ptr); }
void destroy() const { __builtin_coro_destroy(ptr); }
bool done() const { return __builtin_coro_done(ptr); }
// ...
protected:
void *ptr;
};
template <typename Promise> struct coroutine_handle : coroutine_handle<> {
// ...
Promise &promise() const {
return *reinterpret_cast<Promise *>(
__builtin_coro_promise(ptr, alignof(Promise), /*from-promise=*/false));
}
static coroutine_handle from_promise(Promise &promise) {
coroutine_handle p;
p.ptr = __builtin_coro_promise(&promise, alignof(Promise),
/*from-promise=*/true);
return p;
}
};
Other coroutine builtins are either for internal clang use or for use during
development of the coroutine feature. See `Coroutines in LLVM
<http://llvm.org/docs/Coroutines.html#intrinsics>`_ for
more information on their semantics. Note that builtins matching the intrinsics
that take token as the first parameter (llvm.coro.begin, llvm.coro.alloc,
llvm.coro.free and llvm.coro.suspend) omit the token parameter and fill it to
an appropriate value during the emission.
**Syntax**:
.. code-block:: c
size_t __builtin_coro_size()
void *__builtin_coro_frame()
void *__builtin_coro_free(void *coro_frame)
void *__builtin_coro_id(int align, void *promise, void *fnaddr, void *parts)
bool __builtin_coro_alloc()
void *__builtin_coro_begin(void *memory)
void __builtin_coro_end(void *coro_frame, bool unwind)
char __builtin_coro_suspend(bool final)
bool __builtin_coro_param(void *original, void *copy)
Note that there is no builtin matching the `llvm.coro.save` intrinsic. LLVM
automatically will insert one if the first argument to `llvm.coro.suspend` is
token `none`. If a user calls `__builin_suspend`, clang will insert `token none`
as the first argument to the intrinsic.
Non-standard C++11 Attributes
=============================

View File

@ -1330,6 +1330,22 @@ BUILTIN(__builtin___get_unsafe_stack_ptr, "v*", "Fn")
BUILTIN(__builtin_nontemporal_store, "v.", "t")
BUILTIN(__builtin_nontemporal_load, "v.", "t")
// Coroutine intrinsics.
BUILTIN(__builtin_coro_resume, "vv*", "")
BUILTIN(__builtin_coro_destroy, "vv*", "")
BUILTIN(__builtin_coro_done, "bv*", "n")
BUILTIN(__builtin_coro_promise, "v*v*IiIb", "n")
BUILTIN(__builtin_coro_size, "z", "n")
BUILTIN(__builtin_coro_frame, "v*", "n")
BUILTIN(__builtin_coro_free, "v*v*", "n")
BUILTIN(__builtin_coro_id, "v*Iiv*v*v*", "n")
BUILTIN(__builtin_coro_alloc, "b", "n")
BUILTIN(__builtin_coro_begin, "v*v*", "n")
BUILTIN(__builtin_coro_end, "vv*Ib", "n")
BUILTIN(__builtin_coro_suspend, "cIb", "n")
BUILTIN(__builtin_coro_param, "bv*v*", "n")
// OpenCL v2.0 s6.13.16, s9.17.3.5 - Pipe functions.
// We need the generic prototype, since the packet type could be anything.
LANGBUILTIN(read_pipe, "i.", "tn", OCLC20_LANG)

View File

@ -41,6 +41,7 @@
#include "llvm/Target/TargetMachine.h"
#include "llvm/Target/TargetOptions.h"
#include "llvm/Target/TargetSubtargetInfo.h"
#include "llvm/Transforms/Coroutines.h"
#include "llvm/Transforms/IPO.h"
#include "llvm/Transforms/IPO/AlwaysInliner.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
@ -401,6 +402,9 @@ void EmitAssemblyHelper::CreatePasses(legacy::PassManager &MPM,
addDataFlowSanitizerPass);
}
if (LangOpts.CoroutinesTS)
addCoroutinePassesToExtensionPoints(PMBuilder);
if (LangOpts.Sanitize.hasOneOf(SanitizerKind::Efficiency)) {
PMBuilder.addExtension(PassManagerBuilder::EP_OptimizerLast,
addEfficiencySanitizerPass);

View File

@ -2135,6 +2135,39 @@ RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD,
break;
}
case Builtin::BI__builtin_coro_size: {
auto & Context = getContext();
auto SizeTy = Context.getSizeType();
auto T = Builder.getIntNTy(Context.getTypeSize(SizeTy));
Value *F = CGM.getIntrinsic(Intrinsic::coro_size, T);
return RValue::get(Builder.CreateCall(F));
}
case Builtin::BI__builtin_coro_id:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_id);
case Builtin::BI__builtin_coro_promise:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_promise);
case Builtin::BI__builtin_coro_resume:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_resume);
case Builtin::BI__builtin_coro_frame:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_frame);
case Builtin::BI__builtin_coro_free:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_free);
case Builtin::BI__builtin_coro_destroy:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_destroy);
case Builtin::BI__builtin_coro_done:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_done);
case Builtin::BI__builtin_coro_alloc:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_alloc);
case Builtin::BI__builtin_coro_begin:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_begin);
case Builtin::BI__builtin_coro_end:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_end);
case Builtin::BI__builtin_coro_suspend:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_suspend);
case Builtin::BI__builtin_coro_param:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_param);
// OpenCL v2.0 s6.13.16.2, Built-in pipe read and write functions
case Builtin::BIread_pipe:
case Builtin::BIwrite_pipe: {

View File

@ -0,0 +1,100 @@
//===----- CGCoroutine.cpp - Emit LLVM Code for C++ coroutines ------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This contains code dealing with C++ code generation of coroutines.
//
//===----------------------------------------------------------------------===//
#include "CodeGenFunction.h"
using namespace clang;
using namespace CodeGen;
namespace clang {
namespace CodeGen {
struct CGCoroData {
// Stores the llvm.coro.id emitted in the function so that we can supply it
// as the first argument to coro.begin, coro.alloc and coro.free intrinsics.
// Note: llvm.coro.id returns a token that cannot be directly expressed in a
// builtin.
llvm::CallInst *CoroId = nullptr;
// If coro.id came from the builtin, remember the expression to give better
// diagnostic. If CoroIdExpr is nullptr, the coro.id was created by
// EmitCoroutineBody.
CallExpr const *CoroIdExpr = nullptr;
};
}
}
clang::CodeGen::CodeGenFunction::CGCoroInfo::CGCoroInfo() {}
CodeGenFunction::CGCoroInfo::~CGCoroInfo() {}
static bool createCoroData(CodeGenFunction &CGF,
CodeGenFunction::CGCoroInfo &CurCoro,
llvm::CallInst *CoroId, CallExpr const *CoroIdExpr) {
if (CurCoro.Data) {
if (CurCoro.Data->CoroIdExpr)
CGF.CGM.Error(CoroIdExpr->getLocStart(),
"only one __builtin_coro_id can be used in a function");
else if (CoroIdExpr)
CGF.CGM.Error(CoroIdExpr->getLocStart(),
"__builtin_coro_id shall not be used in a C++ coroutine");
else
llvm_unreachable("EmitCoroutineBodyStatement called twice?");
return false;
}
CurCoro.Data = std::unique_ptr<CGCoroData>(new CGCoroData);
CurCoro.Data->CoroId = CoroId;
CurCoro.Data->CoroIdExpr = CoroIdExpr;
return true;
}
// Emit coroutine intrinsic and patch up arguments of the token type.
RValue CodeGenFunction::EmitCoroutineIntrinsic(const CallExpr *E,
unsigned int IID) {
SmallVector<llvm::Value *, 8> Args;
switch (IID) {
default:
break;
// The following three intrinsics take a token parameter referring to a token
// returned by earlier call to @llvm.coro.id. Since we cannot represent it in
// builtins, we patch it up here.
case llvm::Intrinsic::coro_alloc:
case llvm::Intrinsic::coro_begin:
case llvm::Intrinsic::coro_free: {
if (CurCoro.Data && CurCoro.Data->CoroId) {
Args.push_back(CurCoro.Data->CoroId);
break;
}
CGM.Error(E->getLocStart(), "this builtin expect that __builtin_coro_id has"
" been used earlier in this function");
// Fallthrough to the next case to add TokenNone as the first argument.
}
// @llvm.coro.suspend takes a token parameter. Add token 'none' as the first
// argument.
case llvm::Intrinsic::coro_suspend:
Args.push_back(llvm::ConstantTokenNone::get(getLLVMContext()));
break;
}
for (auto &Arg : E->arguments())
Args.push_back(EmitScalarExpr(Arg));
llvm::Value *F = CGM.getIntrinsic(IID);
llvm::CallInst *Call = Builder.CreateCall(F, Args);
// If we see @llvm.coro.id remember it in the CoroData. We will update
// coro.alloc, coro.begin and coro.free intrinsics to refer to it.
if (IID == llvm::Intrinsic::coro_id) {
createCoroData(*this, CurCoro, Call, E);
}
return RValue::get(Call);
}

View File

@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS
BitReader
BitWriter
Core
Coroutines
Coverage
IPO
IRReader
@ -42,6 +43,7 @@ add_clang_library(clangCodeGen
CGCall.cpp
CGClass.cpp
CGCleanup.cpp
CGCoroutine.cpp
CGDebugInfo.cpp
CGDecl.cpp
CGDeclCXX.cpp

View File

@ -88,6 +88,7 @@ class BlockFieldFlags;
class RegionCodeGenTy;
class TargetCodeGenInfo;
struct OMPTaskDataTy;
struct CGCoroData;
/// The kind of evaluation to perform on values of a particular
/// type. Basically, is the code in CGExprScalar, CGExprComplex, or
@ -155,6 +156,16 @@ public:
QualType FnRetTy;
llvm::Function *CurFn;
// Holds coroutine data if the current function is a coroutine. We use a
// wrapper to manage its lifetime, so that we don't have to define CGCoroData
// in this header.
struct CGCoroInfo {
std::unique_ptr<CGCoroData> Data;
CGCoroInfo();
~CGCoroInfo();
};
CGCoroInfo CurCoro;
/// CurGD - The GlobalDecl for the current function being compiled.
GlobalDecl CurGD;
@ -2290,6 +2301,8 @@ public:
void EmitObjCAtSynchronizedStmt(const ObjCAtSynchronizedStmt &S);
void EmitObjCAutoreleasePoolStmt(const ObjCAutoreleasePoolStmt &S);
RValue EmitCoroutineIntrinsic(const CallExpr *E, unsigned int IID);
void EnterCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock = false);
void ExitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock = false);

View File

@ -0,0 +1,10 @@
// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc18.0.0 -fcoroutines-ts -emit-llvm %s -o - -verify
void f() {
__builtin_coro_alloc(); // expected-error {{this builtin expect that __builtin_coro_id}}
__builtin_coro_begin(0); // expected-error {{this builtin expect that __builtin_coro_id}}
__builtin_coro_free(0); // expected-error {{this builtin expect that __builtin_coro_id}}
__builtin_coro_id(32, 0, 0, 0);
__builtin_coro_id(32, 0, 0, 0); // expected-error {{only one __builtin_coro_id}}
}

View File

@ -0,0 +1,56 @@
// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc18.0.0 -fcoroutines-ts -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s
void *myAlloc(long long);
// CHECK-LABEL: f(
void f(int n) {
// CHECK: %n.addr = alloca i32
// CHECK: %n_copy = alloca i32
// CHECK: %promise = alloca i32
int n_copy;
int promise;
// CHECK: %[[PROM_ADDR:.+]] = bitcast i32* %promise to i8*
// CHECK-NEXT: %[[COROID:.+]] = call token @llvm.coro.id(i32 32, i8* %[[PROM_ADDR]], i8* null, i8* null)
__builtin_coro_id(32, &promise, 0, 0);
// CHECK-NEXT: call i1 @llvm.coro.alloc(token %[[COROID]])
__builtin_coro_alloc();
// CHECK-NEXT: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
// CHECK-NEXT: %[[MEM:.+]] = call i8* @myAlloc(i64 %[[SIZE]])
// CHECK-NEXT: %[[BEG:.+]] = call i8* @llvm.coro.begin(token %[[COROID]], i8* %[[MEM]])
__builtin_coro_begin(myAlloc(__builtin_coro_size()));
// CHECK-NEXT: %[[FRAME1:.+]] = call i8* @llvm.coro.frame()
// CHECK-NEXT: call void @llvm.coro.resume(i8* %[[FRAME1]])
__builtin_coro_resume(__builtin_coro_frame());
// CHECK-NEXT: %[[FRAME2:.+]] = call i8* @llvm.coro.frame()
// CHECK-NEXT: call void @llvm.coro.destroy(i8* %[[FRAME2]])
__builtin_coro_destroy(__builtin_coro_frame());
// CHECK-NEXT: %[[FRAME3:.+]] = call i8* @llvm.coro.frame()
// CHECK-NEXT: call i1 @llvm.coro.done(i8* %[[FRAME3]])
__builtin_coro_done(__builtin_coro_frame());
// CHECK-NEXT: %[[FRAME4:.+]] = call i8* @llvm.coro.frame()
// CHECK-NEXT: call i8* @llvm.coro.promise(i8* %[[FRAME4]], i32 48, i1 false)
__builtin_coro_promise(__builtin_coro_frame(), 48, 0);
// CHECK-NEXT: %[[FRAME5:.+]] = call i8* @llvm.coro.frame()
// CHECK-NEXT: call i8* @llvm.coro.free(token %[[COROID]], i8* %[[FRAME5]])
__builtin_coro_free(__builtin_coro_frame());
// CHECK-NEXT: %[[FRAME6:.+]] = call i8* @llvm.coro.frame()
// CHECK-NEXT: call void @llvm.coro.end(i8* %[[FRAME6]], i1 false)
__builtin_coro_end(__builtin_coro_frame(), 0);
// CHECK-NEXT: call i8 @llvm.coro.suspend(token none, i1 true)
__builtin_coro_suspend(1);
// CHECK-NEXT: %[[N_ADDR:.+]] = bitcast i32* %n.addr to i8*
// CHECK-NEXT: %[[N_COPY_ADDR:.+]] = bitcast i32* %n_copy to i8*
// CHECK-NEXT: call i1 @llvm.coro.param(i8* %[[N_ADDR]], i8* %[[N_COPY_ADDR]])
__builtin_coro_param(&n, &n_copy);
}