You've already forked linux-packaging-mono
Imported Upstream version 6.0.0.172
Former-commit-id: f3cc9b82f3e5bd8f0fd3ebc098f789556b44e9cd
This commit is contained in:
parent
8016999e4d
commit
64ac736ec5
11
external/llvm/lib/Transforms/Coroutines/CMakeLists.txt
vendored
Normal file
11
external/llvm/lib/Transforms/Coroutines/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
add_llvm_library(LLVMCoroutines
|
||||
Coroutines.cpp
|
||||
CoroCleanup.cpp
|
||||
CoroEarly.cpp
|
||||
CoroElide.cpp
|
||||
CoroFrame.cpp
|
||||
CoroSplit.cpp
|
||||
|
||||
DEPENDS
|
||||
intrinsics_gen
|
||||
)
|
137
external/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
vendored
Normal file
137
external/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
//===- CoroCleanup.cpp - Coroutine Cleanup Pass ---------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// This pass lowers all remaining coroutine intrinsics.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "CoroInternal.h"
|
||||
#include "llvm/IR/IRBuilder.h"
|
||||
#include "llvm/IR/InstIterator.h"
|
||||
#include "llvm/IR/LegacyPassManager.h"
|
||||
#include "llvm/Pass.h"
|
||||
#include "llvm/Transforms/Scalar.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
#define DEBUG_TYPE "coro-cleanup"
|
||||
|
||||
namespace {
|
||||
// Created on demand if CoroCleanup pass has work to do.
|
||||
struct Lowerer : coro::LowererBase {
|
||||
IRBuilder<> Builder;
|
||||
Lowerer(Module &M) : LowererBase(M), Builder(Context) {}
|
||||
bool lowerRemainingCoroIntrinsics(Function &F);
|
||||
};
|
||||
}
|
||||
|
||||
static void simplifyCFG(Function &F) {
|
||||
llvm::legacy::FunctionPassManager FPM(F.getParent());
|
||||
FPM.add(createCFGSimplificationPass());
|
||||
|
||||
FPM.doInitialization();
|
||||
FPM.run(F);
|
||||
FPM.doFinalization();
|
||||
}
|
||||
|
||||
static void lowerSubFn(IRBuilder<> &Builder, CoroSubFnInst *SubFn) {
|
||||
Builder.SetInsertPoint(SubFn);
|
||||
Value *FrameRaw = SubFn->getFrame();
|
||||
int Index = SubFn->getIndex();
|
||||
|
||||
auto *FrameTy = StructType::get(
|
||||
SubFn->getContext(), {Builder.getInt8PtrTy(), Builder.getInt8PtrTy()});
|
||||
PointerType *FramePtrTy = FrameTy->getPointerTo();
|
||||
|
||||
Builder.SetInsertPoint(SubFn);
|
||||
auto *FramePtr = Builder.CreateBitCast(FrameRaw, FramePtrTy);
|
||||
auto *Gep = Builder.CreateConstInBoundsGEP2_32(FrameTy, FramePtr, 0, Index);
|
||||
auto *Load = Builder.CreateLoad(Gep);
|
||||
|
||||
SubFn->replaceAllUsesWith(Load);
|
||||
}
|
||||
|
||||
bool Lowerer::lowerRemainingCoroIntrinsics(Function &F) {
|
||||
bool Changed = false;
|
||||
|
||||
for (auto IB = inst_begin(F), E = inst_end(F); IB != E;) {
|
||||
Instruction &I = *IB++;
|
||||
if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
|
||||
switch (II->getIntrinsicID()) {
|
||||
default:
|
||||
continue;
|
||||
case Intrinsic::coro_begin:
|
||||
II->replaceAllUsesWith(II->getArgOperand(1));
|
||||
break;
|
||||
case Intrinsic::coro_free:
|
||||
II->replaceAllUsesWith(II->getArgOperand(1));
|
||||
break;
|
||||
case Intrinsic::coro_alloc:
|
||||
II->replaceAllUsesWith(ConstantInt::getTrue(Context));
|
||||
break;
|
||||
case Intrinsic::coro_id:
|
||||
II->replaceAllUsesWith(ConstantTokenNone::get(Context));
|
||||
break;
|
||||
case Intrinsic::coro_subfn_addr:
|
||||
lowerSubFn(Builder, cast<CoroSubFnInst>(II));
|
||||
break;
|
||||
}
|
||||
II->eraseFromParent();
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Changed) {
|
||||
// After replacement were made we can cleanup the function body a little.
|
||||
simplifyCFG(F);
|
||||
}
|
||||
return Changed;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Top Level Driver
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
|
||||
struct CoroCleanup : FunctionPass {
|
||||
static char ID; // Pass identification, replacement for typeid
|
||||
|
||||
CoroCleanup() : FunctionPass(ID) {
|
||||
initializeCoroCleanupPass(*PassRegistry::getPassRegistry());
|
||||
}
|
||||
|
||||
std::unique_ptr<Lowerer> L;
|
||||
|
||||
// This pass has work to do only if we find intrinsics we are going to lower
|
||||
// in the module.
|
||||
bool doInitialization(Module &M) override {
|
||||
if (coro::declaresIntrinsics(M, {"llvm.coro.alloc", "llvm.coro.begin",
|
||||
"llvm.coro.subfn.addr", "llvm.coro.free",
|
||||
"llvm.coro.id"}))
|
||||
L = llvm::make_unique<Lowerer>(M);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool runOnFunction(Function &F) override {
|
||||
if (L)
|
||||
return L->lowerRemainingCoroIntrinsics(F);
|
||||
return false;
|
||||
}
|
||||
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
||||
if (!L)
|
||||
AU.setPreservesAll();
|
||||
}
|
||||
StringRef getPassName() const override { return "Coroutine Cleanup"; }
|
||||
};
|
||||
}
|
||||
|
||||
char CoroCleanup::ID = 0;
|
||||
INITIALIZE_PASS(CoroCleanup, "coro-cleanup",
|
||||
"Lower all coroutine related intrinsics", false, false)
|
||||
|
||||
Pass *llvm::createCoroCleanupPass() { return new CoroCleanup(); }
|
223
external/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
vendored
Normal file
223
external/llvm/lib/Transforms/Coroutines/CoroEarly.cpp
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
//===- CoroEarly.cpp - Coroutine Early Function Pass ----------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// This pass lowers coroutine intrinsics that hide the details of the exact
|
||||
// calling convention for coroutine resume and destroy functions and details of
|
||||
// the structure of the coroutine frame.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "CoroInternal.h"
|
||||
#include "llvm/IR/CallSite.h"
|
||||
#include "llvm/IR/IRBuilder.h"
|
||||
#include "llvm/IR/InstIterator.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/Pass.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
#define DEBUG_TYPE "coro-early"
|
||||
|
||||
namespace {
|
||||
// Created on demand if CoroEarly pass has work to do.
|
||||
class Lowerer : public coro::LowererBase {
|
||||
IRBuilder<> Builder;
|
||||
PointerType *const AnyResumeFnPtrTy;
|
||||
|
||||
void lowerResumeOrDestroy(CallSite CS, CoroSubFnInst::ResumeKind);
|
||||
void lowerCoroPromise(CoroPromiseInst *Intrin);
|
||||
void lowerCoroDone(IntrinsicInst *II);
|
||||
|
||||
public:
|
||||
Lowerer(Module &M)
|
||||
: LowererBase(M), Builder(Context),
|
||||
AnyResumeFnPtrTy(FunctionType::get(Type::getVoidTy(Context), Int8Ptr,
|
||||
/*isVarArg=*/false)
|
||||
->getPointerTo()) {}
|
||||
bool lowerEarlyIntrinsics(Function &F);
|
||||
};
|
||||
}
|
||||
|
||||
// Replace a direct call to coro.resume or coro.destroy with an indirect call to
|
||||
// an address returned by coro.subfn.addr intrinsic. This is done so that
|
||||
// CGPassManager recognizes devirtualization when CoroElide pass replaces a call
|
||||
// to coro.subfn.addr with an appropriate function address.
|
||||
void Lowerer::lowerResumeOrDestroy(CallSite CS,
|
||||
CoroSubFnInst::ResumeKind Index) {
|
||||
Value *ResumeAddr =
|
||||
makeSubFnCall(CS.getArgOperand(0), Index, CS.getInstruction());
|
||||
CS.setCalledFunction(ResumeAddr);
|
||||
CS.setCallingConv(CallingConv::Fast);
|
||||
}
|
||||
|
||||
// Coroutine promise field is always at the fixed offset from the beginning of
|
||||
// the coroutine frame. i8* coro.promise(i8*, i1 from) intrinsic adds an offset
|
||||
// to a passed pointer to move from coroutine frame to coroutine promise and
|
||||
// vice versa. Since we don't know exactly which coroutine frame it is, we build
|
||||
// a coroutine frame mock up starting with two function pointers, followed by a
|
||||
// properly aligned coroutine promise field.
|
||||
// TODO: Handle the case when coroutine promise alloca has align override.
|
||||
void Lowerer::lowerCoroPromise(CoroPromiseInst *Intrin) {
|
||||
Value *Operand = Intrin->getArgOperand(0);
|
||||
unsigned Alignement = Intrin->getAlignment();
|
||||
Type *Int8Ty = Builder.getInt8Ty();
|
||||
|
||||
auto *SampleStruct =
|
||||
StructType::get(Context, {AnyResumeFnPtrTy, AnyResumeFnPtrTy, Int8Ty});
|
||||
const DataLayout &DL = TheModule.getDataLayout();
|
||||
int64_t Offset = alignTo(
|
||||
DL.getStructLayout(SampleStruct)->getElementOffset(2), Alignement);
|
||||
if (Intrin->isFromPromise())
|
||||
Offset = -Offset;
|
||||
|
||||
Builder.SetInsertPoint(Intrin);
|
||||
Value *Replacement =
|
||||
Builder.CreateConstInBoundsGEP1_32(Int8Ty, Operand, Offset);
|
||||
|
||||
Intrin->replaceAllUsesWith(Replacement);
|
||||
Intrin->eraseFromParent();
|
||||
}
|
||||
|
||||
// When a coroutine reaches final suspend point, it zeros out ResumeFnAddr in
|
||||
// the coroutine frame (it is UB to resume from a final suspend point).
|
||||
// The llvm.coro.done intrinsic is used to check whether a coroutine is
|
||||
// suspended at the final suspend point or not.
|
||||
void Lowerer::lowerCoroDone(IntrinsicInst *II) {
|
||||
Value *Operand = II->getArgOperand(0);
|
||||
|
||||
// ResumeFnAddr is the first pointer sized element of the coroutine frame.
|
||||
auto *FrameTy = Int8Ptr;
|
||||
PointerType *FramePtrTy = FrameTy->getPointerTo();
|
||||
|
||||
Builder.SetInsertPoint(II);
|
||||
auto *BCI = Builder.CreateBitCast(Operand, FramePtrTy);
|
||||
auto *Gep = Builder.CreateConstInBoundsGEP1_32(FrameTy, BCI, 0);
|
||||
auto *Load = Builder.CreateLoad(Gep);
|
||||
auto *Cond = Builder.CreateICmpEQ(Load, NullPtr);
|
||||
|
||||
II->replaceAllUsesWith(Cond);
|
||||
II->eraseFromParent();
|
||||
}
|
||||
|
||||
// Prior to CoroSplit, calls to coro.begin needs to be marked as NoDuplicate,
|
||||
// as CoroSplit assumes there is exactly one coro.begin. After CoroSplit,
|
||||
// NoDuplicate attribute will be removed from coro.begin otherwise, it will
|
||||
// interfere with inlining.
|
||||
static void setCannotDuplicate(CoroIdInst *CoroId) {
|
||||
for (User *U : CoroId->users())
|
||||
if (auto *CB = dyn_cast<CoroBeginInst>(U))
|
||||
CB->setCannotDuplicate();
|
||||
}
|
||||
|
||||
bool Lowerer::lowerEarlyIntrinsics(Function &F) {
|
||||
bool Changed = false;
|
||||
CoroIdInst *CoroId = nullptr;
|
||||
SmallVector<CoroFreeInst *, 4> CoroFrees;
|
||||
for (auto IB = inst_begin(F), IE = inst_end(F); IB != IE;) {
|
||||
Instruction &I = *IB++;
|
||||
if (auto CS = CallSite(&I)) {
|
||||
switch (CS.getIntrinsicID()) {
|
||||
default:
|
||||
continue;
|
||||
case Intrinsic::coro_free:
|
||||
CoroFrees.push_back(cast<CoroFreeInst>(&I));
|
||||
break;
|
||||
case Intrinsic::coro_suspend:
|
||||
// Make sure that final suspend point is not duplicated as CoroSplit
|
||||
// pass expects that there is at most one final suspend point.
|
||||
if (cast<CoroSuspendInst>(&I)->isFinal())
|
||||
CS.setCannotDuplicate();
|
||||
break;
|
||||
case Intrinsic::coro_end:
|
||||
// Make sure that fallthrough coro.end is not duplicated as CoroSplit
|
||||
// pass expects that there is at most one fallthrough coro.end.
|
||||
if (cast<CoroEndInst>(&I)->isFallthrough())
|
||||
CS.setCannotDuplicate();
|
||||
break;
|
||||
case Intrinsic::coro_id:
|
||||
// Mark a function that comes out of the frontend that has a coro.id
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Intrinsic::coro_resume:
|
||||
lowerResumeOrDestroy(CS, CoroSubFnInst::ResumeIndex);
|
||||
break;
|
||||
case Intrinsic::coro_destroy:
|
||||
lowerResumeOrDestroy(CS, CoroSubFnInst::DestroyIndex);
|
||||
break;
|
||||
case Intrinsic::coro_promise:
|
||||
lowerCoroPromise(cast<CoroPromiseInst>(&I));
|
||||
break;
|
||||
case Intrinsic::coro_done:
|
||||
lowerCoroDone(cast<IntrinsicInst>(&I));
|
||||
break;
|
||||
}
|
||||
Changed = true;
|
||||
}
|
||||
}
|
||||
// Make sure that all CoroFree reference the coro.id intrinsic.
|
||||
// Token type is not exposed through coroutine C/C++ builtins to plain C, so
|
||||
// we allow specifying none and fixing it up here.
|
||||
if (CoroId)
|
||||
for (CoroFreeInst *CF : CoroFrees)
|
||||
CF->setArgOperand(0, CoroId);
|
||||
return Changed;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Top Level Driver
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
|
||||
struct CoroEarly : public FunctionPass {
|
||||
static char ID; // Pass identification, replacement for typeid.
|
||||
CoroEarly() : FunctionPass(ID) {
|
||||
initializeCoroEarlyPass(*PassRegistry::getPassRegistry());
|
||||
}
|
||||
|
||||
std::unique_ptr<Lowerer> L;
|
||||
|
||||
// This pass has work to do only if we find intrinsics we are going to lower
|
||||
// in the module.
|
||||
bool doInitialization(Module &M) override {
|
||||
if (coro::declaresIntrinsics(M, {"llvm.coro.id", "llvm.coro.destroy",
|
||||
"llvm.coro.done", "llvm.coro.end",
|
||||
"llvm.coro.free", "llvm.coro.promise",
|
||||
"llvm.coro.resume", "llvm.coro.suspend"}))
|
||||
L = llvm::make_unique<Lowerer>(M);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool runOnFunction(Function &F) override {
|
||||
if (!L)
|
||||
return false;
|
||||
|
||||
return L->lowerEarlyIntrinsics(F);
|
||||
}
|
||||
|
||||
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
||||
AU.setPreservesCFG();
|
||||
}
|
||||
StringRef getPassName() const override {
|
||||
return "Lower early coroutine intrinsics";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
char CoroEarly::ID = 0;
|
||||
INITIALIZE_PASS(CoroEarly, "coro-early", "Lower early coroutine intrinsics",
|
||||
false, false)
|
||||
|
||||
Pass *llvm::createCoroEarlyPass() { return new CoroEarly(); }
|
321
external/llvm/lib/Transforms/Coroutines/CoroElide.cpp
vendored
Normal file
321
external/llvm/lib/Transforms/Coroutines/CoroElide.cpp
vendored
Normal file
@ -0,0 +1,321 @@
|
||||
//===- CoroElide.cpp - Coroutine Frame Allocation Elision Pass ------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// This pass replaces dynamic allocation of coroutine frame with alloca and
|
||||
// replaces calls to llvm.coro.resume and llvm.coro.destroy with direct calls
|
||||
// to coroutine sub-functions.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "CoroInternal.h"
|
||||
#include "llvm/Analysis/AliasAnalysis.h"
|
||||
#include "llvm/Analysis/InstructionSimplify.h"
|
||||
#include "llvm/IR/InstIterator.h"
|
||||
#include "llvm/Pass.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
#define DEBUG_TYPE "coro-elide"
|
||||
|
||||
namespace {
|
||||
// Created on demand if CoroElide pass has work to do.
|
||||
struct Lowerer : coro::LowererBase {
|
||||
SmallVector<CoroIdInst *, 4> CoroIds;
|
||||
SmallVector<CoroBeginInst *, 1> CoroBegins;
|
||||
SmallVector<CoroAllocInst *, 1> CoroAllocs;
|
||||
SmallVector<CoroSubFnInst *, 4> ResumeAddr;
|
||||
SmallVector<CoroSubFnInst *, 4> DestroyAddr;
|
||||
SmallVector<CoroFreeInst *, 1> CoroFrees;
|
||||
|
||||
Lowerer(Module &M) : LowererBase(M) {}
|
||||
|
||||
void elideHeapAllocations(Function *F, Type *FrameTy, AAResults &AA);
|
||||
bool shouldElide() const;
|
||||
bool processCoroId(CoroIdInst *, AAResults &AA);
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
// Go through the list of coro.subfn.addr intrinsics and replace them with the
|
||||
// provided constant.
|
||||
static void replaceWithConstant(Constant *Value,
|
||||
SmallVectorImpl<CoroSubFnInst *> &Users) {
|
||||
if (Users.empty())
|
||||
return;
|
||||
|
||||
// See if we need to bitcast the constant to match the type of the intrinsic
|
||||
// being replaced. Note: All coro.subfn.addr intrinsics return the same type,
|
||||
// so we only need to examine the type of the first one in the list.
|
||||
Type *IntrTy = Users.front()->getType();
|
||||
Type *ValueTy = Value->getType();
|
||||
if (ValueTy != IntrTy) {
|
||||
// May need to tweak the function type to match the type expected at the
|
||||
// use site.
|
||||
assert(ValueTy->isPointerTy() && IntrTy->isPointerTy());
|
||||
Value = ConstantExpr::getBitCast(Value, IntrTy);
|
||||
}
|
||||
|
||||
// Now the value type matches the type of the intrinsic. Replace them all!
|
||||
for (CoroSubFnInst *I : Users)
|
||||
replaceAndRecursivelySimplify(I, Value);
|
||||
}
|
||||
|
||||
// See if any operand of the call instruction references the coroutine frame.
|
||||
static bool operandReferences(CallInst *CI, AllocaInst *Frame, AAResults &AA) {
|
||||
for (Value *Op : CI->operand_values())
|
||||
if (AA.alias(Op, Frame) != NoAlias)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for any tail calls referencing the coroutine frame and remove tail
|
||||
// attribute from them, since now coroutine frame resides on the stack and tail
|
||||
// call implies that the function does not references anything on the stack.
|
||||
static void removeTailCallAttribute(AllocaInst *Frame, AAResults &AA) {
|
||||
Function &F = *Frame->getFunction();
|
||||
MemoryLocation Mem(Frame);
|
||||
for (Instruction &I : instructions(F))
|
||||
if (auto *Call = dyn_cast<CallInst>(&I))
|
||||
if (Call->isTailCall() && operandReferences(Call, Frame, AA)) {
|
||||
// FIXME: If we ever hit this check. Evaluate whether it is more
|
||||
// appropriate to retain musttail and allow the code to compile.
|
||||
if (Call->isMustTailCall())
|
||||
report_fatal_error("Call referring to the coroutine frame cannot be "
|
||||
"marked as musttail");
|
||||
Call->setTailCall(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Given a resume function @f.resume(%f.frame* %frame), returns %f.frame type.
|
||||
static Type *getFrameType(Function *Resume) {
|
||||
auto *ArgType = Resume->arg_begin()->getType();
|
||||
return cast<PointerType>(ArgType)->getElementType();
|
||||
}
|
||||
|
||||
// Finds first non alloca instruction in the entry block of a function.
|
||||
static Instruction *getFirstNonAllocaInTheEntryBlock(Function *F) {
|
||||
for (Instruction &I : F->getEntryBlock())
|
||||
if (!isa<AllocaInst>(&I))
|
||||
return &I;
|
||||
llvm_unreachable("no terminator in the entry block");
|
||||
}
|
||||
|
||||
// To elide heap allocations we need to suppress code blocks guarded by
|
||||
// llvm.coro.alloc and llvm.coro.free instructions.
|
||||
void Lowerer::elideHeapAllocations(Function *F, Type *FrameTy, AAResults &AA) {
|
||||
LLVMContext &C = FrameTy->getContext();
|
||||
auto *InsertPt =
|
||||
getFirstNonAllocaInTheEntryBlock(CoroIds.front()->getFunction());
|
||||
|
||||
// Replacing llvm.coro.alloc with false will suppress dynamic
|
||||
// allocation as it is expected for the frontend to generate the code that
|
||||
// looks like:
|
||||
// id = coro.id(...)
|
||||
// mem = coro.alloc(id) ? malloc(coro.size()) : 0;
|
||||
// coro.begin(id, mem)
|
||||
auto *False = ConstantInt::getFalse(C);
|
||||
for (auto *CA : CoroAllocs) {
|
||||
CA->replaceAllUsesWith(False);
|
||||
CA->eraseFromParent();
|
||||
}
|
||||
|
||||
// FIXME: Design how to transmit alignment information for every alloca that
|
||||
// is spilled into the coroutine frame and recreate the alignment information
|
||||
// here. Possibly we will need to do a mini SROA here and break the coroutine
|
||||
// frame into individual AllocaInst recreating the original alignment.
|
||||
const DataLayout &DL = F->getParent()->getDataLayout();
|
||||
auto *Frame = new AllocaInst(FrameTy, DL.getAllocaAddrSpace(), "", InsertPt);
|
||||
auto *FrameVoidPtr =
|
||||
new BitCastInst(Frame, Type::getInt8PtrTy(C), "vFrame", InsertPt);
|
||||
|
||||
for (auto *CB : CoroBegins) {
|
||||
CB->replaceAllUsesWith(FrameVoidPtr);
|
||||
CB->eraseFromParent();
|
||||
}
|
||||
|
||||
// Since now coroutine frame lives on the stack we need to make sure that
|
||||
// any tail call referencing it, must be made non-tail call.
|
||||
removeTailCallAttribute(Frame, AA);
|
||||
}
|
||||
|
||||
bool Lowerer::shouldElide() const {
|
||||
// If no CoroAllocs, we cannot suppress allocation, so elision is not
|
||||
// possible.
|
||||
if (CoroAllocs.empty())
|
||||
return false;
|
||||
|
||||
// Check that for every coro.begin there is a coro.destroy directly
|
||||
// referencing the SSA value of that coro.begin. If the value escaped, then
|
||||
// coro.destroy would have been referencing a memory location storing that
|
||||
// value and not the virtual register.
|
||||
|
||||
SmallPtrSet<CoroBeginInst *, 8> ReferencedCoroBegins;
|
||||
|
||||
for (CoroSubFnInst *DA : DestroyAddr) {
|
||||
if (auto *CB = dyn_cast<CoroBeginInst>(DA->getFrame()))
|
||||
ReferencedCoroBegins.insert(CB);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
// If size of the set is the same as total number of CoroBegins, means we
|
||||
// found a coro.free or coro.destroy mentioning a coro.begin and we can
|
||||
// perform heap elision.
|
||||
return ReferencedCoroBegins.size() == CoroBegins.size();
|
||||
}
|
||||
|
||||
bool Lowerer::processCoroId(CoroIdInst *CoroId, AAResults &AA) {
|
||||
CoroBegins.clear();
|
||||
CoroAllocs.clear();
|
||||
CoroFrees.clear();
|
||||
ResumeAddr.clear();
|
||||
DestroyAddr.clear();
|
||||
|
||||
// Collect all coro.begin and coro.allocs associated with this coro.id.
|
||||
for (User *U : CoroId->users()) {
|
||||
if (auto *CB = dyn_cast<CoroBeginInst>(U))
|
||||
CoroBegins.push_back(CB);
|
||||
else if (auto *CA = dyn_cast<CoroAllocInst>(U))
|
||||
CoroAllocs.push_back(CA);
|
||||
else if (auto *CF = dyn_cast<CoroFreeInst>(U))
|
||||
CoroFrees.push_back(CF);
|
||||
}
|
||||
|
||||
// Collect all coro.subfn.addrs associated with coro.begin.
|
||||
// Note, we only devirtualize the calls if their coro.subfn.addr refers to
|
||||
// coro.begin directly. If we run into cases where this check is too
|
||||
// conservative, we can consider relaxing the check.
|
||||
for (CoroBeginInst *CB : CoroBegins) {
|
||||
for (User *U : CB->users())
|
||||
if (auto *II = dyn_cast<CoroSubFnInst>(U))
|
||||
switch (II->getIndex()) {
|
||||
case CoroSubFnInst::ResumeIndex:
|
||||
ResumeAddr.push_back(II);
|
||||
break;
|
||||
case CoroSubFnInst::DestroyIndex:
|
||||
DestroyAddr.push_back(II);
|
||||
break;
|
||||
default:
|
||||
llvm_unreachable("unexpected coro.subfn.addr constant");
|
||||
}
|
||||
}
|
||||
|
||||
// PostSplit coro.id refers to an array of subfunctions in its Info
|
||||
// argument.
|
||||
ConstantArray *Resumers = CoroId->getInfo().Resumers;
|
||||
assert(Resumers && "PostSplit coro.id Info argument must refer to an array"
|
||||
"of coroutine subfunctions");
|
||||
auto *ResumeAddrConstant =
|
||||
ConstantExpr::getExtractValue(Resumers, CoroSubFnInst::ResumeIndex);
|
||||
|
||||
replaceWithConstant(ResumeAddrConstant, ResumeAddr);
|
||||
|
||||
bool ShouldElide = shouldElide();
|
||||
|
||||
auto *DestroyAddrConstant = ConstantExpr::getExtractValue(
|
||||
Resumers,
|
||||
ShouldElide ? CoroSubFnInst::CleanupIndex : CoroSubFnInst::DestroyIndex);
|
||||
|
||||
replaceWithConstant(DestroyAddrConstant, DestroyAddr);
|
||||
|
||||
if (ShouldElide) {
|
||||
auto *FrameTy = getFrameType(cast<Function>(ResumeAddrConstant));
|
||||
elideHeapAllocations(CoroId->getFunction(), FrameTy, AA);
|
||||
coro::replaceCoroFree(CoroId, /*Elide=*/true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// See if there are any coro.subfn.addr instructions referring to coro.devirt
|
||||
// trigger, if so, replace them with a direct call to devirt trigger function.
|
||||
static bool replaceDevirtTrigger(Function &F) {
|
||||
SmallVector<CoroSubFnInst *, 1> DevirtAddr;
|
||||
for (auto &I : instructions(F))
|
||||
if (auto *SubFn = dyn_cast<CoroSubFnInst>(&I))
|
||||
if (SubFn->getIndex() == CoroSubFnInst::RestartTrigger)
|
||||
DevirtAddr.push_back(SubFn);
|
||||
|
||||
if (DevirtAddr.empty())
|
||||
return false;
|
||||
|
||||
Module &M = *F.getParent();
|
||||
Function *DevirtFn = M.getFunction(CORO_DEVIRT_TRIGGER_FN);
|
||||
assert(DevirtFn && "coro.devirt.fn not found");
|
||||
replaceWithConstant(DevirtFn, DevirtAddr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Top Level Driver
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
struct CoroElide : FunctionPass {
|
||||
static char ID;
|
||||
CoroElide() : FunctionPass(ID) {
|
||||
initializeCoroElidePass(*PassRegistry::getPassRegistry());
|
||||
}
|
||||
|
||||
std::unique_ptr<Lowerer> L;
|
||||
|
||||
bool doInitialization(Module &M) override {
|
||||
if (coro::declaresIntrinsics(M, {"llvm.coro.id"}))
|
||||
L = llvm::make_unique<Lowerer>(M);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool runOnFunction(Function &F) override {
|
||||
if (!L)
|
||||
return false;
|
||||
|
||||
bool Changed = false;
|
||||
|
||||
if (F.hasFnAttribute(CORO_PRESPLIT_ATTR))
|
||||
Changed = replaceDevirtTrigger(F);
|
||||
|
||||
L->CoroIds.clear();
|
||||
|
||||
// Collect all PostSplit coro.ids.
|
||||
for (auto &I : instructions(F))
|
||||
if (auto *CII = dyn_cast<CoroIdInst>(&I))
|
||||
if (CII->getInfo().isPostSplit())
|
||||
// If it is the coroutine itself, don't touch it.
|
||||
if (CII->getCoroutine() != CII->getFunction())
|
||||
L->CoroIds.push_back(CII);
|
||||
|
||||
// If we did not find any coro.id, there is nothing to do.
|
||||
if (L->CoroIds.empty())
|
||||
return Changed;
|
||||
|
||||
AAResults &AA = getAnalysis<AAResultsWrapperPass>().getAAResults();
|
||||
|
||||
for (auto *CII : L->CoroIds)
|
||||
Changed |= L->processCoroId(CII, AA);
|
||||
|
||||
return Changed;
|
||||
}
|
||||
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
||||
AU.addRequired<AAResultsWrapperPass>();
|
||||
}
|
||||
StringRef getPassName() const override { return "Coroutine Elision"; }
|
||||
};
|
||||
}
|
||||
|
||||
char CoroElide::ID = 0;
|
||||
INITIALIZE_PASS_BEGIN(
|
||||
CoroElide, "coro-elide",
|
||||
"Coroutine frame allocation elision and indirect calls replacement", false,
|
||||
false)
|
||||
INITIALIZE_PASS_DEPENDENCY(AAResultsWrapperPass)
|
||||
INITIALIZE_PASS_END(
|
||||
CoroElide, "coro-elide",
|
||||
"Coroutine frame allocation elision and indirect calls replacement", false,
|
||||
false)
|
||||
|
||||
Pass *llvm::createCoroElidePass() { return new CoroElide(); }
|
860
external/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
vendored
Normal file
860
external/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
323
external/llvm/lib/Transforms/Coroutines/CoroInstr.h
vendored
Normal file
323
external/llvm/lib/Transforms/Coroutines/CoroInstr.h
vendored
Normal file
@ -0,0 +1,323 @@
|
||||
//===-- CoroInstr.h - Coroutine Intrinsics Instruction Wrappers -*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// This file defines classes that make it really easy to deal with intrinsic
|
||||
// functions with the isa/dyncast family of functions. In particular, this
|
||||
// allows you to do things like:
|
||||
//
|
||||
// if (auto *SF = dyn_cast<CoroSubFnInst>(Inst))
|
||||
// ... SF->getFrame() ...
|
||||
//
|
||||
// All intrinsic function calls are instances of the call instruction, so these
|
||||
// are all subclasses of the CallInst class. Note that none of these classes
|
||||
// has state or virtual methods, which is an important part of this gross/neat
|
||||
// hack working.
|
||||
//
|
||||
// The helpful comment above is borrowed from llvm/IntrinsicInst.h, we keep
|
||||
// coroutine intrinsic wrappers here since they are only used by the passes in
|
||||
// the Coroutine library.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TRANSFORMS_COROUTINES_COROINSTR_H
|
||||
#define LLVM_LIB_TRANSFORMS_COROUTINES_COROINSTR_H
|
||||
|
||||
#include "llvm/IR/GlobalVariable.h"
|
||||
#include "llvm/IR/IntrinsicInst.h"
|
||||
|
||||
namespace llvm {
|
||||
|
||||
/// This class represents the llvm.coro.subfn.addr instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroSubFnInst : public IntrinsicInst {
|
||||
enum { FrameArg, IndexArg };
|
||||
|
||||
public:
|
||||
enum ResumeKind {
|
||||
RestartTrigger = -1,
|
||||
ResumeIndex,
|
||||
DestroyIndex,
|
||||
CleanupIndex,
|
||||
IndexLast,
|
||||
IndexFirst = RestartTrigger
|
||||
};
|
||||
|
||||
Value *getFrame() const { return getArgOperand(FrameArg); }
|
||||
ResumeKind getIndex() const {
|
||||
int64_t Index = getRawIndex()->getValue().getSExtValue();
|
||||
assert(Index >= IndexFirst && Index < IndexLast &&
|
||||
"unexpected CoroSubFnInst index argument");
|
||||
return static_cast<ResumeKind>(Index);
|
||||
}
|
||||
|
||||
ConstantInt *getRawIndex() const {
|
||||
return cast<ConstantInt>(getArgOperand(IndexArg));
|
||||
}
|
||||
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_subfn_addr;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.alloc instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroAllocInst : public IntrinsicInst {
|
||||
public:
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_alloc;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.alloc instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroIdInst : public IntrinsicInst {
|
||||
enum { AlignArg, PromiseArg, CoroutineArg, InfoArg };
|
||||
|
||||
public:
|
||||
CoroAllocInst *getCoroAlloc() {
|
||||
for (User *U : users())
|
||||
if (auto *CA = dyn_cast<CoroAllocInst>(U))
|
||||
return CA;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IntrinsicInst *getCoroBegin() {
|
||||
for (User *U : users())
|
||||
if (auto *II = dyn_cast<IntrinsicInst>(U))
|
||||
if (II->getIntrinsicID() == Intrinsic::coro_begin)
|
||||
return II;
|
||||
llvm_unreachable("no coro.begin associated with coro.id");
|
||||
}
|
||||
|
||||
AllocaInst *getPromise() const {
|
||||
Value *Arg = getArgOperand(PromiseArg);
|
||||
return isa<ConstantPointerNull>(Arg)
|
||||
? nullptr
|
||||
: cast<AllocaInst>(Arg->stripPointerCasts());
|
||||
}
|
||||
|
||||
void clearPromise() {
|
||||
Value *Arg = getArgOperand(PromiseArg);
|
||||
setArgOperand(PromiseArg,
|
||||
ConstantPointerNull::get(Type::getInt8PtrTy(getContext())));
|
||||
if (isa<AllocaInst>(Arg))
|
||||
return;
|
||||
assert((isa<BitCastInst>(Arg) || isa<GetElementPtrInst>(Arg)) &&
|
||||
"unexpected instruction designating the promise");
|
||||
// TODO: Add a check that any remaining users of Inst are after coro.begin
|
||||
// or add code to move the users after coro.begin.
|
||||
auto *Inst = cast<Instruction>(Arg);
|
||||
if (Inst->use_empty()) {
|
||||
Inst->eraseFromParent();
|
||||
return;
|
||||
}
|
||||
Inst->moveBefore(getCoroBegin()->getNextNode());
|
||||
}
|
||||
|
||||
// Info argument of coro.id is
|
||||
// fresh out of the frontend: null ;
|
||||
// outlined : {Init, Return, Susp1, Susp2, ...} ;
|
||||
// postsplit : [resume, destroy, cleanup] ;
|
||||
//
|
||||
// If parts of the coroutine were outlined to protect against undesirable
|
||||
// code motion, these functions will be stored in a struct literal referred to
|
||||
// by the Info parameter. Note: this is only needed before coroutine is split.
|
||||
//
|
||||
// After coroutine is split, resume functions are stored in an array
|
||||
// referred to by this parameter.
|
||||
|
||||
struct Info {
|
||||
ConstantStruct *OutlinedParts = nullptr;
|
||||
ConstantArray *Resumers = nullptr;
|
||||
|
||||
bool hasOutlinedParts() const { return OutlinedParts != nullptr; }
|
||||
bool isPostSplit() const { return Resumers != nullptr; }
|
||||
bool isPreSplit() const { return !isPostSplit(); }
|
||||
};
|
||||
Info getInfo() const {
|
||||
Info Result;
|
||||
auto *GV = dyn_cast<GlobalVariable>(getRawInfo());
|
||||
if (!GV)
|
||||
return Result;
|
||||
|
||||
assert(GV->isConstant() && GV->hasDefinitiveInitializer());
|
||||
Constant *Initializer = GV->getInitializer();
|
||||
if ((Result.OutlinedParts = dyn_cast<ConstantStruct>(Initializer)))
|
||||
return Result;
|
||||
|
||||
Result.Resumers = cast<ConstantArray>(Initializer);
|
||||
return Result;
|
||||
}
|
||||
Constant *getRawInfo() const {
|
||||
return cast<Constant>(getArgOperand(InfoArg)->stripPointerCasts());
|
||||
}
|
||||
|
||||
void setInfo(Constant *C) { setArgOperand(InfoArg, C); }
|
||||
|
||||
Function *getCoroutine() const {
|
||||
return cast<Function>(getArgOperand(CoroutineArg)->stripPointerCasts());
|
||||
}
|
||||
void setCoroutineSelf() {
|
||||
assert(isa<ConstantPointerNull>(getArgOperand(CoroutineArg)) &&
|
||||
"Coroutine argument is already assigned");
|
||||
auto *const Int8PtrTy = Type::getInt8PtrTy(getContext());
|
||||
setArgOperand(CoroutineArg,
|
||||
ConstantExpr::getBitCast(getFunction(), Int8PtrTy));
|
||||
}
|
||||
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_id;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.frame instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroFrameInst : public IntrinsicInst {
|
||||
public:
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_frame;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.free instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroFreeInst : public IntrinsicInst {
|
||||
enum { IdArg, FrameArg };
|
||||
|
||||
public:
|
||||
Value *getFrame() const { return getArgOperand(FrameArg); }
|
||||
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_free;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This class represents the llvm.coro.begin instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroBeginInst : public IntrinsicInst {
|
||||
enum { IdArg, MemArg };
|
||||
|
||||
public:
|
||||
CoroIdInst *getId() const { return cast<CoroIdInst>(getArgOperand(IdArg)); }
|
||||
|
||||
Value *getMem() const { return getArgOperand(MemArg); }
|
||||
|
||||
// Methods for support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_begin;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.save instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroSaveInst : public IntrinsicInst {
|
||||
public:
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_save;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.promise instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroPromiseInst : public IntrinsicInst {
|
||||
enum { FrameArg, AlignArg, FromArg };
|
||||
|
||||
public:
|
||||
bool isFromPromise() const {
|
||||
return cast<Constant>(getArgOperand(FromArg))->isOneValue();
|
||||
}
|
||||
unsigned getAlignment() const {
|
||||
return cast<ConstantInt>(getArgOperand(AlignArg))->getZExtValue();
|
||||
}
|
||||
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_promise;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.suspend instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroSuspendInst : public IntrinsicInst {
|
||||
enum { SaveArg, FinalArg };
|
||||
|
||||
public:
|
||||
CoroSaveInst *getCoroSave() const {
|
||||
Value *Arg = getArgOperand(SaveArg);
|
||||
if (auto *SI = dyn_cast<CoroSaveInst>(Arg))
|
||||
return SI;
|
||||
assert(isa<ConstantTokenNone>(Arg));
|
||||
return nullptr;
|
||||
}
|
||||
bool isFinal() const {
|
||||
return cast<Constant>(getArgOperand(FinalArg))->isOneValue();
|
||||
}
|
||||
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_suspend;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.size instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroSizeInst : public IntrinsicInst {
|
||||
public:
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_size;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents the llvm.coro.end instruction.
|
||||
class LLVM_LIBRARY_VISIBILITY CoroEndInst : public IntrinsicInst {
|
||||
enum { FrameArg, UnwindArg };
|
||||
|
||||
public:
|
||||
bool isFallthrough() const { return !isUnwind(); }
|
||||
bool isUnwind() const {
|
||||
return cast<Constant>(getArgOperand(UnwindArg))->isOneValue();
|
||||
}
|
||||
|
||||
// Methods to support type inquiry through isa, cast, and dyn_cast:
|
||||
static bool classof(const IntrinsicInst *I) {
|
||||
return I->getIntrinsicID() == Intrinsic::coro_end;
|
||||
}
|
||||
static bool classof(const Value *V) {
|
||||
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
|
||||
}
|
||||
};
|
||||
|
||||
} // End namespace llvm.
|
||||
|
||||
#endif
|
107
external/llvm/lib/Transforms/Coroutines/CoroInternal.h
vendored
Normal file
107
external/llvm/lib/Transforms/Coroutines/CoroInternal.h
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
//===- CoroInternal.h - Internal Coroutine interfaces ---------*- C++ -*---===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Common definitions/declarations used internally by coroutine lowering passes.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_LIB_TRANSFORMS_COROUTINES_COROINTERNAL_H
|
||||
#define LLVM_LIB_TRANSFORMS_COROUTINES_COROINTERNAL_H
|
||||
|
||||
#include "CoroInstr.h"
|
||||
#include "llvm/Transforms/Coroutines.h"
|
||||
|
||||
namespace llvm {
|
||||
|
||||
class CallGraph;
|
||||
class CallGraphSCC;
|
||||
class PassRegistry;
|
||||
|
||||
void initializeCoroEarlyPass(PassRegistry &);
|
||||
void initializeCoroSplitPass(PassRegistry &);
|
||||
void initializeCoroElidePass(PassRegistry &);
|
||||
void initializeCoroCleanupPass(PassRegistry &);
|
||||
|
||||
// CoroEarly pass marks every function that has coro.begin with a string
|
||||
// attribute "coroutine.presplit"="0". CoroSplit pass processes the coroutine
|
||||
// twice. First, it lets it go through complete IPO optimization pipeline as a
|
||||
// single function. It forces restart of the pipeline by inserting an indirect
|
||||
// call to an empty function "coro.devirt.trigger" which is devirtualized by
|
||||
// CoroElide pass that triggers a restart of the pipeline by CGPassManager.
|
||||
// When CoroSplit pass sees the same coroutine the second time, it splits it up,
|
||||
// adds coroutine subfunctions to the SCC to be processed by IPO pipeline.
|
||||
|
||||
#define CORO_PRESPLIT_ATTR "coroutine.presplit"
|
||||
#define UNPREPARED_FOR_SPLIT "0"
|
||||
#define PREPARED_FOR_SPLIT "1"
|
||||
|
||||
#define CORO_DEVIRT_TRIGGER_FN "coro.devirt.trigger"
|
||||
|
||||
namespace coro {
|
||||
|
||||
bool declaresIntrinsics(Module &M, std::initializer_list<StringRef>);
|
||||
void replaceAllCoroAllocs(CoroBeginInst *CB, bool Replacement);
|
||||
void replaceAllCoroFrees(CoroBeginInst *CB, Value *Replacement);
|
||||
void replaceCoroFree(CoroIdInst *CoroId, bool Elide);
|
||||
void updateCallGraph(Function &Caller, ArrayRef<Function *> Funcs,
|
||||
CallGraph &CG, CallGraphSCC &SCC);
|
||||
|
||||
// Keeps data and helper functions for lowering coroutine intrinsics.
|
||||
struct LowererBase {
|
||||
Module &TheModule;
|
||||
LLVMContext &Context;
|
||||
PointerType *const Int8Ptr;
|
||||
FunctionType *const ResumeFnType;
|
||||
ConstantPointerNull *const NullPtr;
|
||||
|
||||
LowererBase(Module &M);
|
||||
Value *makeSubFnCall(Value *Arg, int Index, Instruction *InsertPt);
|
||||
};
|
||||
|
||||
// Holds structural Coroutine Intrinsics for a particular function and other
|
||||
// values used during CoroSplit pass.
|
||||
struct LLVM_LIBRARY_VISIBILITY Shape {
|
||||
CoroBeginInst *CoroBegin;
|
||||
SmallVector<CoroEndInst *, 4> CoroEnds;
|
||||
SmallVector<CoroSizeInst *, 2> CoroSizes;
|
||||
SmallVector<CoroSuspendInst *, 4> CoroSuspends;
|
||||
|
||||
// Field Indexes for known coroutine frame fields.
|
||||
enum {
|
||||
ResumeField,
|
||||
DestroyField,
|
||||
PromiseField,
|
||||
IndexField,
|
||||
LastKnownField = IndexField
|
||||
};
|
||||
|
||||
StructType *FrameTy;
|
||||
Instruction *FramePtr;
|
||||
BasicBlock *AllocaSpillBlock;
|
||||
SwitchInst *ResumeSwitch;
|
||||
AllocaInst *PromiseAlloca;
|
||||
bool HasFinalSuspend;
|
||||
|
||||
IntegerType *getIndexType() const {
|
||||
assert(FrameTy && "frame type not assigned");
|
||||
return cast<IntegerType>(FrameTy->getElementType(IndexField));
|
||||
}
|
||||
ConstantInt *getIndex(uint64_t Value) const {
|
||||
return ConstantInt::get(getIndexType(), Value);
|
||||
}
|
||||
|
||||
Shape() = default;
|
||||
explicit Shape(Function &F) { buildFrom(F); }
|
||||
void buildFrom(Function &F);
|
||||
};
|
||||
|
||||
void buildCoroutineFrame(Function &F, Shape &Shape);
|
||||
|
||||
} // End namespace coro.
|
||||
} // End namespace llvm
|
||||
|
||||
#endif
|
865
external/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
vendored
Normal file
865
external/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
345
external/llvm/lib/Transforms/Coroutines/Coroutines.cpp
vendored
Normal file
345
external/llvm/lib/Transforms/Coroutines/Coroutines.cpp
vendored
Normal file
@ -0,0 +1,345 @@
|
||||
//===- Coroutines.cpp -----------------------------------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implements the common infrastructure for Coroutine Passes.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "CoroInstr.h"
|
||||
#include "CoroInternal.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Analysis/CallGraph.h"
|
||||
#include "llvm/Analysis/CallGraphSCCPass.h"
|
||||
#include "llvm/IR/Attributes.h"
|
||||
#include "llvm/IR/CallSite.h"
|
||||
#include "llvm/IR/Constants.h"
|
||||
#include "llvm/IR/DerivedTypes.h"
|
||||
#include "llvm/IR/Function.h"
|
||||
#include "llvm/IR/InstIterator.h"
|
||||
#include "llvm/IR/Instructions.h"
|
||||
#include "llvm/IR/IntrinsicInst.h"
|
||||
#include "llvm/IR/Intrinsics.h"
|
||||
#include "llvm/IR/LegacyPassManager.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/IR/Type.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Transforms/Coroutines.h"
|
||||
#include "llvm/Transforms/IPO.h"
|
||||
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
|
||||
#include "llvm/Transforms/Utils/Local.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
void llvm::initializeCoroutines(PassRegistry &Registry) {
|
||||
initializeCoroEarlyPass(Registry);
|
||||
initializeCoroSplitPass(Registry);
|
||||
initializeCoroElidePass(Registry);
|
||||
initializeCoroCleanupPass(Registry);
|
||||
}
|
||||
|
||||
static void addCoroutineOpt0Passes(const PassManagerBuilder &Builder,
|
||||
legacy::PassManagerBase &PM) {
|
||||
PM.add(createCoroSplitPass());
|
||||
PM.add(createCoroElidePass());
|
||||
|
||||
PM.add(createBarrierNoopPass());
|
||||
PM.add(createCoroCleanupPass());
|
||||
}
|
||||
|
||||
static void addCoroutineEarlyPasses(const PassManagerBuilder &Builder,
|
||||
legacy::PassManagerBase &PM) {
|
||||
PM.add(createCoroEarlyPass());
|
||||
}
|
||||
|
||||
static void addCoroutineScalarOptimizerPasses(const PassManagerBuilder &Builder,
|
||||
legacy::PassManagerBase &PM) {
|
||||
PM.add(createCoroElidePass());
|
||||
}
|
||||
|
||||
static void addCoroutineSCCPasses(const PassManagerBuilder &Builder,
|
||||
legacy::PassManagerBase &PM) {
|
||||
PM.add(createCoroSplitPass());
|
||||
}
|
||||
|
||||
static void addCoroutineOptimizerLastPasses(const PassManagerBuilder &Builder,
|
||||
legacy::PassManagerBase &PM) {
|
||||
PM.add(createCoroCleanupPass());
|
||||
}
|
||||
|
||||
void llvm::addCoroutinePassesToExtensionPoints(PassManagerBuilder &Builder) {
|
||||
Builder.addExtension(PassManagerBuilder::EP_EarlyAsPossible,
|
||||
addCoroutineEarlyPasses);
|
||||
Builder.addExtension(PassManagerBuilder::EP_EnabledOnOptLevel0,
|
||||
addCoroutineOpt0Passes);
|
||||
Builder.addExtension(PassManagerBuilder::EP_CGSCCOptimizerLate,
|
||||
addCoroutineSCCPasses);
|
||||
Builder.addExtension(PassManagerBuilder::EP_ScalarOptimizerLate,
|
||||
addCoroutineScalarOptimizerPasses);
|
||||
Builder.addExtension(PassManagerBuilder::EP_OptimizerLast,
|
||||
addCoroutineOptimizerLastPasses);
|
||||
}
|
||||
|
||||
// Construct the lowerer base class and initialize its members.
|
||||
coro::LowererBase::LowererBase(Module &M)
|
||||
: TheModule(M), Context(M.getContext()),
|
||||
Int8Ptr(Type::getInt8PtrTy(Context)),
|
||||
ResumeFnType(FunctionType::get(Type::getVoidTy(Context), Int8Ptr,
|
||||
/*isVarArg=*/false)),
|
||||
NullPtr(ConstantPointerNull::get(Int8Ptr)) {}
|
||||
|
||||
// Creates a sequence of instructions to obtain a resume function address using
|
||||
// llvm.coro.subfn.addr. It generates the following sequence:
|
||||
//
|
||||
// call i8* @llvm.coro.subfn.addr(i8* %Arg, i8 %index)
|
||||
// bitcast i8* %2 to void(i8*)*
|
||||
|
||||
Value *coro::LowererBase::makeSubFnCall(Value *Arg, int Index,
|
||||
Instruction *InsertPt) {
|
||||
auto *IndexVal = ConstantInt::get(Type::getInt8Ty(Context), Index);
|
||||
auto *Fn = Intrinsic::getDeclaration(&TheModule, Intrinsic::coro_subfn_addr);
|
||||
|
||||
assert(Index >= CoroSubFnInst::IndexFirst &&
|
||||
Index < CoroSubFnInst::IndexLast &&
|
||||
"makeSubFnCall: Index value out of range");
|
||||
auto *Call = CallInst::Create(Fn, {Arg, IndexVal}, "", InsertPt);
|
||||
|
||||
auto *Bitcast =
|
||||
new BitCastInst(Call, ResumeFnType->getPointerTo(), "", InsertPt);
|
||||
return Bitcast;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static bool isCoroutineIntrinsicName(StringRef Name) {
|
||||
// NOTE: Must be sorted!
|
||||
static const char *const CoroIntrinsics[] = {
|
||||
"llvm.coro.alloc", "llvm.coro.begin", "llvm.coro.destroy",
|
||||
"llvm.coro.done", "llvm.coro.end", "llvm.coro.frame",
|
||||
"llvm.coro.free", "llvm.coro.id", "llvm.coro.param",
|
||||
"llvm.coro.promise", "llvm.coro.resume", "llvm.coro.save",
|
||||
"llvm.coro.size", "llvm.coro.subfn.addr", "llvm.coro.suspend",
|
||||
};
|
||||
return Intrinsic::lookupLLVMIntrinsicByName(CoroIntrinsics, Name) != -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Verifies if a module has named values listed. Also, in debug mode verifies
|
||||
// that names are intrinsic names.
|
||||
bool coro::declaresIntrinsics(Module &M,
|
||||
std::initializer_list<StringRef> List) {
|
||||
for (StringRef Name : List) {
|
||||
assert(isCoroutineIntrinsicName(Name) && "not a coroutine intrinsic");
|
||||
if (M.getNamedValue(Name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace all coro.frees associated with the provided CoroId either with 'null'
|
||||
// if Elide is true and with its frame parameter otherwise.
|
||||
void coro::replaceCoroFree(CoroIdInst *CoroId, bool Elide) {
|
||||
SmallVector<CoroFreeInst *, 4> CoroFrees;
|
||||
for (User *U : CoroId->users())
|
||||
if (auto CF = dyn_cast<CoroFreeInst>(U))
|
||||
CoroFrees.push_back(CF);
|
||||
|
||||
if (CoroFrees.empty())
|
||||
return;
|
||||
|
||||
Value *Replacement =
|
||||
Elide ? ConstantPointerNull::get(Type::getInt8PtrTy(CoroId->getContext()))
|
||||
: CoroFrees.front()->getFrame();
|
||||
|
||||
for (CoroFreeInst *CF : CoroFrees) {
|
||||
CF->replaceAllUsesWith(Replacement);
|
||||
CF->eraseFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This code is stolen from CallGraph::addToCallGraph(Function *F), which
|
||||
// happens to be private. It is better for this functionality exposed by the
|
||||
// CallGraph.
|
||||
static void buildCGN(CallGraph &CG, CallGraphNode *Node) {
|
||||
Function *F = Node->getFunction();
|
||||
|
||||
// Look for calls by this function.
|
||||
for (Instruction &I : instructions(F))
|
||||
if (CallSite CS = CallSite(cast<Value>(&I))) {
|
||||
const Function *Callee = CS.getCalledFunction();
|
||||
if (!Callee || !Intrinsic::isLeaf(Callee->getIntrinsicID()))
|
||||
// Indirect calls of intrinsics are not allowed so no need to check.
|
||||
// We can be more precise here by using TargetArg returned by
|
||||
// Intrinsic::isLeaf.
|
||||
Node->addCalledFunction(CS, CG.getCallsExternalNode());
|
||||
else if (!Callee->isIntrinsic())
|
||||
Node->addCalledFunction(CS, CG.getOrInsertFunction(Callee));
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild CGN after we extracted parts of the code from ParentFunc into
|
||||
// NewFuncs. Builds CGNs for the NewFuncs and adds them to the current SCC.
|
||||
void coro::updateCallGraph(Function &ParentFunc, ArrayRef<Function *> NewFuncs,
|
||||
CallGraph &CG, CallGraphSCC &SCC) {
|
||||
// Rebuild CGN from scratch for the ParentFunc
|
||||
auto *ParentNode = CG[&ParentFunc];
|
||||
ParentNode->removeAllCalledFunctions();
|
||||
buildCGN(CG, ParentNode);
|
||||
|
||||
SmallVector<CallGraphNode *, 8> Nodes(SCC.begin(), SCC.end());
|
||||
|
||||
for (Function *F : NewFuncs) {
|
||||
CallGraphNode *Callee = CG.getOrInsertFunction(F);
|
||||
Nodes.push_back(Callee);
|
||||
buildCGN(CG, Callee);
|
||||
}
|
||||
|
||||
SCC.initialize(Nodes);
|
||||
}
|
||||
|
||||
static void clear(coro::Shape &Shape) {
|
||||
Shape.CoroBegin = nullptr;
|
||||
Shape.CoroEnds.clear();
|
||||
Shape.CoroSizes.clear();
|
||||
Shape.CoroSuspends.clear();
|
||||
|
||||
Shape.FrameTy = nullptr;
|
||||
Shape.FramePtr = nullptr;
|
||||
Shape.AllocaSpillBlock = nullptr;
|
||||
Shape.ResumeSwitch = nullptr;
|
||||
Shape.PromiseAlloca = nullptr;
|
||||
Shape.HasFinalSuspend = false;
|
||||
}
|
||||
|
||||
static CoroSaveInst *createCoroSave(CoroBeginInst *CoroBegin,
|
||||
CoroSuspendInst *SuspendInst) {
|
||||
Module *M = SuspendInst->getModule();
|
||||
auto *Fn = Intrinsic::getDeclaration(M, Intrinsic::coro_save);
|
||||
auto *SaveInst =
|
||||
cast<CoroSaveInst>(CallInst::Create(Fn, CoroBegin, "", SuspendInst));
|
||||
assert(!SuspendInst->getCoroSave());
|
||||
SuspendInst->setArgOperand(0, SaveInst);
|
||||
return SaveInst;
|
||||
}
|
||||
|
||||
// Collect "interesting" coroutine intrinsics.
|
||||
void coro::Shape::buildFrom(Function &F) {
|
||||
size_t FinalSuspendIndex = 0;
|
||||
clear(*this);
|
||||
SmallVector<CoroFrameInst *, 8> CoroFrames;
|
||||
SmallVector<CoroSaveInst *, 2> UnusedCoroSaves;
|
||||
|
||||
for (Instruction &I : instructions(F)) {
|
||||
if (auto II = dyn_cast<IntrinsicInst>(&I)) {
|
||||
switch (II->getIntrinsicID()) {
|
||||
default:
|
||||
continue;
|
||||
case Intrinsic::coro_size:
|
||||
CoroSizes.push_back(cast<CoroSizeInst>(II));
|
||||
break;
|
||||
case Intrinsic::coro_frame:
|
||||
CoroFrames.push_back(cast<CoroFrameInst>(II));
|
||||
break;
|
||||
case Intrinsic::coro_save:
|
||||
// After optimizations, coro_suspends using this coro_save might have
|
||||
// been removed, remember orphaned coro_saves to remove them later.
|
||||
if (II->use_empty())
|
||||
UnusedCoroSaves.push_back(cast<CoroSaveInst>(II));
|
||||
break;
|
||||
case Intrinsic::coro_suspend:
|
||||
CoroSuspends.push_back(cast<CoroSuspendInst>(II));
|
||||
if (CoroSuspends.back()->isFinal()) {
|
||||
if (HasFinalSuspend)
|
||||
report_fatal_error(
|
||||
"Only one suspend point can be marked as final");
|
||||
HasFinalSuspend = true;
|
||||
FinalSuspendIndex = CoroSuspends.size() - 1;
|
||||
}
|
||||
break;
|
||||
case Intrinsic::coro_begin: {
|
||||
auto CB = cast<CoroBeginInst>(II);
|
||||
if (CB->getId()->getInfo().isPreSplit()) {
|
||||
if (CoroBegin)
|
||||
report_fatal_error(
|
||||
"coroutine should have exactly one defining @llvm.coro.begin");
|
||||
CB->addAttribute(AttributeList::ReturnIndex, Attribute::NonNull);
|
||||
CB->addAttribute(AttributeList::ReturnIndex, Attribute::NoAlias);
|
||||
CB->removeAttribute(AttributeList::FunctionIndex,
|
||||
Attribute::NoDuplicate);
|
||||
CoroBegin = CB;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Intrinsic::coro_end:
|
||||
CoroEnds.push_back(cast<CoroEndInst>(II));
|
||||
if (CoroEnds.back()->isFallthrough()) {
|
||||
// Make sure that the fallthrough coro.end is the first element in the
|
||||
// CoroEnds vector.
|
||||
if (CoroEnds.size() > 1) {
|
||||
if (CoroEnds.front()->isFallthrough())
|
||||
report_fatal_error(
|
||||
"Only one coro.end can be marked as fallthrough");
|
||||
std::swap(CoroEnds.front(), CoroEnds.back());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If for some reason, we were not able to find coro.begin, bailout.
|
||||
if (!CoroBegin) {
|
||||
// Replace coro.frame which are supposed to be lowered to the result of
|
||||
// coro.begin with undef.
|
||||
auto *Undef = UndefValue::get(Type::getInt8PtrTy(F.getContext()));
|
||||
for (CoroFrameInst *CF : CoroFrames) {
|
||||
CF->replaceAllUsesWith(Undef);
|
||||
CF->eraseFromParent();
|
||||
}
|
||||
|
||||
// Replace all coro.suspend with undef and remove related coro.saves if
|
||||
// present.
|
||||
for (CoroSuspendInst *CS : CoroSuspends) {
|
||||
CS->replaceAllUsesWith(UndefValue::get(CS->getType()));
|
||||
CS->eraseFromParent();
|
||||
if (auto *CoroSave = CS->getCoroSave())
|
||||
CoroSave->eraseFromParent();
|
||||
}
|
||||
|
||||
// Replace all coro.ends with unreachable instruction.
|
||||
for (CoroEndInst *CE : CoroEnds)
|
||||
changeToUnreachable(CE, /*UseLLVMTrap=*/false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The coro.free intrinsic is always lowered to the result of coro.begin.
|
||||
for (CoroFrameInst *CF : CoroFrames) {
|
||||
CF->replaceAllUsesWith(CoroBegin);
|
||||
CF->eraseFromParent();
|
||||
}
|
||||
|
||||
// Canonicalize coro.suspend by inserting a coro.save if needed.
|
||||
for (CoroSuspendInst *CS : CoroSuspends)
|
||||
if (!CS->getCoroSave())
|
||||
createCoroSave(CoroBegin, CS);
|
||||
|
||||
// Move final suspend to be the last element in the CoroSuspends vector.
|
||||
if (HasFinalSuspend &&
|
||||
FinalSuspendIndex != CoroSuspends.size() - 1)
|
||||
std::swap(CoroSuspends[FinalSuspendIndex], CoroSuspends.back());
|
||||
|
||||
// Remove orphaned coro.saves.
|
||||
for (CoroSaveInst *CoroSave : UnusedCoroSaves)
|
||||
CoroSave->eraseFromParent();
|
||||
}
|
22
external/llvm/lib/Transforms/Coroutines/LLVMBuild.txt
vendored
Normal file
22
external/llvm/lib/Transforms/Coroutines/LLVMBuild.txt
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
;===- ./lib/Transforms/Coroutines/LLVMBuild.txt ----------------*- Conf -*--===;
|
||||
;
|
||||
; The LLVM Compiler Infrastructure
|
||||
;
|
||||
; This file is distributed under the University of Illinois Open Source
|
||||
; License. See LICENSE.TXT for details.
|
||||
;
|
||||
;===------------------------------------------------------------------------===;
|
||||
;
|
||||
; This is an LLVMBuild description file for the components in this subdirectory.
|
||||
;
|
||||
; For more information on the LLVMBuild system, please see:
|
||||
;
|
||||
; http://llvm.org/docs/LLVMBuild.html
|
||||
;
|
||||
;===------------------------------------------------------------------------===;
|
||||
|
||||
[component_0]
|
||||
type = Library
|
||||
name = Coroutines
|
||||
parent = Transforms
|
||||
required_libraries = Analysis Core IPO Scalar Support TransformUtils
|
Reference in New Issue
Block a user