125f80e16SEugene Zhulenev //===- AsyncToAsyncRuntime.cpp - Lower from Async to Async Runtime --------===// 225f80e16SEugene Zhulenev // 325f80e16SEugene Zhulenev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 425f80e16SEugene Zhulenev // See https://llvm.org/LICENSE.txt for license information. 525f80e16SEugene Zhulenev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 625f80e16SEugene Zhulenev // 725f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 825f80e16SEugene Zhulenev // 925f80e16SEugene Zhulenev // This file implements lowering from high level async operations to async.coro 1025f80e16SEugene Zhulenev // and async.runtime operations. 1125f80e16SEugene Zhulenev // 1225f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 1325f80e16SEugene Zhulenev 1425f80e16SEugene Zhulenev #include "PassDetail.h" 15de7a4e53SEugene Zhulenev #include "mlir/Conversion/SCFToStandard/SCFToStandard.h" 1625f80e16SEugene Zhulenev #include "mlir/Dialect/Async/IR/Async.h" 1725f80e16SEugene Zhulenev #include "mlir/Dialect/Async/Passes.h" 18de7a4e53SEugene Zhulenev #include "mlir/Dialect/SCF/SCF.h" 1925f80e16SEugene Zhulenev #include "mlir/Dialect/StandardOps/IR/Ops.h" 2025f80e16SEugene Zhulenev #include "mlir/IR/BlockAndValueMapping.h" 2125f80e16SEugene Zhulenev #include "mlir/IR/ImplicitLocOpBuilder.h" 2225f80e16SEugene Zhulenev #include "mlir/IR/PatternMatch.h" 2325f80e16SEugene Zhulenev #include "mlir/Transforms/DialectConversion.h" 2425f80e16SEugene Zhulenev #include "mlir/Transforms/RegionUtils.h" 2525f80e16SEugene Zhulenev #include "llvm/ADT/SetVector.h" 26297a5b7cSNico Weber #include "llvm/Support/Debug.h" 2725f80e16SEugene Zhulenev 2825f80e16SEugene Zhulenev using namespace mlir; 2925f80e16SEugene Zhulenev using namespace mlir::async; 3025f80e16SEugene Zhulenev 3125f80e16SEugene Zhulenev #define DEBUG_TYPE "async-to-async-runtime" 3225f80e16SEugene Zhulenev // Prefix for functions outlined from `async.execute` op regions. 3325f80e16SEugene Zhulenev static constexpr const char kAsyncFnPrefix[] = "async_execute_fn"; 3425f80e16SEugene Zhulenev 3525f80e16SEugene Zhulenev namespace { 3625f80e16SEugene Zhulenev 3725f80e16SEugene Zhulenev class AsyncToAsyncRuntimePass 3825f80e16SEugene Zhulenev : public AsyncToAsyncRuntimeBase<AsyncToAsyncRuntimePass> { 3925f80e16SEugene Zhulenev public: 4025f80e16SEugene Zhulenev AsyncToAsyncRuntimePass() = default; 4125f80e16SEugene Zhulenev void runOnOperation() override; 4225f80e16SEugene Zhulenev }; 4325f80e16SEugene Zhulenev 4425f80e16SEugene Zhulenev } // namespace 4525f80e16SEugene Zhulenev 4625f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 4725f80e16SEugene Zhulenev // async.execute op outlining to the coroutine functions. 4825f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 4925f80e16SEugene Zhulenev 5025f80e16SEugene Zhulenev /// Function targeted for coroutine transformation has two additional blocks at 5125f80e16SEugene Zhulenev /// the end: coroutine cleanup and coroutine suspension. 5225f80e16SEugene Zhulenev /// 5325f80e16SEugene Zhulenev /// async.await op lowering additionaly creates a resume block for each 5425f80e16SEugene Zhulenev /// operation to enable non-blocking waiting via coroutine suspension. 5525f80e16SEugene Zhulenev namespace { 5625f80e16SEugene Zhulenev struct CoroMachinery { 5739957aa4SEugene Zhulenev FuncOp func; 5839957aa4SEugene Zhulenev 5925f80e16SEugene Zhulenev // Async execute region returns a completion token, and an async value for 6025f80e16SEugene Zhulenev // each yielded value. 6125f80e16SEugene Zhulenev // 6225f80e16SEugene Zhulenev // %token, %result = async.execute -> !async.value<T> { 6325f80e16SEugene Zhulenev // %0 = constant ... : T 6425f80e16SEugene Zhulenev // async.yield %0 : T 6525f80e16SEugene Zhulenev // } 6625f80e16SEugene Zhulenev Value asyncToken; // token representing completion of the async region 6725f80e16SEugene Zhulenev llvm::SmallVector<Value, 4> returnValues; // returned async values 6825f80e16SEugene Zhulenev 6925f80e16SEugene Zhulenev Value coroHandle; // coroutine handle (!async.coro.handle value) 7039957aa4SEugene Zhulenev Block *setError; // switch completion token and all values to error state 7125f80e16SEugene Zhulenev Block *cleanup; // coroutine cleanup block 7225f80e16SEugene Zhulenev Block *suspend; // coroutine suspension block 7325f80e16SEugene Zhulenev }; 7425f80e16SEugene Zhulenev } // namespace 7525f80e16SEugene Zhulenev 76*6ea22d46Sbakhtiyar /// Utility to partially update the regular function CFG to the coroutine CFG 77*6ea22d46Sbakhtiyar /// compatible with LLVM coroutines switched-resume lowering using 78*6ea22d46Sbakhtiyar /// `async.runtime.*` and `async.coro.*` operations. Modifies the entry block 79*6ea22d46Sbakhtiyar /// by prepending its ops with coroutine setup. Also inserts trailing blocks. 80*6ea22d46Sbakhtiyar /// 81*6ea22d46Sbakhtiyar /// The result types of the passed `func` must start with an `async.token` 82*6ea22d46Sbakhtiyar /// and be continued with some number of `async.value`s. 83*6ea22d46Sbakhtiyar /// 84*6ea22d46Sbakhtiyar /// It's up to the caller of this function to fix up the terminators of the 85*6ea22d46Sbakhtiyar /// preexisting blocks of the passed func op. If the passed `func` is legal, 86*6ea22d46Sbakhtiyar /// this typically means rewriting every return op as a yield op and a branch op 87*6ea22d46Sbakhtiyar /// to the suspend block. 8825f80e16SEugene Zhulenev /// 8925f80e16SEugene Zhulenev /// See LLVM coroutines documentation: https://llvm.org/docs/Coroutines.html 9025f80e16SEugene Zhulenev /// 9125f80e16SEugene Zhulenev /// - `entry` block sets up the coroutine. 9239957aa4SEugene Zhulenev /// - `set_error` block sets completion token and async values state to error. 9325f80e16SEugene Zhulenev /// - `cleanup` block cleans up the coroutine state. 9425f80e16SEugene Zhulenev /// - `suspend block after the @llvm.coro.end() defines what value will be 9525f80e16SEugene Zhulenev /// returned to the initial caller of a coroutine. Everything before the 9625f80e16SEugene Zhulenev /// @llvm.coro.end() will be executed at every suspension point. 9725f80e16SEugene Zhulenev /// 9825f80e16SEugene Zhulenev /// Coroutine structure (only the important bits): 9925f80e16SEugene Zhulenev /// 100*6ea22d46Sbakhtiyar /// func @some_fn(<function-arguments>) -> (!async.token, !async.value<T>) 10125f80e16SEugene Zhulenev /// { 10225f80e16SEugene Zhulenev /// ^entry(<function-arguments>): 10325f80e16SEugene Zhulenev /// %token = <async token> : !async.token // create async runtime token 10425f80e16SEugene Zhulenev /// %value = <async value> : !async.value<T> // create async value 10525f80e16SEugene Zhulenev /// %id = async.coro.id // create a coroutine id 10625f80e16SEugene Zhulenev /// %hdl = async.coro.begin %id // create a coroutine handle 107*6ea22d46Sbakhtiyar /// /* other ops of the preexisting entry block */ 108*6ea22d46Sbakhtiyar /// 109*6ea22d46Sbakhtiyar /// /* other preexisting blocks */ 11025f80e16SEugene Zhulenev /// 11139957aa4SEugene Zhulenev /// ^set_error: // this block created lazily only if needed (see code below) 11239957aa4SEugene Zhulenev /// async.runtime.set_error %token : !async.token 11339957aa4SEugene Zhulenev /// async.runtime.set_error %value : !async.value<T> 11439957aa4SEugene Zhulenev /// br ^cleanup 11539957aa4SEugene Zhulenev /// 11625f80e16SEugene Zhulenev /// ^cleanup: 11725f80e16SEugene Zhulenev /// async.coro.free %hdl // delete the coroutine state 11825f80e16SEugene Zhulenev /// br ^suspend 11925f80e16SEugene Zhulenev /// 12025f80e16SEugene Zhulenev /// ^suspend: 12125f80e16SEugene Zhulenev /// async.coro.end %hdl // marks the end of a coroutine 12225f80e16SEugene Zhulenev /// return %token, %value : !async.token, !async.value<T> 12325f80e16SEugene Zhulenev /// } 12425f80e16SEugene Zhulenev /// 12525f80e16SEugene Zhulenev static CoroMachinery setupCoroMachinery(FuncOp func) { 126*6ea22d46Sbakhtiyar assert(!func.getBlocks().empty() && "Function must have an entry block"); 12725f80e16SEugene Zhulenev 12825f80e16SEugene Zhulenev MLIRContext *ctx = func.getContext(); 129*6ea22d46Sbakhtiyar Block *entryBlock = &func.getBlocks().front(); 13025f80e16SEugene Zhulenev auto builder = ImplicitLocOpBuilder::atBlockBegin(func->getLoc(), entryBlock); 13125f80e16SEugene Zhulenev 13225f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 13325f80e16SEugene Zhulenev // Allocate async token/values that we will return from a ramp function. 13425f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 13525f80e16SEugene Zhulenev auto retToken = builder.create<RuntimeCreateOp>(TokenType::get(ctx)).result(); 13625f80e16SEugene Zhulenev 13725f80e16SEugene Zhulenev llvm::SmallVector<Value, 4> retValues; 13825f80e16SEugene Zhulenev for (auto resType : func.getCallableResults().drop_front()) 13925f80e16SEugene Zhulenev retValues.emplace_back(builder.create<RuntimeCreateOp>(resType).result()); 14025f80e16SEugene Zhulenev 14125f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 14225f80e16SEugene Zhulenev // Initialize coroutine: get coroutine id and coroutine handle. 14325f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 14425f80e16SEugene Zhulenev auto coroIdOp = builder.create<CoroIdOp>(CoroIdType::get(ctx)); 14525f80e16SEugene Zhulenev auto coroHdlOp = 14625f80e16SEugene Zhulenev builder.create<CoroBeginOp>(CoroHandleType::get(ctx), coroIdOp.id()); 14725f80e16SEugene Zhulenev 14825f80e16SEugene Zhulenev Block *cleanupBlock = func.addBlock(); 14925f80e16SEugene Zhulenev Block *suspendBlock = func.addBlock(); 15025f80e16SEugene Zhulenev 15125f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 15225f80e16SEugene Zhulenev // Coroutine cleanup block: deallocate coroutine frame, free the memory. 15325f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 15425f80e16SEugene Zhulenev builder.setInsertionPointToStart(cleanupBlock); 15525f80e16SEugene Zhulenev builder.create<CoroFreeOp>(coroIdOp.id(), coroHdlOp.handle()); 15625f80e16SEugene Zhulenev 15725f80e16SEugene Zhulenev // Branch into the suspend block. 15825f80e16SEugene Zhulenev builder.create<BranchOp>(suspendBlock); 15925f80e16SEugene Zhulenev 16025f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 16125f80e16SEugene Zhulenev // Coroutine suspend block: mark the end of a coroutine and return allocated 16225f80e16SEugene Zhulenev // async token. 16325f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 16425f80e16SEugene Zhulenev builder.setInsertionPointToStart(suspendBlock); 16525f80e16SEugene Zhulenev 16625f80e16SEugene Zhulenev // Mark the end of a coroutine: async.coro.end 16725f80e16SEugene Zhulenev builder.create<CoroEndOp>(coroHdlOp.handle()); 16825f80e16SEugene Zhulenev 16925f80e16SEugene Zhulenev // Return created `async.token` and `async.values` from the suspend block. 17025f80e16SEugene Zhulenev // This will be the return value of a coroutine ramp function. 17125f80e16SEugene Zhulenev SmallVector<Value, 4> ret{retToken}; 17225f80e16SEugene Zhulenev ret.insert(ret.end(), retValues.begin(), retValues.end()); 17325f80e16SEugene Zhulenev builder.create<ReturnOp>(ret); 17425f80e16SEugene Zhulenev 17525f80e16SEugene Zhulenev // `async.await` op lowering will create resume blocks for async 17625f80e16SEugene Zhulenev // continuations, and will conditionally branch to cleanup or suspend blocks. 17725f80e16SEugene Zhulenev 17825f80e16SEugene Zhulenev CoroMachinery machinery; 17939957aa4SEugene Zhulenev machinery.func = func; 18025f80e16SEugene Zhulenev machinery.asyncToken = retToken; 18125f80e16SEugene Zhulenev machinery.returnValues = retValues; 18225f80e16SEugene Zhulenev machinery.coroHandle = coroHdlOp.handle(); 18339957aa4SEugene Zhulenev machinery.setError = nullptr; // created lazily only if needed 18425f80e16SEugene Zhulenev machinery.cleanup = cleanupBlock; 18525f80e16SEugene Zhulenev machinery.suspend = suspendBlock; 18625f80e16SEugene Zhulenev return machinery; 18725f80e16SEugene Zhulenev } 18825f80e16SEugene Zhulenev 18939957aa4SEugene Zhulenev // Lazily creates `set_error` block only if it is required for lowering to the 19039957aa4SEugene Zhulenev // runtime operations (see for example lowering of assert operation). 19139957aa4SEugene Zhulenev static Block *setupSetErrorBlock(CoroMachinery &coro) { 19239957aa4SEugene Zhulenev if (coro.setError) 19339957aa4SEugene Zhulenev return coro.setError; 19439957aa4SEugene Zhulenev 19539957aa4SEugene Zhulenev coro.setError = coro.func.addBlock(); 19639957aa4SEugene Zhulenev coro.setError->moveBefore(coro.cleanup); 19739957aa4SEugene Zhulenev 19839957aa4SEugene Zhulenev auto builder = 19939957aa4SEugene Zhulenev ImplicitLocOpBuilder::atBlockBegin(coro.func->getLoc(), coro.setError); 20039957aa4SEugene Zhulenev 20139957aa4SEugene Zhulenev // Coroutine set_error block: set error on token and all returned values. 20239957aa4SEugene Zhulenev builder.create<RuntimeSetErrorOp>(coro.asyncToken); 20339957aa4SEugene Zhulenev for (Value retValue : coro.returnValues) 20439957aa4SEugene Zhulenev builder.create<RuntimeSetErrorOp>(retValue); 20539957aa4SEugene Zhulenev 20639957aa4SEugene Zhulenev // Branch into the cleanup block. 20739957aa4SEugene Zhulenev builder.create<BranchOp>(coro.cleanup); 20839957aa4SEugene Zhulenev 20939957aa4SEugene Zhulenev return coro.setError; 21039957aa4SEugene Zhulenev } 21139957aa4SEugene Zhulenev 21225f80e16SEugene Zhulenev /// Outline the body region attached to the `async.execute` op into a standalone 21325f80e16SEugene Zhulenev /// function. 21425f80e16SEugene Zhulenev /// 21525f80e16SEugene Zhulenev /// Note that this is not reversible transformation. 21625f80e16SEugene Zhulenev static std::pair<FuncOp, CoroMachinery> 21725f80e16SEugene Zhulenev outlineExecuteOp(SymbolTable &symbolTable, ExecuteOp execute) { 21825f80e16SEugene Zhulenev ModuleOp module = execute->getParentOfType<ModuleOp>(); 21925f80e16SEugene Zhulenev 22025f80e16SEugene Zhulenev MLIRContext *ctx = module.getContext(); 22125f80e16SEugene Zhulenev Location loc = execute.getLoc(); 22225f80e16SEugene Zhulenev 22325f80e16SEugene Zhulenev // Collect all outlined function inputs. 2244efb7754SRiver Riddle SetVector<mlir::Value> functionInputs(execute.dependencies().begin(), 22525f80e16SEugene Zhulenev execute.dependencies().end()); 22625f80e16SEugene Zhulenev functionInputs.insert(execute.operands().begin(), execute.operands().end()); 22725f80e16SEugene Zhulenev getUsedValuesDefinedAbove(execute.body(), functionInputs); 22825f80e16SEugene Zhulenev 22925f80e16SEugene Zhulenev // Collect types for the outlined function inputs and outputs. 23025f80e16SEugene Zhulenev auto typesRange = llvm::map_range( 23125f80e16SEugene Zhulenev functionInputs, [](Value value) { return value.getType(); }); 23225f80e16SEugene Zhulenev SmallVector<Type, 4> inputTypes(typesRange.begin(), typesRange.end()); 23325f80e16SEugene Zhulenev auto outputTypes = execute.getResultTypes(); 23425f80e16SEugene Zhulenev 23525f80e16SEugene Zhulenev auto funcType = FunctionType::get(ctx, inputTypes, outputTypes); 23625f80e16SEugene Zhulenev auto funcAttrs = ArrayRef<NamedAttribute>(); 23725f80e16SEugene Zhulenev 23825f80e16SEugene Zhulenev // TODO: Derive outlined function name from the parent FuncOp (support 23925f80e16SEugene Zhulenev // multiple nested async.execute operations). 24025f80e16SEugene Zhulenev FuncOp func = FuncOp::create(loc, kAsyncFnPrefix, funcType, funcAttrs); 241973ddb7dSMehdi Amini symbolTable.insert(func); 24225f80e16SEugene Zhulenev 24325f80e16SEugene Zhulenev SymbolTable::setSymbolVisibility(func, SymbolTable::Visibility::Private); 24425f80e16SEugene Zhulenev 24525f80e16SEugene Zhulenev // Prepare a function for coroutine lowering by adding entry/cleanup/suspend 24625f80e16SEugene Zhulenev // blocks, adding async.coro operations and setting up control flow. 247*6ea22d46Sbakhtiyar func.addEntryBlock(); 24825f80e16SEugene Zhulenev CoroMachinery coro = setupCoroMachinery(func); 24925f80e16SEugene Zhulenev 25025f80e16SEugene Zhulenev // Suspend async function at the end of an entry block, and resume it using 25125f80e16SEugene Zhulenev // Async resume operation (execution will be resumed in a thread managed by 25225f80e16SEugene Zhulenev // the async runtime). 25325f80e16SEugene Zhulenev Block *entryBlock = &func.getBlocks().front(); 254*6ea22d46Sbakhtiyar auto builder = ImplicitLocOpBuilder::atBlockEnd(loc, entryBlock); 25525f80e16SEugene Zhulenev 25625f80e16SEugene Zhulenev // Save the coroutine state: async.coro.save 25725f80e16SEugene Zhulenev auto coroSaveOp = 25825f80e16SEugene Zhulenev builder.create<CoroSaveOp>(CoroStateType::get(ctx), coro.coroHandle); 25925f80e16SEugene Zhulenev 26025f80e16SEugene Zhulenev // Pass coroutine to the runtime to be resumed on a runtime managed thread. 26125f80e16SEugene Zhulenev builder.create<RuntimeResumeOp>(coro.coroHandle); 262*6ea22d46Sbakhtiyar builder.create<BranchOp>(coro.cleanup); 26325f80e16SEugene Zhulenev 26425f80e16SEugene Zhulenev // Split the entry block before the terminator (branch to suspend block). 26525f80e16SEugene Zhulenev auto *terminatorOp = entryBlock->getTerminator(); 26625f80e16SEugene Zhulenev Block *suspended = terminatorOp->getBlock(); 26725f80e16SEugene Zhulenev Block *resume = suspended->splitBlock(terminatorOp); 26825f80e16SEugene Zhulenev 26925f80e16SEugene Zhulenev // Add async.coro.suspend as a suspended block terminator. 27025f80e16SEugene Zhulenev builder.setInsertionPointToEnd(suspended); 27125f80e16SEugene Zhulenev builder.create<CoroSuspendOp>(coroSaveOp.state(), coro.suspend, resume, 27225f80e16SEugene Zhulenev coro.cleanup); 27325f80e16SEugene Zhulenev 27425f80e16SEugene Zhulenev size_t numDependencies = execute.dependencies().size(); 27525f80e16SEugene Zhulenev size_t numOperands = execute.operands().size(); 27625f80e16SEugene Zhulenev 27725f80e16SEugene Zhulenev // Await on all dependencies before starting to execute the body region. 27825f80e16SEugene Zhulenev builder.setInsertionPointToStart(resume); 27925f80e16SEugene Zhulenev for (size_t i = 0; i < numDependencies; ++i) 28025f80e16SEugene Zhulenev builder.create<AwaitOp>(func.getArgument(i)); 28125f80e16SEugene Zhulenev 28225f80e16SEugene Zhulenev // Await on all async value operands and unwrap the payload. 28325f80e16SEugene Zhulenev SmallVector<Value, 4> unwrappedOperands(numOperands); 28425f80e16SEugene Zhulenev for (size_t i = 0; i < numOperands; ++i) { 28525f80e16SEugene Zhulenev Value operand = func.getArgument(numDependencies + i); 28625f80e16SEugene Zhulenev unwrappedOperands[i] = builder.create<AwaitOp>(loc, operand).result(); 28725f80e16SEugene Zhulenev } 28825f80e16SEugene Zhulenev 28925f80e16SEugene Zhulenev // Map from function inputs defined above the execute op to the function 29025f80e16SEugene Zhulenev // arguments. 29125f80e16SEugene Zhulenev BlockAndValueMapping valueMapping; 29225f80e16SEugene Zhulenev valueMapping.map(functionInputs, func.getArguments()); 29325f80e16SEugene Zhulenev valueMapping.map(execute.body().getArguments(), unwrappedOperands); 29425f80e16SEugene Zhulenev 29525f80e16SEugene Zhulenev // Clone all operations from the execute operation body into the outlined 29625f80e16SEugene Zhulenev // function body. 29725f80e16SEugene Zhulenev for (Operation &op : execute.body().getOps()) 29825f80e16SEugene Zhulenev builder.clone(op, valueMapping); 29925f80e16SEugene Zhulenev 30025f80e16SEugene Zhulenev // Replace the original `async.execute` with a call to outlined function. 30125f80e16SEugene Zhulenev ImplicitLocOpBuilder callBuilder(loc, execute); 30225f80e16SEugene Zhulenev auto callOutlinedFunc = callBuilder.create<CallOp>( 30325f80e16SEugene Zhulenev func.getName(), execute.getResultTypes(), functionInputs.getArrayRef()); 30425f80e16SEugene Zhulenev execute.replaceAllUsesWith(callOutlinedFunc.getResults()); 30525f80e16SEugene Zhulenev execute.erase(); 30625f80e16SEugene Zhulenev 30725f80e16SEugene Zhulenev return {func, coro}; 30825f80e16SEugene Zhulenev } 30925f80e16SEugene Zhulenev 31025f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 311d43b2360SEugene Zhulenev // Convert async.create_group operation to async.runtime.create_group 31225f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 31325f80e16SEugene Zhulenev 31425f80e16SEugene Zhulenev namespace { 31525f80e16SEugene Zhulenev class CreateGroupOpLowering : public OpConversionPattern<CreateGroupOp> { 31625f80e16SEugene Zhulenev public: 31725f80e16SEugene Zhulenev using OpConversionPattern::OpConversionPattern; 31825f80e16SEugene Zhulenev 31925f80e16SEugene Zhulenev LogicalResult 32025f80e16SEugene Zhulenev matchAndRewrite(CreateGroupOp op, ArrayRef<Value> operands, 32125f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 322d43b2360SEugene Zhulenev rewriter.replaceOpWithNewOp<RuntimeCreateGroupOp>( 323d43b2360SEugene Zhulenev op, GroupType::get(op->getContext()), operands); 32425f80e16SEugene Zhulenev return success(); 32525f80e16SEugene Zhulenev } 32625f80e16SEugene Zhulenev }; 32725f80e16SEugene Zhulenev } // namespace 32825f80e16SEugene Zhulenev 32925f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 33025f80e16SEugene Zhulenev // Convert async.add_to_group operation to async.runtime.add_to_group. 33125f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 33225f80e16SEugene Zhulenev 33325f80e16SEugene Zhulenev namespace { 33425f80e16SEugene Zhulenev class AddToGroupOpLowering : public OpConversionPattern<AddToGroupOp> { 33525f80e16SEugene Zhulenev public: 33625f80e16SEugene Zhulenev using OpConversionPattern::OpConversionPattern; 33725f80e16SEugene Zhulenev 33825f80e16SEugene Zhulenev LogicalResult 33925f80e16SEugene Zhulenev matchAndRewrite(AddToGroupOp op, ArrayRef<Value> operands, 34025f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 34125f80e16SEugene Zhulenev rewriter.replaceOpWithNewOp<RuntimeAddToGroupOp>( 34225f80e16SEugene Zhulenev op, rewriter.getIndexType(), operands); 34325f80e16SEugene Zhulenev return success(); 34425f80e16SEugene Zhulenev } 34525f80e16SEugene Zhulenev }; 34625f80e16SEugene Zhulenev } // namespace 34725f80e16SEugene Zhulenev 34825f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 34925f80e16SEugene Zhulenev // Convert async.await and async.await_all operations to the async.runtime.await 35025f80e16SEugene Zhulenev // or async.runtime.await_and_resume operations. 35125f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 35225f80e16SEugene Zhulenev 35325f80e16SEugene Zhulenev namespace { 35425f80e16SEugene Zhulenev template <typename AwaitType, typename AwaitableType> 35525f80e16SEugene Zhulenev class AwaitOpLoweringBase : public OpConversionPattern<AwaitType> { 35625f80e16SEugene Zhulenev using AwaitAdaptor = typename AwaitType::Adaptor; 35725f80e16SEugene Zhulenev 35825f80e16SEugene Zhulenev public: 35939957aa4SEugene Zhulenev AwaitOpLoweringBase(MLIRContext *ctx, 36039957aa4SEugene Zhulenev llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions) 36125f80e16SEugene Zhulenev : OpConversionPattern<AwaitType>(ctx), 36225f80e16SEugene Zhulenev outlinedFunctions(outlinedFunctions) {} 36325f80e16SEugene Zhulenev 36425f80e16SEugene Zhulenev LogicalResult 36525f80e16SEugene Zhulenev matchAndRewrite(AwaitType op, ArrayRef<Value> operands, 36625f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 36725f80e16SEugene Zhulenev // We can only await on one the `AwaitableType` (for `await` it can be 36825f80e16SEugene Zhulenev // a `token` or a `value`, for `await_all` it must be a `group`). 36925f80e16SEugene Zhulenev if (!op.operand().getType().template isa<AwaitableType>()) 37025f80e16SEugene Zhulenev return rewriter.notifyMatchFailure(op, "unsupported awaitable type"); 37125f80e16SEugene Zhulenev 37225f80e16SEugene Zhulenev // Check if await operation is inside the outlined coroutine function. 37325f80e16SEugene Zhulenev auto func = op->template getParentOfType<FuncOp>(); 37425f80e16SEugene Zhulenev auto outlined = outlinedFunctions.find(func); 37525f80e16SEugene Zhulenev const bool isInCoroutine = outlined != outlinedFunctions.end(); 37625f80e16SEugene Zhulenev 37725f80e16SEugene Zhulenev Location loc = op->getLoc(); 37825f80e16SEugene Zhulenev Value operand = AwaitAdaptor(operands).operand(); 37925f80e16SEugene Zhulenev 38025f80e16SEugene Zhulenev // Inside regular functions we use the blocking wait operation to wait for 38125f80e16SEugene Zhulenev // the async object (token, value or group) to become available. 38225f80e16SEugene Zhulenev if (!isInCoroutine) 38325f80e16SEugene Zhulenev rewriter.create<RuntimeAwaitOp>(loc, operand); 38425f80e16SEugene Zhulenev 38525f80e16SEugene Zhulenev // Inside the coroutine we convert await operation into coroutine suspension 38625f80e16SEugene Zhulenev // point, and resume execution asynchronously. 38725f80e16SEugene Zhulenev if (isInCoroutine) { 38839957aa4SEugene Zhulenev CoroMachinery &coro = outlined->getSecond(); 38925f80e16SEugene Zhulenev Block *suspended = op->getBlock(); 39025f80e16SEugene Zhulenev 39125f80e16SEugene Zhulenev ImplicitLocOpBuilder builder(loc, op, rewriter.getListener()); 39225f80e16SEugene Zhulenev MLIRContext *ctx = op->getContext(); 39325f80e16SEugene Zhulenev 39425f80e16SEugene Zhulenev // Save the coroutine state and resume on a runtime managed thread when 39525f80e16SEugene Zhulenev // the operand becomes available. 39625f80e16SEugene Zhulenev auto coroSaveOp = 39725f80e16SEugene Zhulenev builder.create<CoroSaveOp>(CoroStateType::get(ctx), coro.coroHandle); 39825f80e16SEugene Zhulenev builder.create<RuntimeAwaitAndResumeOp>(operand, coro.coroHandle); 39925f80e16SEugene Zhulenev 40025f80e16SEugene Zhulenev // Split the entry block before the await operation. 40125f80e16SEugene Zhulenev Block *resume = rewriter.splitBlock(suspended, Block::iterator(op)); 40225f80e16SEugene Zhulenev 40325f80e16SEugene Zhulenev // Add async.coro.suspend as a suspended block terminator. 40425f80e16SEugene Zhulenev builder.setInsertionPointToEnd(suspended); 40525f80e16SEugene Zhulenev builder.create<CoroSuspendOp>(coroSaveOp.state(), coro.suspend, resume, 40625f80e16SEugene Zhulenev coro.cleanup); 40725f80e16SEugene Zhulenev 40839957aa4SEugene Zhulenev // Split the resume block into error checking and continuation. 40939957aa4SEugene Zhulenev Block *continuation = rewriter.splitBlock(resume, Block::iterator(op)); 41039957aa4SEugene Zhulenev 41139957aa4SEugene Zhulenev // Check if the awaited value is in the error state. 41239957aa4SEugene Zhulenev builder.setInsertionPointToStart(resume); 413d8c84d2aSEugene Zhulenev auto isError = 414d8c84d2aSEugene Zhulenev builder.create<RuntimeIsErrorOp>(loc, rewriter.getI1Type(), operand); 41539957aa4SEugene Zhulenev builder.create<CondBranchOp>(isError, 41639957aa4SEugene Zhulenev /*trueDest=*/setupSetErrorBlock(coro), 41739957aa4SEugene Zhulenev /*trueArgs=*/ArrayRef<Value>(), 41839957aa4SEugene Zhulenev /*falseDest=*/continuation, 41939957aa4SEugene Zhulenev /*falseArgs=*/ArrayRef<Value>()); 42039957aa4SEugene Zhulenev 42139957aa4SEugene Zhulenev // Make sure that replacement value will be constructed in the 42239957aa4SEugene Zhulenev // continuation block. 42339957aa4SEugene Zhulenev rewriter.setInsertionPointToStart(continuation); 42439957aa4SEugene Zhulenev } 42525f80e16SEugene Zhulenev 42625f80e16SEugene Zhulenev // Erase or replace the await operation with the new value. 42725f80e16SEugene Zhulenev if (Value replaceWith = getReplacementValue(op, operand, rewriter)) 42825f80e16SEugene Zhulenev rewriter.replaceOp(op, replaceWith); 42925f80e16SEugene Zhulenev else 43025f80e16SEugene Zhulenev rewriter.eraseOp(op); 43125f80e16SEugene Zhulenev 43225f80e16SEugene Zhulenev return success(); 43325f80e16SEugene Zhulenev } 43425f80e16SEugene Zhulenev 43525f80e16SEugene Zhulenev virtual Value getReplacementValue(AwaitType op, Value operand, 43625f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const { 43725f80e16SEugene Zhulenev return Value(); 43825f80e16SEugene Zhulenev } 43925f80e16SEugene Zhulenev 44025f80e16SEugene Zhulenev private: 44139957aa4SEugene Zhulenev llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions; 44225f80e16SEugene Zhulenev }; 44325f80e16SEugene Zhulenev 44425f80e16SEugene Zhulenev /// Lowering for `async.await` with a token operand. 44525f80e16SEugene Zhulenev class AwaitTokenOpLowering : public AwaitOpLoweringBase<AwaitOp, TokenType> { 44625f80e16SEugene Zhulenev using Base = AwaitOpLoweringBase<AwaitOp, TokenType>; 44725f80e16SEugene Zhulenev 44825f80e16SEugene Zhulenev public: 44925f80e16SEugene Zhulenev using Base::Base; 45025f80e16SEugene Zhulenev }; 45125f80e16SEugene Zhulenev 45225f80e16SEugene Zhulenev /// Lowering for `async.await` with a value operand. 45325f80e16SEugene Zhulenev class AwaitValueOpLowering : public AwaitOpLoweringBase<AwaitOp, ValueType> { 45425f80e16SEugene Zhulenev using Base = AwaitOpLoweringBase<AwaitOp, ValueType>; 45525f80e16SEugene Zhulenev 45625f80e16SEugene Zhulenev public: 45725f80e16SEugene Zhulenev using Base::Base; 45825f80e16SEugene Zhulenev 45925f80e16SEugene Zhulenev Value 46025f80e16SEugene Zhulenev getReplacementValue(AwaitOp op, Value operand, 46125f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 46225f80e16SEugene Zhulenev // Load from the async value storage. 46325f80e16SEugene Zhulenev auto valueType = operand.getType().cast<ValueType>().getValueType(); 46425f80e16SEugene Zhulenev return rewriter.create<RuntimeLoadOp>(op->getLoc(), valueType, operand); 46525f80e16SEugene Zhulenev } 46625f80e16SEugene Zhulenev }; 46725f80e16SEugene Zhulenev 46825f80e16SEugene Zhulenev /// Lowering for `async.await_all` operation. 46925f80e16SEugene Zhulenev class AwaitAllOpLowering : public AwaitOpLoweringBase<AwaitAllOp, GroupType> { 47025f80e16SEugene Zhulenev using Base = AwaitOpLoweringBase<AwaitAllOp, GroupType>; 47125f80e16SEugene Zhulenev 47225f80e16SEugene Zhulenev public: 47325f80e16SEugene Zhulenev using Base::Base; 47425f80e16SEugene Zhulenev }; 47525f80e16SEugene Zhulenev 47625f80e16SEugene Zhulenev } // namespace 47725f80e16SEugene Zhulenev 47825f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 47925f80e16SEugene Zhulenev // Convert async.yield operation to async.runtime operations. 48025f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 48125f80e16SEugene Zhulenev 48225f80e16SEugene Zhulenev class YieldOpLowering : public OpConversionPattern<async::YieldOp> { 48325f80e16SEugene Zhulenev public: 48425f80e16SEugene Zhulenev YieldOpLowering( 48525f80e16SEugene Zhulenev MLIRContext *ctx, 48625f80e16SEugene Zhulenev const llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions) 48725f80e16SEugene Zhulenev : OpConversionPattern<async::YieldOp>(ctx), 48825f80e16SEugene Zhulenev outlinedFunctions(outlinedFunctions) {} 48925f80e16SEugene Zhulenev 49025f80e16SEugene Zhulenev LogicalResult 49125f80e16SEugene Zhulenev matchAndRewrite(async::YieldOp op, ArrayRef<Value> operands, 49225f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 49339957aa4SEugene Zhulenev // Check if yield operation is inside the async coroutine function. 49425f80e16SEugene Zhulenev auto func = op->template getParentOfType<FuncOp>(); 49525f80e16SEugene Zhulenev auto outlined = outlinedFunctions.find(func); 49625f80e16SEugene Zhulenev if (outlined == outlinedFunctions.end()) 49725f80e16SEugene Zhulenev return rewriter.notifyMatchFailure( 49839957aa4SEugene Zhulenev op, "operation is not inside the async coroutine function"); 49925f80e16SEugene Zhulenev 50025f80e16SEugene Zhulenev Location loc = op->getLoc(); 50125f80e16SEugene Zhulenev const CoroMachinery &coro = outlined->getSecond(); 50225f80e16SEugene Zhulenev 50325f80e16SEugene Zhulenev // Store yielded values into the async values storage and switch async 50425f80e16SEugene Zhulenev // values state to available. 50525f80e16SEugene Zhulenev for (auto tuple : llvm::zip(operands, coro.returnValues)) { 50625f80e16SEugene Zhulenev Value yieldValue = std::get<0>(tuple); 50725f80e16SEugene Zhulenev Value asyncValue = std::get<1>(tuple); 50825f80e16SEugene Zhulenev rewriter.create<RuntimeStoreOp>(loc, yieldValue, asyncValue); 50925f80e16SEugene Zhulenev rewriter.create<RuntimeSetAvailableOp>(loc, asyncValue); 51025f80e16SEugene Zhulenev } 51125f80e16SEugene Zhulenev 51225f80e16SEugene Zhulenev // Switch the coroutine completion token to available state. 51325f80e16SEugene Zhulenev rewriter.replaceOpWithNewOp<RuntimeSetAvailableOp>(op, coro.asyncToken); 51425f80e16SEugene Zhulenev 51525f80e16SEugene Zhulenev return success(); 51625f80e16SEugene Zhulenev } 51725f80e16SEugene Zhulenev 51825f80e16SEugene Zhulenev private: 51925f80e16SEugene Zhulenev const llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions; 52025f80e16SEugene Zhulenev }; 52125f80e16SEugene Zhulenev 52225f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 52339957aa4SEugene Zhulenev // Convert std.assert operation to cond_br into `set_error` block. 52439957aa4SEugene Zhulenev //===----------------------------------------------------------------------===// 52539957aa4SEugene Zhulenev 52639957aa4SEugene Zhulenev class AssertOpLowering : public OpConversionPattern<AssertOp> { 52739957aa4SEugene Zhulenev public: 52839957aa4SEugene Zhulenev AssertOpLowering(MLIRContext *ctx, 52939957aa4SEugene Zhulenev llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions) 53039957aa4SEugene Zhulenev : OpConversionPattern<AssertOp>(ctx), 53139957aa4SEugene Zhulenev outlinedFunctions(outlinedFunctions) {} 53239957aa4SEugene Zhulenev 53339957aa4SEugene Zhulenev LogicalResult 53439957aa4SEugene Zhulenev matchAndRewrite(AssertOp op, ArrayRef<Value> operands, 53539957aa4SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 53639957aa4SEugene Zhulenev // Check if assert operation is inside the async coroutine function. 53739957aa4SEugene Zhulenev auto func = op->template getParentOfType<FuncOp>(); 53839957aa4SEugene Zhulenev auto outlined = outlinedFunctions.find(func); 53939957aa4SEugene Zhulenev if (outlined == outlinedFunctions.end()) 54039957aa4SEugene Zhulenev return rewriter.notifyMatchFailure( 54139957aa4SEugene Zhulenev op, "operation is not inside the async coroutine function"); 54239957aa4SEugene Zhulenev 54339957aa4SEugene Zhulenev Location loc = op->getLoc(); 54439957aa4SEugene Zhulenev CoroMachinery &coro = outlined->getSecond(); 54539957aa4SEugene Zhulenev 54639957aa4SEugene Zhulenev Block *cont = rewriter.splitBlock(op->getBlock(), Block::iterator(op)); 54739957aa4SEugene Zhulenev rewriter.setInsertionPointToEnd(cont->getPrevNode()); 54839957aa4SEugene Zhulenev rewriter.create<CondBranchOp>(loc, AssertOpAdaptor(operands).arg(), 54939957aa4SEugene Zhulenev /*trueDest=*/cont, 55039957aa4SEugene Zhulenev /*trueArgs=*/ArrayRef<Value>(), 55139957aa4SEugene Zhulenev /*falseDest=*/setupSetErrorBlock(coro), 55239957aa4SEugene Zhulenev /*falseArgs=*/ArrayRef<Value>()); 55339957aa4SEugene Zhulenev rewriter.eraseOp(op); 55439957aa4SEugene Zhulenev 55539957aa4SEugene Zhulenev return success(); 55639957aa4SEugene Zhulenev } 55739957aa4SEugene Zhulenev 55839957aa4SEugene Zhulenev private: 55939957aa4SEugene Zhulenev llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions; 56039957aa4SEugene Zhulenev }; 56139957aa4SEugene Zhulenev 56239957aa4SEugene Zhulenev //===----------------------------------------------------------------------===// 56325f80e16SEugene Zhulenev 564*6ea22d46Sbakhtiyar /// Rewrite a func as a coroutine by: 565*6ea22d46Sbakhtiyar /// 1) Wrapping the results into `async.value`. 566*6ea22d46Sbakhtiyar /// 2) Prepending the results with `async.token`. 567*6ea22d46Sbakhtiyar /// 3) Setting up coroutine blocks. 568*6ea22d46Sbakhtiyar /// 4) Rewriting return ops as yield op and branch op into the suspend block. 569*6ea22d46Sbakhtiyar static CoroMachinery rewriteFuncAsCoroutine(FuncOp func) { 570*6ea22d46Sbakhtiyar auto *ctx = func->getContext(); 571*6ea22d46Sbakhtiyar auto loc = func.getLoc(); 572*6ea22d46Sbakhtiyar SmallVector<Type> resultTypes; 573*6ea22d46Sbakhtiyar resultTypes.reserve(func.getCallableResults().size()); 574*6ea22d46Sbakhtiyar llvm::transform(func.getCallableResults(), std::back_inserter(resultTypes), 575*6ea22d46Sbakhtiyar [](Type type) { return ValueType::get(type); }); 576*6ea22d46Sbakhtiyar func.setType(FunctionType::get(ctx, func.getType().getInputs(), resultTypes)); 577*6ea22d46Sbakhtiyar func.insertResult(0, TokenType::get(ctx), {}); 578*6ea22d46Sbakhtiyar CoroMachinery coro = setupCoroMachinery(func); 579*6ea22d46Sbakhtiyar for (Block &block : func.getBlocks()) { 580*6ea22d46Sbakhtiyar if (&block == coro.suspend) 581*6ea22d46Sbakhtiyar continue; 582*6ea22d46Sbakhtiyar 583*6ea22d46Sbakhtiyar Operation *terminator = block.getTerminator(); 584*6ea22d46Sbakhtiyar if (auto returnOp = dyn_cast<ReturnOp>(*terminator)) { 585*6ea22d46Sbakhtiyar ImplicitLocOpBuilder builder(loc, returnOp); 586*6ea22d46Sbakhtiyar builder.create<YieldOp>(returnOp.getOperands()); 587*6ea22d46Sbakhtiyar builder.create<BranchOp>(coro.cleanup); 588*6ea22d46Sbakhtiyar returnOp.erase(); 589*6ea22d46Sbakhtiyar } 590*6ea22d46Sbakhtiyar } 591*6ea22d46Sbakhtiyar return coro; 592*6ea22d46Sbakhtiyar } 593*6ea22d46Sbakhtiyar 594*6ea22d46Sbakhtiyar /// Rewrites a call into a function that has been rewritten as a coroutine. 595*6ea22d46Sbakhtiyar /// 596*6ea22d46Sbakhtiyar /// The invocation of this function is safe only when call ops are traversed in 597*6ea22d46Sbakhtiyar /// reverse order of how they appear in a single block. See `funcsToCoroutines`. 598*6ea22d46Sbakhtiyar static void rewriteCallsiteForCoroutine(CallOp oldCall, FuncOp func) { 599*6ea22d46Sbakhtiyar auto loc = func.getLoc(); 600*6ea22d46Sbakhtiyar ImplicitLocOpBuilder callBuilder(loc, oldCall); 601*6ea22d46Sbakhtiyar auto newCall = callBuilder.create<CallOp>( 602*6ea22d46Sbakhtiyar func.getName(), func.getCallableResults(), oldCall.getArgOperands()); 603*6ea22d46Sbakhtiyar 604*6ea22d46Sbakhtiyar // Await on the async token and all the value results and unwrap the latter. 605*6ea22d46Sbakhtiyar callBuilder.create<AwaitOp>(loc, newCall.getResults().front()); 606*6ea22d46Sbakhtiyar SmallVector<Value> unwrappedResults; 607*6ea22d46Sbakhtiyar unwrappedResults.reserve(newCall->getResults().size() - 1); 608*6ea22d46Sbakhtiyar for (Value result : newCall.getResults().drop_front()) 609*6ea22d46Sbakhtiyar unwrappedResults.push_back( 610*6ea22d46Sbakhtiyar callBuilder.create<AwaitOp>(loc, result).result()); 611*6ea22d46Sbakhtiyar // Careful, when result of a call is piped into another call this could lead 612*6ea22d46Sbakhtiyar // to a dangling pointer. 613*6ea22d46Sbakhtiyar oldCall.replaceAllUsesWith(unwrappedResults); 614*6ea22d46Sbakhtiyar oldCall.erase(); 615*6ea22d46Sbakhtiyar } 616*6ea22d46Sbakhtiyar 617*6ea22d46Sbakhtiyar static LogicalResult 618*6ea22d46Sbakhtiyar funcsToCoroutines(ModuleOp module, 619*6ea22d46Sbakhtiyar llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions) { 620*6ea22d46Sbakhtiyar // The following code supports the general case when 2 functions mutually 621*6ea22d46Sbakhtiyar // recurse into each other. Because of this and that we are relying on 622*6ea22d46Sbakhtiyar // SymbolUserMap to find pointers to calling FuncOps, we cannot simply erase 623*6ea22d46Sbakhtiyar // a FuncOp while inserting an equivalent coroutine, because that could lead 624*6ea22d46Sbakhtiyar // to dangling pointers. 625*6ea22d46Sbakhtiyar 626*6ea22d46Sbakhtiyar SmallVector<FuncOp> funcWorklist; 627*6ea22d46Sbakhtiyar 628*6ea22d46Sbakhtiyar // Careful, it's okay to add a func to the worklist multiple times if and only 629*6ea22d46Sbakhtiyar // if the loop processing the worklist will skip the functions that have 630*6ea22d46Sbakhtiyar // already been converted to coroutines. 631*6ea22d46Sbakhtiyar auto addToWorklist = [&outlinedFunctions, &funcWorklist](FuncOp func) { 632*6ea22d46Sbakhtiyar // N.B. To refactor this code into a separate pass the lookup in 633*6ea22d46Sbakhtiyar // outlinedFunctions is the most obvious obstacle. Looking at an arbitrary 634*6ea22d46Sbakhtiyar // func and recognizing if it has a coroutine structure is messy. Passing 635*6ea22d46Sbakhtiyar // this dict between the passes is ugly. 636*6ea22d46Sbakhtiyar if (outlinedFunctions.find(func) == outlinedFunctions.end()) { 637*6ea22d46Sbakhtiyar for (Operation &op : func.body().getOps()) { 638*6ea22d46Sbakhtiyar if (dyn_cast<AwaitOp>(op) || dyn_cast<AwaitAllOp>(op)) { 639*6ea22d46Sbakhtiyar funcWorklist.push_back(func); 640*6ea22d46Sbakhtiyar break; 641*6ea22d46Sbakhtiyar } 642*6ea22d46Sbakhtiyar } 643*6ea22d46Sbakhtiyar } 644*6ea22d46Sbakhtiyar }; 645*6ea22d46Sbakhtiyar 646*6ea22d46Sbakhtiyar // Traverse in post-order collecting for each func op the await ops it has. 647*6ea22d46Sbakhtiyar for (FuncOp func : module.getOps<FuncOp>()) 648*6ea22d46Sbakhtiyar addToWorklist(func); 649*6ea22d46Sbakhtiyar 650*6ea22d46Sbakhtiyar SymbolTableCollection symbolTable; 651*6ea22d46Sbakhtiyar SymbolUserMap symbolUserMap(symbolTable, module); 652*6ea22d46Sbakhtiyar 653*6ea22d46Sbakhtiyar // Rewrite funcs, while updating call sites and adding them to the worklist. 654*6ea22d46Sbakhtiyar while (!funcWorklist.empty()) { 655*6ea22d46Sbakhtiyar auto func = funcWorklist.pop_back_val(); 656*6ea22d46Sbakhtiyar auto insertion = outlinedFunctions.insert({func, CoroMachinery{}}); 657*6ea22d46Sbakhtiyar if (!insertion.second) 658*6ea22d46Sbakhtiyar // This function has already been processed because this is either 659*6ea22d46Sbakhtiyar // the corecursive case, or a caller with multiple calls to a newly 660*6ea22d46Sbakhtiyar // created corouting. Either way, skip updating the call sites. 661*6ea22d46Sbakhtiyar continue; 662*6ea22d46Sbakhtiyar insertion.first->second = rewriteFuncAsCoroutine(func); 663*6ea22d46Sbakhtiyar SmallVector<Operation *> users(symbolUserMap.getUsers(func).begin(), 664*6ea22d46Sbakhtiyar symbolUserMap.getUsers(func).end()); 665*6ea22d46Sbakhtiyar // If there are multiple calls from the same block they need to be traversed 666*6ea22d46Sbakhtiyar // in reverse order so that symbolUserMap references are not invalidated 667*6ea22d46Sbakhtiyar // when updating the users of the call op which is earlier in the block. 668*6ea22d46Sbakhtiyar llvm::sort(users, [](Operation *a, Operation *b) { 669*6ea22d46Sbakhtiyar Block *blockA = a->getBlock(); 670*6ea22d46Sbakhtiyar Block *blockB = b->getBlock(); 671*6ea22d46Sbakhtiyar // Impose arbitrary order on blocks so that there is a well-defined order. 672*6ea22d46Sbakhtiyar return blockA > blockB || (blockA == blockB && !a->isBeforeInBlock(b)); 673*6ea22d46Sbakhtiyar }); 674*6ea22d46Sbakhtiyar // Rewrite the callsites to await on results of the newly created coroutine. 675*6ea22d46Sbakhtiyar for (Operation *op : users) { 676*6ea22d46Sbakhtiyar if (CallOp call = dyn_cast<mlir::CallOp>(*op)) { 677*6ea22d46Sbakhtiyar FuncOp caller = call->getParentOfType<FuncOp>(); 678*6ea22d46Sbakhtiyar rewriteCallsiteForCoroutine(call, func); // Careful, erases the call op. 679*6ea22d46Sbakhtiyar addToWorklist(caller); 680*6ea22d46Sbakhtiyar } else { 681*6ea22d46Sbakhtiyar op->emitError("Unexpected reference to func referenced by symbol"); 682*6ea22d46Sbakhtiyar return failure(); 683*6ea22d46Sbakhtiyar } 684*6ea22d46Sbakhtiyar } 685*6ea22d46Sbakhtiyar } 686*6ea22d46Sbakhtiyar return success(); 687*6ea22d46Sbakhtiyar } 688*6ea22d46Sbakhtiyar 689*6ea22d46Sbakhtiyar //===----------------------------------------------------------------------===// 69025f80e16SEugene Zhulenev void AsyncToAsyncRuntimePass::runOnOperation() { 69125f80e16SEugene Zhulenev ModuleOp module = getOperation(); 69225f80e16SEugene Zhulenev SymbolTable symbolTable(module); 69325f80e16SEugene Zhulenev 69425f80e16SEugene Zhulenev // Outline all `async.execute` body regions into async functions (coroutines). 69525f80e16SEugene Zhulenev llvm::DenseMap<FuncOp, CoroMachinery> outlinedFunctions; 69625f80e16SEugene Zhulenev 69725f80e16SEugene Zhulenev module.walk([&](ExecuteOp execute) { 69825f80e16SEugene Zhulenev outlinedFunctions.insert(outlineExecuteOp(symbolTable, execute)); 69925f80e16SEugene Zhulenev }); 70025f80e16SEugene Zhulenev 70125f80e16SEugene Zhulenev LLVM_DEBUG({ 70225f80e16SEugene Zhulenev llvm::dbgs() << "Outlined " << outlinedFunctions.size() 70325f80e16SEugene Zhulenev << " functions built from async.execute operations\n"; 70425f80e16SEugene Zhulenev }); 70525f80e16SEugene Zhulenev 706de7a4e53SEugene Zhulenev // Returns true if operation is inside the coroutine. 707de7a4e53SEugene Zhulenev auto isInCoroutine = [&](Operation *op) -> bool { 708de7a4e53SEugene Zhulenev auto parentFunc = op->getParentOfType<FuncOp>(); 709de7a4e53SEugene Zhulenev return outlinedFunctions.find(parentFunc) != outlinedFunctions.end(); 710de7a4e53SEugene Zhulenev }; 711de7a4e53SEugene Zhulenev 712*6ea22d46Sbakhtiyar if (eliminateBlockingAwaitOps && 713*6ea22d46Sbakhtiyar failed(funcsToCoroutines(module, outlinedFunctions))) { 714*6ea22d46Sbakhtiyar signalPassFailure(); 715*6ea22d46Sbakhtiyar return; 716*6ea22d46Sbakhtiyar } 717*6ea22d46Sbakhtiyar 71825f80e16SEugene Zhulenev // Lower async operations to async.runtime operations. 71925f80e16SEugene Zhulenev MLIRContext *ctx = module->getContext(); 720dc4e913bSChris Lattner RewritePatternSet asyncPatterns(ctx); 72125f80e16SEugene Zhulenev 722de7a4e53SEugene Zhulenev // Conversion to async runtime augments original CFG with the coroutine CFG, 723de7a4e53SEugene Zhulenev // and we have to make sure that structured control flow operations with async 724de7a4e53SEugene Zhulenev // operations in nested regions will be converted to branch-based control flow 725de7a4e53SEugene Zhulenev // before we add the coroutine basic blocks. 726de7a4e53SEugene Zhulenev populateLoopToStdConversionPatterns(asyncPatterns); 727de7a4e53SEugene Zhulenev 72825f80e16SEugene Zhulenev // Async lowering does not use type converter because it must preserve all 72925f80e16SEugene Zhulenev // types for async.runtime operations. 730dc4e913bSChris Lattner asyncPatterns.add<CreateGroupOpLowering, AddToGroupOpLowering>(ctx); 731dc4e913bSChris Lattner asyncPatterns.add<AwaitTokenOpLowering, AwaitValueOpLowering, 73225f80e16SEugene Zhulenev AwaitAllOpLowering, YieldOpLowering>(ctx, 73325f80e16SEugene Zhulenev outlinedFunctions); 73425f80e16SEugene Zhulenev 73539957aa4SEugene Zhulenev // Lower assertions to conditional branches into error blocks. 73639957aa4SEugene Zhulenev asyncPatterns.add<AssertOpLowering>(ctx, outlinedFunctions); 73739957aa4SEugene Zhulenev 73825f80e16SEugene Zhulenev // All high level async operations must be lowered to the runtime operations. 73925f80e16SEugene Zhulenev ConversionTarget runtimeTarget(*ctx); 74025f80e16SEugene Zhulenev runtimeTarget.addLegalDialect<AsyncDialect>(); 74125f80e16SEugene Zhulenev runtimeTarget.addIllegalOp<CreateGroupOp, AddToGroupOp>(); 74225f80e16SEugene Zhulenev runtimeTarget.addIllegalOp<ExecuteOp, AwaitOp, AwaitAllOp, async::YieldOp>(); 74325f80e16SEugene Zhulenev 744de7a4e53SEugene Zhulenev // Decide if structured control flow has to be lowered to branch-based CFG. 745de7a4e53SEugene Zhulenev runtimeTarget.addDynamicallyLegalDialect<scf::SCFDialect>([&](Operation *op) { 746de7a4e53SEugene Zhulenev auto walkResult = op->walk([&](Operation *nested) { 747de7a4e53SEugene Zhulenev bool isAsync = isa<async::AsyncDialect>(nested->getDialect()); 748de7a4e53SEugene Zhulenev return isAsync && isInCoroutine(nested) ? WalkResult::interrupt() 749de7a4e53SEugene Zhulenev : WalkResult::advance(); 750de7a4e53SEugene Zhulenev }); 751de7a4e53SEugene Zhulenev return !walkResult.wasInterrupted(); 752de7a4e53SEugene Zhulenev }); 753de7a4e53SEugene Zhulenev runtimeTarget.addLegalOp<BranchOp, CondBranchOp>(); 754de7a4e53SEugene Zhulenev 7558f23fac4SEugene Zhulenev // Assertions must be converted to runtime errors inside async functions. 7568f23fac4SEugene Zhulenev runtimeTarget.addDynamicallyLegalOp<AssertOp>([&](AssertOp op) -> bool { 7578f23fac4SEugene Zhulenev auto func = op->getParentOfType<FuncOp>(); 7588f23fac4SEugene Zhulenev return outlinedFunctions.find(func) == outlinedFunctions.end(); 7598f23fac4SEugene Zhulenev }); 76039957aa4SEugene Zhulenev 761*6ea22d46Sbakhtiyar if (eliminateBlockingAwaitOps) 762*6ea22d46Sbakhtiyar runtimeTarget.addIllegalOp<RuntimeAwaitOp>(); 763*6ea22d46Sbakhtiyar 76425f80e16SEugene Zhulenev if (failed(applyPartialConversion(module, runtimeTarget, 76525f80e16SEugene Zhulenev std::move(asyncPatterns)))) { 76625f80e16SEugene Zhulenev signalPassFailure(); 76725f80e16SEugene Zhulenev return; 76825f80e16SEugene Zhulenev } 76925f80e16SEugene Zhulenev } 77025f80e16SEugene Zhulenev 77125f80e16SEugene Zhulenev std::unique_ptr<OperationPass<ModuleOp>> mlir::createAsyncToAsyncRuntimePass() { 77225f80e16SEugene Zhulenev return std::make_unique<AsyncToAsyncRuntimePass>(); 77325f80e16SEugene Zhulenev } 774