Skip to content

[Coroutines] Support for Custom ABIs #111755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@ class TargetTransformInfoImplBase {
case Intrinsic::experimental_gc_relocate:
case Intrinsic::coro_alloc:
case Intrinsic::coro_begin:
case Intrinsic::coro_begin_custom_abi:
case Intrinsic::coro_free:
case Intrinsic::coro_end:
case Intrinsic::coro_frame:
Expand Down
3 changes: 2 additions & 1 deletion llvm/include/llvm/IR/Intrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -1719,7 +1719,8 @@ def int_coro_prepare_async : Intrinsic<[llvm_ptr_ty], [llvm_ptr_ty],
[IntrNoMem]>;
def int_coro_begin : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
[WriteOnly<ArgIndex<1>>]>;

def int_coro_begin_custom_abi : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty, llvm_i32_ty],
[WriteOnly<ArgIndex<1>>]>;
def int_coro_free : Intrinsic<[llvm_ptr_ty], [llvm_token_ty, llvm_ptr_ty],
[IntrReadMem, IntrArgMemOnly,
ReadOnly<ArgIndex<1>>,
Expand Down
8 changes: 7 additions & 1 deletion llvm/include/llvm/Transforms/Coroutines/ABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ namespace coro {
// This interface/API is to provide an object oriented way to implement ABI
// functionality. This is intended to replace use of the ABI enum to perform
// ABI operations. The ABIs (e.g. Switch, Async, Retcon{Once}) are the common
// ABIs.
// ABIs. However, specific users may need to modify the behavior of these. This
// can be accomplished by inheriting one of the common ABIs and overriding one
// or more of the methods to create a custom ABI. To use a custom ABI for a
// given coroutine the coro.begin.custom.abi intrinsic is used in place of the
// coro.begin intrinsic. This takes an additional i32 arg that specifies the
// index of an ABI generator for the custom ABI object in a SmallVector passed
// to CoroSplitPass ctor.

class BaseABI {
public:
Expand Down
19 changes: 15 additions & 4 deletions llvm/include/llvm/Transforms/Coroutines/CoroInstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ class AnyCoroIdInst : public IntrinsicInst {
IntrinsicInst *getCoroBegin() {
for (User *U : users())
if (auto *II = dyn_cast<IntrinsicInst>(U))
if (II->getIntrinsicID() == Intrinsic::coro_begin)
if (II->getIntrinsicID() == Intrinsic::coro_begin ||
II->getIntrinsicID() == Intrinsic::coro_begin_custom_abi)
return II;
llvm_unreachable("no coro.begin associated with coro.id");
}
Expand Down Expand Up @@ -442,20 +443,30 @@ class CoroFreeInst : public IntrinsicInst {
}
};

/// This class represents the llvm.coro.begin instructions.
/// This class represents the llvm.coro.begin or llvm.coro.begin.custom.abi
/// instructions.
class CoroBeginInst : public IntrinsicInst {
enum { IdArg, MemArg };
enum { IdArg, MemArg, CustomABIArg };

public:
AnyCoroIdInst *getId() const {
return cast<AnyCoroIdInst>(getArgOperand(IdArg));
}

bool hasCustomABI() const {
return getIntrinsicID() == Intrinsic::coro_begin_custom_abi;
}

int getCustomABI() const {
return cast<ConstantInt>(getArgOperand(CustomABIArg))->getZExtValue();
}

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;
return I->getIntrinsicID() == Intrinsic::coro_begin ||
I->getIntrinsicID() == Intrinsic::coro_begin_custom_abi;
}
static bool classof(const Value *V) {
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
Expand Down
13 changes: 11 additions & 2 deletions llvm/include/llvm/Transforms/Coroutines/CoroSplit.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,26 @@ struct Shape;
} // namespace coro

struct CoroSplitPass : PassInfoMixin<CoroSplitPass> {
using BaseABITy =
std::function<std::unique_ptr<coro::BaseABI>(Function &, coro::Shape &)>;

CoroSplitPass(bool OptimizeFrame = false);

CoroSplitPass(SmallVector<BaseABITy> GenCustomABIs,
bool OptimizeFrame = false);

CoroSplitPass(std::function<bool(Instruction &)> MaterializableCallback,
bool OptimizeFrame = false);

CoroSplitPass(std::function<bool(Instruction &)> MaterializableCallback,
SmallVector<BaseABITy> GenCustomABIs,
bool OptimizeFrame = false);

PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
LazyCallGraph &CG, CGSCCUpdateResult &UR);

static bool isRequired() { return true; }

using BaseABITy =
std::function<std::unique_ptr<coro::BaseABI>(Function &, coro::Shape &)>;
// Generator for an ABI transformer
BaseABITy CreateAndInitABI;

Expand Down
4 changes: 3 additions & 1 deletion llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ bool Lowerer::lower(Function &F) {
default:
continue;
case Intrinsic::coro_begin:
case Intrinsic::coro_begin_custom_abi:
II->replaceAllUsesWith(II->getArgOperand(1));
break;
case Intrinsic::coro_free:
Expand Down Expand Up @@ -112,7 +113,8 @@ static bool declaresCoroCleanupIntrinsics(const Module &M) {
M, {"llvm.coro.alloc", "llvm.coro.begin", "llvm.coro.subfn.addr",
"llvm.coro.free", "llvm.coro.id", "llvm.coro.id.retcon",
"llvm.coro.id.async", "llvm.coro.id.retcon.once",
"llvm.coro.async.size.replace", "llvm.coro.async.resume"});
"llvm.coro.async.size.replace", "llvm.coro.async.resume",
"llvm.coro.begin.custom.abi"});
}

PreservedAnalyses CoroCleanupPass::run(Module &M,
Expand Down
38 changes: 35 additions & 3 deletions llvm/lib/Transforms/Coroutines/CoroSplit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2200,7 +2200,15 @@ static void addPrepareFunction(const Module &M,

static std::unique_ptr<coro::BaseABI>
CreateNewABI(Function &F, coro::Shape &S,
std::function<bool(Instruction &)> IsMatCallback) {
std::function<bool(Instruction &)> IsMatCallback,
const SmallVector<CoroSplitPass::BaseABITy> GenCustomABIs) {
if (S.CoroBegin->hasCustomABI()) {
unsigned CustomABI = S.CoroBegin->getCustomABI();
if (CustomABI >= GenCustomABIs.size())
llvm_unreachable("Custom ABI not found amoung those specified");
return GenCustomABIs[CustomABI](F, S);
}

switch (S.ABI) {
case coro::ABI::Switch:
return std::unique_ptr<coro::BaseABI>(
Expand All @@ -2221,7 +2229,17 @@ CreateNewABI(Function &F, coro::Shape &S,
CoroSplitPass::CoroSplitPass(bool OptimizeFrame)
: CreateAndInitABI([](Function &F, coro::Shape &S) {
std::unique_ptr<coro::BaseABI> ABI =
CreateNewABI(F, S, coro::isTriviallyMaterializable);
CreateNewABI(F, S, coro::isTriviallyMaterializable, {});
ABI->init();
return ABI;
}),
OptimizeFrame(OptimizeFrame) {}

CoroSplitPass::CoroSplitPass(
SmallVector<CoroSplitPass::BaseABITy> GenCustomABIs, bool OptimizeFrame)
: CreateAndInitABI([=](Function &F, coro::Shape &S) {
std::unique_ptr<coro::BaseABI> ABI =
CreateNewABI(F, S, coro::isTriviallyMaterializable, GenCustomABIs);
ABI->init();
return ABI;
}),
Expand All @@ -2232,7 +2250,21 @@ CoroSplitPass::CoroSplitPass(bool OptimizeFrame)
CoroSplitPass::CoroSplitPass(std::function<bool(Instruction &)> IsMatCallback,
bool OptimizeFrame)
: CreateAndInitABI([=](Function &F, coro::Shape &S) {
std::unique_ptr<coro::BaseABI> ABI = CreateNewABI(F, S, IsMatCallback);
std::unique_ptr<coro::BaseABI> ABI =
CreateNewABI(F, S, IsMatCallback, {});
ABI->init();
return ABI;
}),
OptimizeFrame(OptimizeFrame) {}

// For back compatibility, constructor takes a materializable callback and
// creates a generator for an ABI with a modified materializable callback.
CoroSplitPass::CoroSplitPass(
std::function<bool(Instruction &)> IsMatCallback,
SmallVector<CoroSplitPass::BaseABITy> GenCustomABIs, bool OptimizeFrame)
: CreateAndInitABI([=](Function &F, coro::Shape &S) {
std::unique_ptr<coro::BaseABI> ABI =
CreateNewABI(F, S, IsMatCallback, GenCustomABIs);
ABI->init();
return ABI;
}),
Expand Down
4 changes: 3 additions & 1 deletion llvm/lib/Transforms/Coroutines/Coroutines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ static const char *const CoroIntrinsics[] = {
"llvm.coro.await.suspend.handle",
"llvm.coro.await.suspend.void",
"llvm.coro.begin",
"llvm.coro.begin.custom.abi",
"llvm.coro.destroy",
"llvm.coro.done",
"llvm.coro.end",
Expand Down Expand Up @@ -247,7 +248,8 @@ void coro::Shape::analyze(Function &F,
}
break;
}
case Intrinsic::coro_begin: {
case Intrinsic::coro_begin:
case Intrinsic::coro_begin_custom_abi: {
auto CB = cast<CoroBeginInst>(II);

// Ignore coro id's that aren't pre-split.
Expand Down
87 changes: 87 additions & 0 deletions llvm/unittests/Transforms/Coroutines/ExtraRematTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,91 @@ TEST_F(ExtraRematTest, TestCoroRematWithCallback) {
CallInst *CI = getCallByName(Resume1, "should.remat");
ASSERT_TRUE(CI);
}

StringRef TextCoroBeginCustomABI = R"(
define ptr @f(i32 %n) presplitcoroutine {
entry:
%id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
%size = call i32 @llvm.coro.size.i32()
%alloc = call ptr @malloc(i32 %size)
%hdl = call ptr @llvm.coro.begin.custom.abi(token %id, ptr %alloc, i32 0)

%inc1 = add i32 %n, 1
%val2 = call i32 @should.remat(i32 %inc1)
%sp1 = call i8 @llvm.coro.suspend(token none, i1 false)
switch i8 %sp1, label %suspend [i8 0, label %resume1
i8 1, label %cleanup]
resume1:
%inc2 = add i32 %val2, 1
%sp2 = call i8 @llvm.coro.suspend(token none, i1 false)
switch i8 %sp1, label %suspend [i8 0, label %resume2
i8 1, label %cleanup]

resume2:
call void @print(i32 %val2)
call void @print(i32 %inc2)
br label %cleanup

cleanup:
%mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
call void @free(ptr %mem)
br label %suspend
suspend:
call i1 @llvm.coro.end(ptr %hdl, i1 0)
ret ptr %hdl
}

declare ptr @llvm.coro.free(token, ptr)
declare i32 @llvm.coro.size.i32()
declare i8 @llvm.coro.suspend(token, i1)
declare void @llvm.coro.resume(ptr)
declare void @llvm.coro.destroy(ptr)

declare token @llvm.coro.id(i32, ptr, ptr, ptr)
declare i1 @llvm.coro.alloc(token)
declare ptr @llvm.coro.begin.custom.abi(token, ptr, i32)
declare i1 @llvm.coro.end(ptr, i1)

declare i32 @should.remat(i32)

declare noalias ptr @malloc(i32)
declare void @print(i32)
declare void @free(ptr)
)";

// SwitchABI with overridden isMaterializable
class ExtraCustomABI : public coro::SwitchABI {
public:
ExtraCustomABI(Function &F, coro::Shape &S)
: coro::SwitchABI(F, S, ExtraMaterializable) {}
};

TEST_F(ExtraRematTest, TestCoroRematWithCustomABI) {
ParseAssembly(TextCoroBeginCustomABI);

ASSERT_TRUE(M);

CoroSplitPass::BaseABITy GenCustomABI = [](Function &F, coro::Shape &S) {
return std::unique_ptr<coro::BaseABI>(new ExtraCustomABI(F, S));
};

CGSCCPassManager CGPM;
CGPM.addPass(CoroSplitPass({GenCustomABI}));
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
MPM.run(*M, MAM);

// Verify that extra rematerializable instruction has been rematerialized
Function *F = M->getFunction("f.resume");
ASSERT_TRUE(F) << "could not find split function f.resume";

BasicBlock *Resume1 = getBasicBlockByName(F, "resume1");
ASSERT_TRUE(Resume1)
<< "could not find expected BB resume1 in split function";

// With callback the extra rematerialization of the function should have
// happened
CallInst *CI = getCallByName(Resume1, "should.remat");
ASSERT_TRUE(CI);
}

} // namespace
Loading