29#include "llvm/Support/Debug.h"
33#define GEN_PASS_DEF_ASYNCTOASYNCRUNTIMEPASS
34#define GEN_PASS_DEF_ASYNCFUNCTOASYNCRUNTIMEPASS
35#include "mlir/Dialect/Async/Passes.h.inc"
41#define DEBUG_TYPE "async-to-async-runtime"
47class AsyncToAsyncRuntimePass
48 :
public impl::AsyncToAsyncRuntimePassBase<AsyncToAsyncRuntimePass> {
50 AsyncToAsyncRuntimePass() =
default;
51 void runOnOperation()
override;
58class AsyncFuncToAsyncRuntimePass
59 :
public impl::AsyncFuncToAsyncRuntimePassBase<
60 AsyncFuncToAsyncRuntimePass> {
62 AsyncFuncToAsyncRuntimePass() =
default;
63 void runOnOperation()
override;
90 std::optional<Value> asyncToken;
91 llvm::SmallVector<Value, 4> returnValues;
96 std::optional<Block *> setError;
124 std::optional<Block *> cleanupForDestroy;
130 std::shared_ptr<llvm::DenseMap<func::FuncOp, CoroMachinery>>;
177 assert(!
func.getBlocks().empty() &&
"Function must have an entry block");
180 Block *entryBlock = &
func.getBlocks().front();
181 Block *originalEntryBlock =
191 bool isStateful = isa<async::TokenType>(
func.getResultTypes().front());
193 std::optional<Value> retToken;
196 RuntimeCreateOp::create(builder, async::TokenType::get(ctx)));
200 isStateful ?
func.getResultTypes().drop_front() :
func.getResultTypes();
201 for (
auto resType : resValueTypes)
202 retValues.emplace_back(
203 RuntimeCreateOp::create(builder, resType).getResult());
208 auto coroIdOp = CoroIdOp::create(builder, CoroIdType::get(ctx));
210 CoroBeginOp::create(builder, CoroHandleType::get(ctx), coroIdOp.getId());
211 cf::BranchOp::create(builder, originalEntryBlock);
221 builder.setInsertionPointToStart(cleanupBlock);
222 CoroFreeOp::create(builder, coroIdOp.getId(), coroHdlOp.getHandle());
223 cf::BranchOp::create(builder, suspendBlock);
229 builder.setInsertionPointToStart(suspendBlock);
232 CoroEndOp::create(builder, coroHdlOp.getHandle());
238 ret.push_back(*retToken);
239 llvm::append_range(ret, retValues);
240 func::ReturnOp::create(builder, ret);
247 func->setDiscardableAttr(
249 builder.getArrayAttr(StringAttr::get(ctx,
"presplitcoroutine")));
251 CoroMachinery machinery;
252 machinery.func =
func;
253 machinery.asyncToken = retToken;
254 machinery.returnValues = retValues;
255 machinery.coroId = coroIdOp.getId();
256 machinery.coroHandle = coroHdlOp.getHandle();
257 machinery.entry = entryBlock;
258 machinery.setError = std::nullopt;
259 machinery.cleanup = cleanupBlock;
260 machinery.cleanupForDestroy = std::nullopt;
261 machinery.suspend = suspendBlock;
269 return *coro.setError;
271 coro.setError = coro.func.addBlock();
272 (*coro.setError)->moveBefore(coro.cleanup);
279 RuntimeSetErrorOp::create(builder, *coro.asyncToken);
281 for (
Value retValue : coro.returnValues)
282 RuntimeSetErrorOp::create(builder, retValue);
285 cf::BranchOp::create(builder, coro.cleanup);
287 return *coro.setError;
294 CoroMachinery &coro) {
295 if (coro.cleanupForDestroy)
296 return *coro.cleanupForDestroy;
298 coro.cleanupForDestroy = builder.
createBlock(coro.suspend);
299 CoroFreeOp::create(builder, coro.coroId, coro.coroHandle);
300 cf::BranchOp::create(builder, coro.suspend);
301 return *coro.cleanupForDestroy;
312static std::pair<func::FuncOp, CoroMachinery>
314 ModuleOp module = execute->getParentOfType<ModuleOp>();
325 execute.getDependencies());
326 functionInputs.insert_range(execute.getBodyOperands());
330 auto typesRange = llvm::map_range(
331 functionInputs, [](
Value value) {
return value.
getType(); });
333 auto outputTypes = execute.getResultTypes();
335 auto funcType = FunctionType::get(ctx, inputTypes, outputTypes);
349 size_t numDependencies = execute.getDependencies().size();
350 size_t numOperands = execute.getBodyOperands().size();
353 for (
size_t i = 0; i < numDependencies; ++i)
354 AwaitOp::create(builder,
func.getArgument(i));
358 for (
size_t i = 0; i < numOperands; ++i) {
359 Value operand =
func.getArgument(numDependencies + i);
360 unwrappedOperands[i] = AwaitOp::create(builder, loc, operand).getResult();
366 valueMapping.
map(functionInputs,
func.getArguments());
367 valueMapping.
map(execute.getBodyRegion().getArguments(), unwrappedOperands);
371 for (
Operation &op : execute.getBodyRegion().getOps())
372 builder.clone(op, valueMapping);
382 cf::BranchOp branch = cast<cf::BranchOp>(coro.entry->
getTerminator());
383 builder.setInsertionPointToEnd(coro.entry);
387 CoroSaveOp::create(builder, CoroStateType::get(ctx), coro.coroHandle);
391 RuntimeResumeOp::create(builder, coro.coroHandle);
395 CoroSuspendOp::create(builder, coroSaveOp.getState(), coro.suspend,
396 branch.getDest(), destroy);
404 auto callOutlinedFunc = func::CallOp::create(callBuilder,
func.getName(),
405 execute.getResultTypes(),
406 functionInputs.getArrayRef());
407 execute.replaceAllUsesWith(callOutlinedFunc.getResults());
419class CreateGroupOpLowering :
public OpConversionPattern<CreateGroupOp> {
421 using OpConversionPattern::OpConversionPattern;
424 matchAndRewrite(CreateGroupOp op, OpAdaptor adaptor,
425 ConversionPatternRewriter &rewriter)
const override {
426 rewriter.replaceOpWithNewOp<RuntimeCreateGroupOp>(
427 op, GroupType::get(op->getContext()), adaptor.getOperands());
438class AddToGroupOpLowering :
public OpConversionPattern<AddToGroupOp> {
440 using OpConversionPattern::OpConversionPattern;
443 matchAndRewrite(AddToGroupOp op, OpAdaptor adaptor,
444 ConversionPatternRewriter &rewriter)
const override {
445 rewriter.replaceOpWithNewOp<RuntimeAddToGroupOp>(
446 op, rewriter.getIndexType(), adaptor.getOperands());
463class AsyncFuncOpLowering :
public OpConversionPattern<async::FuncOp> {
466 : OpConversionPattern<async::FuncOp>(ctx), coros(std::move(coros)) {}
469 matchAndRewrite(async::FuncOp op, OpAdaptor adaptor,
470 ConversionPatternRewriter &rewriter)
const override {
471 Location loc = op->getLoc();
474 func::FuncOp::create(rewriter, loc, op.getName(), op.getFunctionType());
479 for (
const auto &namedAttr : op->getDiscardableAttrDictionary().getValue())
480 newFuncOp->setDiscardableAttr(namedAttr.getName(), namedAttr.getValue());
482 rewriter.inlineRegionBefore(op.getBody(), newFuncOp.getBody(),
486 (*coros)[newFuncOp] = coro;
489 rewriter.eraseOp(op);
501class AsyncCallOpLowering :
public OpConversionPattern<async::CallOp> {
503 AsyncCallOpLowering(MLIRContext *ctx)
504 : OpConversionPattern<async::CallOp>(ctx) {}
507 matchAndRewrite(async::CallOp op, OpAdaptor adaptor,
508 ConversionPatternRewriter &rewriter)
const override {
509 rewriter.replaceOpWithNewOp<func::CallOp>(
510 op, op.getCallee(), op.getResultTypes(), op.getOperands());
519class AsyncReturnOpLowering :
public OpConversionPattern<async::ReturnOp> {
522 : OpConversionPattern<async::ReturnOp>(ctx), coros(std::move(coros)) {}
525 matchAndRewrite(async::ReturnOp op, OpAdaptor adaptor,
526 ConversionPatternRewriter &rewriter)
const override {
527 auto func = op->template getParentOfType<func::FuncOp>();
528 auto funcCoro = coros->find(func);
529 if (funcCoro == coros->end())
530 return rewriter.notifyMatchFailure(
531 op,
"operation is not inside the async coroutine function");
533 Location loc = op->getLoc();
534 const CoroMachinery &coro = funcCoro->getSecond();
535 rewriter.setInsertionPointAfter(op);
539 for (
auto tuple : llvm::zip(adaptor.getOperands(), coro.returnValues)) {
540 Value returnValue = std::get<0>(tuple);
541 Value asyncValue = std::get<1>(tuple);
542 RuntimeStoreOp::create(rewriter, loc, returnValue, asyncValue);
543 RuntimeSetAvailableOp::create(rewriter, loc, asyncValue);
548 RuntimeSetAvailableOp::create(rewriter, loc, *coro.asyncToken);
550 rewriter.eraseOp(op);
551 cf::BranchOp::create(rewriter, loc, coro.cleanup);
566template <
typename AwaitType,
typename AwaitableType>
567class AwaitOpLoweringBase :
public OpConversionPattern<AwaitType> {
568 using AwaitAdaptor =
typename AwaitType::Adaptor;
572 bool shouldLowerBlockingWait)
573 : OpConversionPattern<AwaitType>(ctx), coros(std::move(coros)),
574 shouldLowerBlockingWait(shouldLowerBlockingWait) {}
577 matchAndRewrite(AwaitType op,
typename AwaitType::Adaptor adaptor,
578 ConversionPatternRewriter &rewriter)
const override {
581 if (!isa<AwaitableType>(op.getOperand().getType()))
582 return rewriter.notifyMatchFailure(op,
"unsupported awaitable type");
585 auto func = op->template getParentOfType<func::FuncOp>();
586 auto funcCoro = coros->find(func);
587 const bool isInCoroutine = funcCoro != coros->end();
589 Location loc = op->getLoc();
590 Value operand = adaptor.getOperand();
592 Type i1 = rewriter.getI1Type();
595 if (!isInCoroutine && !shouldLowerBlockingWait)
600 if (!isInCoroutine) {
601 ImplicitLocOpBuilder builder(loc, rewriter);
602 RuntimeAwaitOp::create(builder, loc, operand);
605 Value isError = RuntimeIsErrorOp::create(builder, i1, operand);
606 Value notError = arith::XOrIOp::create(
608 arith::ConstantOp::create(builder, loc, i1,
609 builder.getIntegerAttr(i1, 1)));
611 cf::AssertOp::create(builder, notError,
612 "Awaited async operand is in error state");
618 CoroMachinery &coro = funcCoro->getSecond();
619 Block *suspended = op->getBlock();
621 ImplicitLocOpBuilder builder(loc, rewriter);
622 MLIRContext *ctx = op->getContext();
627 CoroSaveOp::create(builder, CoroStateType::get(ctx), coro.coroHandle);
628 RuntimeAwaitAndResumeOp::create(builder, operand, coro.coroHandle);
635 builder.setInsertionPointToEnd(suspended);
636 CoroSuspendOp::create(builder, coroSaveOp.getState(), coro.suspend,
643 builder.setInsertionPointToStart(resume);
644 auto isError = RuntimeIsErrorOp::create(builder, loc, i1, operand);
645 cf::CondBranchOp::create(builder, isError,
653 rewriter.setInsertionPointToStart(continuation);
657 if (Value replaceWith = getReplacementValue(op, operand, rewriter))
658 rewriter.replaceOp(op, replaceWith);
660 rewriter.eraseOp(op);
665 virtual Value getReplacementValue(AwaitType op, Value operand,
666 ConversionPatternRewriter &rewriter)
const {
672 bool shouldLowerBlockingWait;
676class AwaitTokenOpLowering
677 :
public AwaitOpLoweringBase<AwaitOp, async::TokenType> {
678 using Base = AwaitOpLoweringBase<AwaitOp, async::TokenType>;
685class AwaitValueOpLowering :
public AwaitOpLoweringBase<AwaitOp, ValueType> {
686 using Base = AwaitOpLoweringBase<AwaitOp, ValueType>;
692 getReplacementValue(AwaitOp op, Value operand,
693 ConversionPatternRewriter &rewriter)
const override {
695 auto valueType = cast<ValueType>(operand.
getType()).getValueType();
696 return RuntimeLoadOp::create(rewriter, op->getLoc(), valueType, operand);
701class AwaitAllOpLowering :
public AwaitOpLoweringBase<AwaitAllOp, GroupType> {
702 using Base = AwaitOpLoweringBase<AwaitAllOp, GroupType>;
717 : OpConversionPattern<
async::YieldOp>(ctx), coros(std::move(coros)) {}
721 ConversionPatternRewriter &rewriter)
const override {
723 auto func = op->template getParentOfType<func::FuncOp>();
724 auto funcCoro = coros->find(
func);
725 if (funcCoro == coros->end())
726 return rewriter.notifyMatchFailure(
727 op,
"operation is not inside the async coroutine function");
730 const CoroMachinery &coro = funcCoro->getSecond();
734 for (
auto tuple : llvm::zip(adaptor.getOperands(), coro.returnValues)) {
735 Value yieldValue = std::get<0>(tuple);
736 Value asyncValue = std::get<1>(tuple);
737 RuntimeStoreOp::create(rewriter, loc, yieldValue, asyncValue);
738 RuntimeSetAvailableOp::create(rewriter, loc, asyncValue);
743 RuntimeSetAvailableOp::create(rewriter, loc, *coro.asyncToken);
745 cf::BranchOp::create(rewriter, loc, coro.cleanup);
746 rewriter.eraseOp(op);
762 : OpConversionPattern<
cf::AssertOp>(ctx), coros(std::move(coros)) {}
766 ConversionPatternRewriter &rewriter)
const override {
768 auto func = op->template getParentOfType<func::FuncOp>();
769 auto funcCoro = coros->find(
func);
770 if (funcCoro == coros->end())
771 return rewriter.notifyMatchFailure(
772 op,
"operation is not inside the async coroutine function");
775 CoroMachinery &coro = funcCoro->getSecond();
778 rewriter.setInsertionPointToEnd(cont->getPrevNode());
779 cf::CondBranchOp::create(rewriter, loc, adaptor.getArg(),
784 rewriter.eraseOp(op);
794void AsyncToAsyncRuntimePass::runOnOperation() {
795 ModuleOp module = getOperation();
796 SymbolTable symbolTable(module);
801 std::make_shared<llvm::DenseMap<func::FuncOp, CoroMachinery>>();
803 module.walk([&](ExecuteOp execute) {
808 llvm::dbgs() <<
"Outlined " << coros->size()
809 <<
" functions built from async.execute operations\n";
813 auto isInCoroutine = [&](Operation *op) ->
bool {
814 auto parentFunc = op->getParentOfType<func::FuncOp>();
815 return coros->contains(parentFunc);
819 MLIRContext *ctx =
module->getContext();
820 RewritePatternSet asyncPatterns(ctx);
830 asyncPatterns.add<CreateGroupOpLowering, AddToGroupOpLowering>(ctx);
833 .add<AwaitTokenOpLowering, AwaitValueOpLowering, AwaitAllOpLowering>(
837 asyncPatterns.add<YieldOpLowering, AssertOpLowering>(ctx, coros);
840 ConversionTarget runtimeTarget(*ctx);
841 runtimeTarget.addLegalDialect<AsyncDialect, func::FuncDialect>();
842 runtimeTarget.addIllegalOp<CreateGroupOp, AddToGroupOp>();
843 runtimeTarget.addIllegalOp<ExecuteOp, AwaitOp, AwaitAllOp, async::YieldOp>();
846 runtimeTarget.addDynamicallyLegalDialect<scf::SCFDialect>([&](Operation *op) {
847 auto walkResult = op->walk([&](Operation *nested) {
848 bool isAsync = isa<async::AsyncDialect>(nested->
getDialect());
850 : WalkResult::advance();
852 return !walkResult.wasInterrupted();
854 runtimeTarget.addLegalOp<cf::AssertOp, arith::XOrIOp, arith::ConstantOp,
855 func::ConstantOp, cf::BranchOp, cf::CondBranchOp>();
858 runtimeTarget.addDynamicallyLegalOp<cf::AssertOp>(
859 [&](cf::AssertOp op) ->
bool {
860 auto func = op->getParentOfType<func::FuncOp>();
861 return !coros->contains(func);
864 if (
failed(applyPartialConversion(module, runtimeTarget,
865 std::move(asyncPatterns)))) {
877 std::make_shared<llvm::DenseMap<func::FuncOp, CoroMachinery>>();
880 patterns.
add<AsyncCallOpLowering>(ctx);
881 patterns.
add<AsyncFuncOpLowering, AsyncReturnOpLowering>(ctx, coros);
883 patterns.
add<AwaitTokenOpLowering, AwaitValueOpLowering, AwaitAllOpLowering>(
887 target.addDynamicallyLegalOp<AwaitOp, AwaitAllOp, YieldOp, cf::AssertOp>(
889 auto exec = op->getParentOfType<ExecuteOp>();
890 auto func = op->getParentOfType<func::FuncOp>();
891 return exec || !coros->contains(
func);
895void AsyncFuncToAsyncRuntimePass::runOnOperation() {
896 ModuleOp module = getOperation();
907 runtimeTarget.addLegalDialect<AsyncDialect, func::FuncDialect>();
908 runtimeTarget.addIllegalOp<async::FuncOp, async::CallOp, async::ReturnOp>();
910 runtimeTarget.addLegalOp<arith::XOrIOp, arith::ConstantOp, func::ConstantOp,
911 cf::BranchOp, cf::CondBranchOp>();
913 if (failed(applyPartialConversion(module, runtimeTarget,
914 std::move(asyncPatterns)))) {
static Block * setupCleanupForDestroyBlock(ImplicitLocOpBuilder &builder, CoroMachinery &coro)
static constexpr const char kAsyncFnPrefix[]
std::shared_ptr< llvm::DenseMap< func::FuncOp, CoroMachinery > > FuncCoroMapPtr
static Block * setupSetErrorBlock(CoroMachinery &coro)
static CoroMachinery setupCoroMachinery(func::FuncOp func)
Utility to partially update the regular function CFG to the coroutine CFG compatible with LLVM corout...
static std::pair< func::FuncOp, CoroMachinery > outlineExecuteOp(SymbolTable &symbolTable, ExecuteOp execute)
Outline the body region attached to the async.execute op into a standalone function.
AssertOpLowering(MLIRContext *ctx, FuncCoroMapPtr coros)
LogicalResult matchAndRewrite(cf::AssertOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(async::YieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
YieldOpLowering(MLIRContext *ctx, FuncCoroMapPtr coros)
Block represents an ordered list of Operations.
OpListType::iterator iterator
Block * splitBlock(iterator splitBefore)
Split the block into two blocks before the specified operation or iterator.
OpListType & getOperations()
Operation * getTerminator()
Get the terminator operation of this block.
typename cf::AssertOp::Adaptor OpAdaptor
This is a utility class for mapping one set of IR entities to another.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
static ImplicitLocOpBuilder atBlockBegin(Location loc, Block *block, Listener *listener=nullptr)
Create a builder and set the insertion point to before the first operation in the block but still ins...
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
RAII guard to reset the insertion point of the builder when destroyed.
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Operation is the basic unit of execution within MLIR.
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class allows for representing and managing the symbol table used by operations with the 'SymbolT...
static Visibility getSymbolVisibility(Operation *symbol)
Returns the visibility of the given symbol operation, which is required to implement SymbolOpInterfac...
static void setSymbolVisibility(Operation *symbol, Visibility vis)
Sets the visibility of the given symbol operation, which is required to implement SymbolOpInterface.
@ Private
The symbol is private and may only be referenced by SymbolRefAttrs local to the operations within the...
StringAttr insert(Operation *symbol, Block::iterator insertPt={})
Insert a new symbol into the table, and rename it as necessary to avoid collisions.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
static WalkResult interrupt()
void cloneConstantsIntoTheRegion(Region ®ion)
Clone ConstantLike operations that are defined above the given region and have users in the region in...
Include the generated interface declarations.
llvm::SetVector< T, Vector, Set, N > SetVector
void getUsedValuesDefinedAbove(Region ®ion, Region &limit, SetVector< Value > &values)
Fill values with a list of values defined at the ancestors of the limit region and used within region...
void populateSCFToControlFlowConversionPatterns(RewritePatternSet &patterns)
Collect a set of patterns to convert SCF operations to CFG branch-based operations within the Control...
void populateAsyncFuncToAsyncRuntimeConversionPatterns(RewritePatternSet &patterns, ConversionTarget &target)