1*25f80e16SEugene Zhulenev //===- AsyncToAsyncRuntime.cpp - Lower from Async to Async Runtime --------===// 2*25f80e16SEugene Zhulenev // 3*25f80e16SEugene Zhulenev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*25f80e16SEugene Zhulenev // See https://llvm.org/LICENSE.txt for license information. 5*25f80e16SEugene Zhulenev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*25f80e16SEugene Zhulenev // 7*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 8*25f80e16SEugene Zhulenev // 9*25f80e16SEugene Zhulenev // This file implements lowering from high level async operations to async.coro 10*25f80e16SEugene Zhulenev // and async.runtime operations. 11*25f80e16SEugene Zhulenev // 12*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 13*25f80e16SEugene Zhulenev 14*25f80e16SEugene Zhulenev #include "PassDetail.h" 15*25f80e16SEugene Zhulenev #include "mlir/Dialect/Async/IR/Async.h" 16*25f80e16SEugene Zhulenev #include "mlir/Dialect/Async/Passes.h" 17*25f80e16SEugene Zhulenev #include "mlir/Dialect/StandardOps/IR/Ops.h" 18*25f80e16SEugene Zhulenev #include "mlir/IR/BlockAndValueMapping.h" 19*25f80e16SEugene Zhulenev #include "mlir/IR/ImplicitLocOpBuilder.h" 20*25f80e16SEugene Zhulenev #include "mlir/IR/PatternMatch.h" 21*25f80e16SEugene Zhulenev #include "mlir/Transforms/DialectConversion.h" 22*25f80e16SEugene Zhulenev #include "mlir/Transforms/RegionUtils.h" 23*25f80e16SEugene Zhulenev #include "llvm/ADT/SetVector.h" 24*25f80e16SEugene Zhulenev 25*25f80e16SEugene Zhulenev using namespace mlir; 26*25f80e16SEugene Zhulenev using namespace mlir::async; 27*25f80e16SEugene Zhulenev 28*25f80e16SEugene Zhulenev #define DEBUG_TYPE "async-to-async-runtime" 29*25f80e16SEugene Zhulenev // Prefix for functions outlined from `async.execute` op regions. 30*25f80e16SEugene Zhulenev static constexpr const char kAsyncFnPrefix[] = "async_execute_fn"; 31*25f80e16SEugene Zhulenev 32*25f80e16SEugene Zhulenev namespace { 33*25f80e16SEugene Zhulenev 34*25f80e16SEugene Zhulenev class AsyncToAsyncRuntimePass 35*25f80e16SEugene Zhulenev : public AsyncToAsyncRuntimeBase<AsyncToAsyncRuntimePass> { 36*25f80e16SEugene Zhulenev public: 37*25f80e16SEugene Zhulenev AsyncToAsyncRuntimePass() = default; 38*25f80e16SEugene Zhulenev void runOnOperation() override; 39*25f80e16SEugene Zhulenev }; 40*25f80e16SEugene Zhulenev 41*25f80e16SEugene Zhulenev } // namespace 42*25f80e16SEugene Zhulenev 43*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 44*25f80e16SEugene Zhulenev // async.execute op outlining to the coroutine functions. 45*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 46*25f80e16SEugene Zhulenev 47*25f80e16SEugene Zhulenev /// Function targeted for coroutine transformation has two additional blocks at 48*25f80e16SEugene Zhulenev /// the end: coroutine cleanup and coroutine suspension. 49*25f80e16SEugene Zhulenev /// 50*25f80e16SEugene Zhulenev /// async.await op lowering additionaly creates a resume block for each 51*25f80e16SEugene Zhulenev /// operation to enable non-blocking waiting via coroutine suspension. 52*25f80e16SEugene Zhulenev namespace { 53*25f80e16SEugene Zhulenev struct CoroMachinery { 54*25f80e16SEugene Zhulenev // Async execute region returns a completion token, and an async value for 55*25f80e16SEugene Zhulenev // each yielded value. 56*25f80e16SEugene Zhulenev // 57*25f80e16SEugene Zhulenev // %token, %result = async.execute -> !async.value<T> { 58*25f80e16SEugene Zhulenev // %0 = constant ... : T 59*25f80e16SEugene Zhulenev // async.yield %0 : T 60*25f80e16SEugene Zhulenev // } 61*25f80e16SEugene Zhulenev Value asyncToken; // token representing completion of the async region 62*25f80e16SEugene Zhulenev llvm::SmallVector<Value, 4> returnValues; // returned async values 63*25f80e16SEugene Zhulenev 64*25f80e16SEugene Zhulenev Value coroHandle; // coroutine handle (!async.coro.handle value) 65*25f80e16SEugene Zhulenev Block *cleanup; // coroutine cleanup block 66*25f80e16SEugene Zhulenev Block *suspend; // coroutine suspension block 67*25f80e16SEugene Zhulenev }; 68*25f80e16SEugene Zhulenev } // namespace 69*25f80e16SEugene Zhulenev 70*25f80e16SEugene Zhulenev /// Builds an coroutine template compatible with LLVM coroutines switched-resume 71*25f80e16SEugene Zhulenev /// lowering using `async.runtime.*` and `async.coro.*` operations. 72*25f80e16SEugene Zhulenev /// 73*25f80e16SEugene Zhulenev /// See LLVM coroutines documentation: https://llvm.org/docs/Coroutines.html 74*25f80e16SEugene Zhulenev /// 75*25f80e16SEugene Zhulenev /// - `entry` block sets up the coroutine. 76*25f80e16SEugene Zhulenev /// - `cleanup` block cleans up the coroutine state. 77*25f80e16SEugene Zhulenev /// - `suspend block after the @llvm.coro.end() defines what value will be 78*25f80e16SEugene Zhulenev /// returned to the initial caller of a coroutine. Everything before the 79*25f80e16SEugene Zhulenev /// @llvm.coro.end() will be executed at every suspension point. 80*25f80e16SEugene Zhulenev /// 81*25f80e16SEugene Zhulenev /// Coroutine structure (only the important bits): 82*25f80e16SEugene Zhulenev /// 83*25f80e16SEugene Zhulenev /// func @async_execute_fn(<function-arguments>) 84*25f80e16SEugene Zhulenev /// -> (!async.token, !async.value<T>) 85*25f80e16SEugene Zhulenev /// { 86*25f80e16SEugene Zhulenev /// ^entry(<function-arguments>): 87*25f80e16SEugene Zhulenev /// %token = <async token> : !async.token // create async runtime token 88*25f80e16SEugene Zhulenev /// %value = <async value> : !async.value<T> // create async value 89*25f80e16SEugene Zhulenev /// %id = async.coro.id // create a coroutine id 90*25f80e16SEugene Zhulenev /// %hdl = async.coro.begin %id // create a coroutine handle 91*25f80e16SEugene Zhulenev /// br ^cleanup 92*25f80e16SEugene Zhulenev /// 93*25f80e16SEugene Zhulenev /// ^cleanup: 94*25f80e16SEugene Zhulenev /// async.coro.free %hdl // delete the coroutine state 95*25f80e16SEugene Zhulenev /// br ^suspend 96*25f80e16SEugene Zhulenev /// 97*25f80e16SEugene Zhulenev /// ^suspend: 98*25f80e16SEugene Zhulenev /// async.coro.end %hdl // marks the end of a coroutine 99*25f80e16SEugene Zhulenev /// return %token, %value : !async.token, !async.value<T> 100*25f80e16SEugene Zhulenev /// } 101*25f80e16SEugene Zhulenev /// 102*25f80e16SEugene Zhulenev /// The actual code for the async.execute operation body region will be inserted 103*25f80e16SEugene Zhulenev /// before the entry block terminator. 104*25f80e16SEugene Zhulenev /// 105*25f80e16SEugene Zhulenev /// 106*25f80e16SEugene Zhulenev static CoroMachinery setupCoroMachinery(FuncOp func) { 107*25f80e16SEugene Zhulenev assert(func.getBody().empty() && "Function must have empty body"); 108*25f80e16SEugene Zhulenev 109*25f80e16SEugene Zhulenev MLIRContext *ctx = func.getContext(); 110*25f80e16SEugene Zhulenev Block *entryBlock = func.addEntryBlock(); 111*25f80e16SEugene Zhulenev 112*25f80e16SEugene Zhulenev auto builder = ImplicitLocOpBuilder::atBlockBegin(func->getLoc(), entryBlock); 113*25f80e16SEugene Zhulenev 114*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 115*25f80e16SEugene Zhulenev // Allocate async token/values that we will return from a ramp function. 116*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 117*25f80e16SEugene Zhulenev auto retToken = builder.create<RuntimeCreateOp>(TokenType::get(ctx)).result(); 118*25f80e16SEugene Zhulenev 119*25f80e16SEugene Zhulenev llvm::SmallVector<Value, 4> retValues; 120*25f80e16SEugene Zhulenev for (auto resType : func.getCallableResults().drop_front()) 121*25f80e16SEugene Zhulenev retValues.emplace_back(builder.create<RuntimeCreateOp>(resType).result()); 122*25f80e16SEugene Zhulenev 123*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 124*25f80e16SEugene Zhulenev // Initialize coroutine: get coroutine id and coroutine handle. 125*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 126*25f80e16SEugene Zhulenev auto coroIdOp = builder.create<CoroIdOp>(CoroIdType::get(ctx)); 127*25f80e16SEugene Zhulenev auto coroHdlOp = 128*25f80e16SEugene Zhulenev builder.create<CoroBeginOp>(CoroHandleType::get(ctx), coroIdOp.id()); 129*25f80e16SEugene Zhulenev 130*25f80e16SEugene Zhulenev Block *cleanupBlock = func.addBlock(); 131*25f80e16SEugene Zhulenev Block *suspendBlock = func.addBlock(); 132*25f80e16SEugene Zhulenev 133*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 134*25f80e16SEugene Zhulenev // Coroutine cleanup block: deallocate coroutine frame, free the memory. 135*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 136*25f80e16SEugene Zhulenev builder.setInsertionPointToStart(cleanupBlock); 137*25f80e16SEugene Zhulenev builder.create<CoroFreeOp>(coroIdOp.id(), coroHdlOp.handle()); 138*25f80e16SEugene Zhulenev 139*25f80e16SEugene Zhulenev // Branch into the suspend block. 140*25f80e16SEugene Zhulenev builder.create<BranchOp>(suspendBlock); 141*25f80e16SEugene Zhulenev 142*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 143*25f80e16SEugene Zhulenev // Coroutine suspend block: mark the end of a coroutine and return allocated 144*25f80e16SEugene Zhulenev // async token. 145*25f80e16SEugene Zhulenev // ------------------------------------------------------------------------ // 146*25f80e16SEugene Zhulenev builder.setInsertionPointToStart(suspendBlock); 147*25f80e16SEugene Zhulenev 148*25f80e16SEugene Zhulenev // Mark the end of a coroutine: async.coro.end 149*25f80e16SEugene Zhulenev builder.create<CoroEndOp>(coroHdlOp.handle()); 150*25f80e16SEugene Zhulenev 151*25f80e16SEugene Zhulenev // Return created `async.token` and `async.values` from the suspend block. 152*25f80e16SEugene Zhulenev // This will be the return value of a coroutine ramp function. 153*25f80e16SEugene Zhulenev SmallVector<Value, 4> ret{retToken}; 154*25f80e16SEugene Zhulenev ret.insert(ret.end(), retValues.begin(), retValues.end()); 155*25f80e16SEugene Zhulenev builder.create<ReturnOp>(ret); 156*25f80e16SEugene Zhulenev 157*25f80e16SEugene Zhulenev // Branch from the entry block to the cleanup block to create a valid CFG. 158*25f80e16SEugene Zhulenev builder.setInsertionPointToEnd(entryBlock); 159*25f80e16SEugene Zhulenev builder.create<BranchOp>(cleanupBlock); 160*25f80e16SEugene Zhulenev 161*25f80e16SEugene Zhulenev // `async.await` op lowering will create resume blocks for async 162*25f80e16SEugene Zhulenev // continuations, and will conditionally branch to cleanup or suspend blocks. 163*25f80e16SEugene Zhulenev 164*25f80e16SEugene Zhulenev CoroMachinery machinery; 165*25f80e16SEugene Zhulenev machinery.asyncToken = retToken; 166*25f80e16SEugene Zhulenev machinery.returnValues = retValues; 167*25f80e16SEugene Zhulenev machinery.coroHandle = coroHdlOp.handle(); 168*25f80e16SEugene Zhulenev machinery.cleanup = cleanupBlock; 169*25f80e16SEugene Zhulenev machinery.suspend = suspendBlock; 170*25f80e16SEugene Zhulenev return machinery; 171*25f80e16SEugene Zhulenev } 172*25f80e16SEugene Zhulenev 173*25f80e16SEugene Zhulenev /// Outline the body region attached to the `async.execute` op into a standalone 174*25f80e16SEugene Zhulenev /// function. 175*25f80e16SEugene Zhulenev /// 176*25f80e16SEugene Zhulenev /// Note that this is not reversible transformation. 177*25f80e16SEugene Zhulenev static std::pair<FuncOp, CoroMachinery> 178*25f80e16SEugene Zhulenev outlineExecuteOp(SymbolTable &symbolTable, ExecuteOp execute) { 179*25f80e16SEugene Zhulenev ModuleOp module = execute->getParentOfType<ModuleOp>(); 180*25f80e16SEugene Zhulenev 181*25f80e16SEugene Zhulenev MLIRContext *ctx = module.getContext(); 182*25f80e16SEugene Zhulenev Location loc = execute.getLoc(); 183*25f80e16SEugene Zhulenev 184*25f80e16SEugene Zhulenev // Collect all outlined function inputs. 185*25f80e16SEugene Zhulenev llvm::SetVector<mlir::Value> functionInputs(execute.dependencies().begin(), 186*25f80e16SEugene Zhulenev execute.dependencies().end()); 187*25f80e16SEugene Zhulenev functionInputs.insert(execute.operands().begin(), execute.operands().end()); 188*25f80e16SEugene Zhulenev getUsedValuesDefinedAbove(execute.body(), functionInputs); 189*25f80e16SEugene Zhulenev 190*25f80e16SEugene Zhulenev // Collect types for the outlined function inputs and outputs. 191*25f80e16SEugene Zhulenev auto typesRange = llvm::map_range( 192*25f80e16SEugene Zhulenev functionInputs, [](Value value) { return value.getType(); }); 193*25f80e16SEugene Zhulenev SmallVector<Type, 4> inputTypes(typesRange.begin(), typesRange.end()); 194*25f80e16SEugene Zhulenev auto outputTypes = execute.getResultTypes(); 195*25f80e16SEugene Zhulenev 196*25f80e16SEugene Zhulenev auto funcType = FunctionType::get(ctx, inputTypes, outputTypes); 197*25f80e16SEugene Zhulenev auto funcAttrs = ArrayRef<NamedAttribute>(); 198*25f80e16SEugene Zhulenev 199*25f80e16SEugene Zhulenev // TODO: Derive outlined function name from the parent FuncOp (support 200*25f80e16SEugene Zhulenev // multiple nested async.execute operations). 201*25f80e16SEugene Zhulenev FuncOp func = FuncOp::create(loc, kAsyncFnPrefix, funcType, funcAttrs); 202*25f80e16SEugene Zhulenev symbolTable.insert(func, Block::iterator(module.getBody()->getTerminator())); 203*25f80e16SEugene Zhulenev 204*25f80e16SEugene Zhulenev SymbolTable::setSymbolVisibility(func, SymbolTable::Visibility::Private); 205*25f80e16SEugene Zhulenev 206*25f80e16SEugene Zhulenev // Prepare a function for coroutine lowering by adding entry/cleanup/suspend 207*25f80e16SEugene Zhulenev // blocks, adding async.coro operations and setting up control flow. 208*25f80e16SEugene Zhulenev CoroMachinery coro = setupCoroMachinery(func); 209*25f80e16SEugene Zhulenev 210*25f80e16SEugene Zhulenev // Suspend async function at the end of an entry block, and resume it using 211*25f80e16SEugene Zhulenev // Async resume operation (execution will be resumed in a thread managed by 212*25f80e16SEugene Zhulenev // the async runtime). 213*25f80e16SEugene Zhulenev Block *entryBlock = &func.getBlocks().front(); 214*25f80e16SEugene Zhulenev auto builder = ImplicitLocOpBuilder::atBlockTerminator(loc, entryBlock); 215*25f80e16SEugene Zhulenev 216*25f80e16SEugene Zhulenev // Save the coroutine state: async.coro.save 217*25f80e16SEugene Zhulenev auto coroSaveOp = 218*25f80e16SEugene Zhulenev builder.create<CoroSaveOp>(CoroStateType::get(ctx), coro.coroHandle); 219*25f80e16SEugene Zhulenev 220*25f80e16SEugene Zhulenev // Pass coroutine to the runtime to be resumed on a runtime managed thread. 221*25f80e16SEugene Zhulenev builder.create<RuntimeResumeOp>(coro.coroHandle); 222*25f80e16SEugene Zhulenev 223*25f80e16SEugene Zhulenev // Split the entry block before the terminator (branch to suspend block). 224*25f80e16SEugene Zhulenev auto *terminatorOp = entryBlock->getTerminator(); 225*25f80e16SEugene Zhulenev Block *suspended = terminatorOp->getBlock(); 226*25f80e16SEugene Zhulenev Block *resume = suspended->splitBlock(terminatorOp); 227*25f80e16SEugene Zhulenev 228*25f80e16SEugene Zhulenev // Add async.coro.suspend as a suspended block terminator. 229*25f80e16SEugene Zhulenev builder.setInsertionPointToEnd(suspended); 230*25f80e16SEugene Zhulenev builder.create<CoroSuspendOp>(coroSaveOp.state(), coro.suspend, resume, 231*25f80e16SEugene Zhulenev coro.cleanup); 232*25f80e16SEugene Zhulenev 233*25f80e16SEugene Zhulenev size_t numDependencies = execute.dependencies().size(); 234*25f80e16SEugene Zhulenev size_t numOperands = execute.operands().size(); 235*25f80e16SEugene Zhulenev 236*25f80e16SEugene Zhulenev // Await on all dependencies before starting to execute the body region. 237*25f80e16SEugene Zhulenev builder.setInsertionPointToStart(resume); 238*25f80e16SEugene Zhulenev for (size_t i = 0; i < numDependencies; ++i) 239*25f80e16SEugene Zhulenev builder.create<AwaitOp>(func.getArgument(i)); 240*25f80e16SEugene Zhulenev 241*25f80e16SEugene Zhulenev // Await on all async value operands and unwrap the payload. 242*25f80e16SEugene Zhulenev SmallVector<Value, 4> unwrappedOperands(numOperands); 243*25f80e16SEugene Zhulenev for (size_t i = 0; i < numOperands; ++i) { 244*25f80e16SEugene Zhulenev Value operand = func.getArgument(numDependencies + i); 245*25f80e16SEugene Zhulenev unwrappedOperands[i] = builder.create<AwaitOp>(loc, operand).result(); 246*25f80e16SEugene Zhulenev } 247*25f80e16SEugene Zhulenev 248*25f80e16SEugene Zhulenev // Map from function inputs defined above the execute op to the function 249*25f80e16SEugene Zhulenev // arguments. 250*25f80e16SEugene Zhulenev BlockAndValueMapping valueMapping; 251*25f80e16SEugene Zhulenev valueMapping.map(functionInputs, func.getArguments()); 252*25f80e16SEugene Zhulenev valueMapping.map(execute.body().getArguments(), unwrappedOperands); 253*25f80e16SEugene Zhulenev 254*25f80e16SEugene Zhulenev // Clone all operations from the execute operation body into the outlined 255*25f80e16SEugene Zhulenev // function body. 256*25f80e16SEugene Zhulenev for (Operation &op : execute.body().getOps()) 257*25f80e16SEugene Zhulenev builder.clone(op, valueMapping); 258*25f80e16SEugene Zhulenev 259*25f80e16SEugene Zhulenev // Replace the original `async.execute` with a call to outlined function. 260*25f80e16SEugene Zhulenev ImplicitLocOpBuilder callBuilder(loc, execute); 261*25f80e16SEugene Zhulenev auto callOutlinedFunc = callBuilder.create<CallOp>( 262*25f80e16SEugene Zhulenev func.getName(), execute.getResultTypes(), functionInputs.getArrayRef()); 263*25f80e16SEugene Zhulenev execute.replaceAllUsesWith(callOutlinedFunc.getResults()); 264*25f80e16SEugene Zhulenev execute.erase(); 265*25f80e16SEugene Zhulenev 266*25f80e16SEugene Zhulenev return {func, coro}; 267*25f80e16SEugene Zhulenev } 268*25f80e16SEugene Zhulenev 269*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 270*25f80e16SEugene Zhulenev // Convert async.create_group operation to async.runtime.create 271*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 272*25f80e16SEugene Zhulenev 273*25f80e16SEugene Zhulenev namespace { 274*25f80e16SEugene Zhulenev class CreateGroupOpLowering : public OpConversionPattern<CreateGroupOp> { 275*25f80e16SEugene Zhulenev public: 276*25f80e16SEugene Zhulenev using OpConversionPattern::OpConversionPattern; 277*25f80e16SEugene Zhulenev 278*25f80e16SEugene Zhulenev LogicalResult 279*25f80e16SEugene Zhulenev matchAndRewrite(CreateGroupOp op, ArrayRef<Value> operands, 280*25f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 281*25f80e16SEugene Zhulenev rewriter.replaceOpWithNewOp<RuntimeCreateOp>( 282*25f80e16SEugene Zhulenev op, GroupType::get(op->getContext())); 283*25f80e16SEugene Zhulenev return success(); 284*25f80e16SEugene Zhulenev } 285*25f80e16SEugene Zhulenev }; 286*25f80e16SEugene Zhulenev } // namespace 287*25f80e16SEugene Zhulenev 288*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 289*25f80e16SEugene Zhulenev // Convert async.add_to_group operation to async.runtime.add_to_group. 290*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 291*25f80e16SEugene Zhulenev 292*25f80e16SEugene Zhulenev namespace { 293*25f80e16SEugene Zhulenev class AddToGroupOpLowering : public OpConversionPattern<AddToGroupOp> { 294*25f80e16SEugene Zhulenev public: 295*25f80e16SEugene Zhulenev using OpConversionPattern::OpConversionPattern; 296*25f80e16SEugene Zhulenev 297*25f80e16SEugene Zhulenev LogicalResult 298*25f80e16SEugene Zhulenev matchAndRewrite(AddToGroupOp op, ArrayRef<Value> operands, 299*25f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 300*25f80e16SEugene Zhulenev rewriter.replaceOpWithNewOp<RuntimeAddToGroupOp>( 301*25f80e16SEugene Zhulenev op, rewriter.getIndexType(), operands); 302*25f80e16SEugene Zhulenev return success(); 303*25f80e16SEugene Zhulenev } 304*25f80e16SEugene Zhulenev }; 305*25f80e16SEugene Zhulenev } // namespace 306*25f80e16SEugene Zhulenev 307*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 308*25f80e16SEugene Zhulenev // Convert async.await and async.await_all operations to the async.runtime.await 309*25f80e16SEugene Zhulenev // or async.runtime.await_and_resume operations. 310*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 311*25f80e16SEugene Zhulenev 312*25f80e16SEugene Zhulenev namespace { 313*25f80e16SEugene Zhulenev template <typename AwaitType, typename AwaitableType> 314*25f80e16SEugene Zhulenev class AwaitOpLoweringBase : public OpConversionPattern<AwaitType> { 315*25f80e16SEugene Zhulenev using AwaitAdaptor = typename AwaitType::Adaptor; 316*25f80e16SEugene Zhulenev 317*25f80e16SEugene Zhulenev public: 318*25f80e16SEugene Zhulenev AwaitOpLoweringBase( 319*25f80e16SEugene Zhulenev MLIRContext *ctx, 320*25f80e16SEugene Zhulenev const llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions) 321*25f80e16SEugene Zhulenev : OpConversionPattern<AwaitType>(ctx), 322*25f80e16SEugene Zhulenev outlinedFunctions(outlinedFunctions) {} 323*25f80e16SEugene Zhulenev 324*25f80e16SEugene Zhulenev LogicalResult 325*25f80e16SEugene Zhulenev matchAndRewrite(AwaitType op, ArrayRef<Value> operands, 326*25f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 327*25f80e16SEugene Zhulenev // We can only await on one the `AwaitableType` (for `await` it can be 328*25f80e16SEugene Zhulenev // a `token` or a `value`, for `await_all` it must be a `group`). 329*25f80e16SEugene Zhulenev if (!op.operand().getType().template isa<AwaitableType>()) 330*25f80e16SEugene Zhulenev return rewriter.notifyMatchFailure(op, "unsupported awaitable type"); 331*25f80e16SEugene Zhulenev 332*25f80e16SEugene Zhulenev // Check if await operation is inside the outlined coroutine function. 333*25f80e16SEugene Zhulenev auto func = op->template getParentOfType<FuncOp>(); 334*25f80e16SEugene Zhulenev auto outlined = outlinedFunctions.find(func); 335*25f80e16SEugene Zhulenev const bool isInCoroutine = outlined != outlinedFunctions.end(); 336*25f80e16SEugene Zhulenev 337*25f80e16SEugene Zhulenev Location loc = op->getLoc(); 338*25f80e16SEugene Zhulenev Value operand = AwaitAdaptor(operands).operand(); 339*25f80e16SEugene Zhulenev 340*25f80e16SEugene Zhulenev // Inside regular functions we use the blocking wait operation to wait for 341*25f80e16SEugene Zhulenev // the async object (token, value or group) to become available. 342*25f80e16SEugene Zhulenev if (!isInCoroutine) 343*25f80e16SEugene Zhulenev rewriter.create<RuntimeAwaitOp>(loc, operand); 344*25f80e16SEugene Zhulenev 345*25f80e16SEugene Zhulenev // Inside the coroutine we convert await operation into coroutine suspension 346*25f80e16SEugene Zhulenev // point, and resume execution asynchronously. 347*25f80e16SEugene Zhulenev if (isInCoroutine) { 348*25f80e16SEugene Zhulenev const CoroMachinery &coro = outlined->getSecond(); 349*25f80e16SEugene Zhulenev Block *suspended = op->getBlock(); 350*25f80e16SEugene Zhulenev 351*25f80e16SEugene Zhulenev ImplicitLocOpBuilder builder(loc, op, rewriter.getListener()); 352*25f80e16SEugene Zhulenev MLIRContext *ctx = op->getContext(); 353*25f80e16SEugene Zhulenev 354*25f80e16SEugene Zhulenev // Save the coroutine state and resume on a runtime managed thread when 355*25f80e16SEugene Zhulenev // the operand becomes available. 356*25f80e16SEugene Zhulenev auto coroSaveOp = 357*25f80e16SEugene Zhulenev builder.create<CoroSaveOp>(CoroStateType::get(ctx), coro.coroHandle); 358*25f80e16SEugene Zhulenev builder.create<RuntimeAwaitAndResumeOp>(operand, coro.coroHandle); 359*25f80e16SEugene Zhulenev 360*25f80e16SEugene Zhulenev // Split the entry block before the await operation. 361*25f80e16SEugene Zhulenev Block *resume = rewriter.splitBlock(suspended, Block::iterator(op)); 362*25f80e16SEugene Zhulenev 363*25f80e16SEugene Zhulenev // Add async.coro.suspend as a suspended block terminator. 364*25f80e16SEugene Zhulenev builder.setInsertionPointToEnd(suspended); 365*25f80e16SEugene Zhulenev builder.create<CoroSuspendOp>(coroSaveOp.state(), coro.suspend, resume, 366*25f80e16SEugene Zhulenev coro.cleanup); 367*25f80e16SEugene Zhulenev 368*25f80e16SEugene Zhulenev // Make sure that replacement value will be constructed in resume block. 369*25f80e16SEugene Zhulenev rewriter.setInsertionPointToStart(resume); 370*25f80e16SEugene Zhulenev } 371*25f80e16SEugene Zhulenev 372*25f80e16SEugene Zhulenev // Erase or replace the await operation with the new value. 373*25f80e16SEugene Zhulenev if (Value replaceWith = getReplacementValue(op, operand, rewriter)) 374*25f80e16SEugene Zhulenev rewriter.replaceOp(op, replaceWith); 375*25f80e16SEugene Zhulenev else 376*25f80e16SEugene Zhulenev rewriter.eraseOp(op); 377*25f80e16SEugene Zhulenev 378*25f80e16SEugene Zhulenev return success(); 379*25f80e16SEugene Zhulenev } 380*25f80e16SEugene Zhulenev 381*25f80e16SEugene Zhulenev virtual Value getReplacementValue(AwaitType op, Value operand, 382*25f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const { 383*25f80e16SEugene Zhulenev return Value(); 384*25f80e16SEugene Zhulenev } 385*25f80e16SEugene Zhulenev 386*25f80e16SEugene Zhulenev private: 387*25f80e16SEugene Zhulenev const llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions; 388*25f80e16SEugene Zhulenev }; 389*25f80e16SEugene Zhulenev 390*25f80e16SEugene Zhulenev /// Lowering for `async.await` with a token operand. 391*25f80e16SEugene Zhulenev class AwaitTokenOpLowering : public AwaitOpLoweringBase<AwaitOp, TokenType> { 392*25f80e16SEugene Zhulenev using Base = AwaitOpLoweringBase<AwaitOp, TokenType>; 393*25f80e16SEugene Zhulenev 394*25f80e16SEugene Zhulenev public: 395*25f80e16SEugene Zhulenev using Base::Base; 396*25f80e16SEugene Zhulenev }; 397*25f80e16SEugene Zhulenev 398*25f80e16SEugene Zhulenev /// Lowering for `async.await` with a value operand. 399*25f80e16SEugene Zhulenev class AwaitValueOpLowering : public AwaitOpLoweringBase<AwaitOp, ValueType> { 400*25f80e16SEugene Zhulenev using Base = AwaitOpLoweringBase<AwaitOp, ValueType>; 401*25f80e16SEugene Zhulenev 402*25f80e16SEugene Zhulenev public: 403*25f80e16SEugene Zhulenev using Base::Base; 404*25f80e16SEugene Zhulenev 405*25f80e16SEugene Zhulenev Value 406*25f80e16SEugene Zhulenev getReplacementValue(AwaitOp op, Value operand, 407*25f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 408*25f80e16SEugene Zhulenev // Load from the async value storage. 409*25f80e16SEugene Zhulenev auto valueType = operand.getType().cast<ValueType>().getValueType(); 410*25f80e16SEugene Zhulenev return rewriter.create<RuntimeLoadOp>(op->getLoc(), valueType, operand); 411*25f80e16SEugene Zhulenev } 412*25f80e16SEugene Zhulenev }; 413*25f80e16SEugene Zhulenev 414*25f80e16SEugene Zhulenev /// Lowering for `async.await_all` operation. 415*25f80e16SEugene Zhulenev class AwaitAllOpLowering : public AwaitOpLoweringBase<AwaitAllOp, GroupType> { 416*25f80e16SEugene Zhulenev using Base = AwaitOpLoweringBase<AwaitAllOp, GroupType>; 417*25f80e16SEugene Zhulenev 418*25f80e16SEugene Zhulenev public: 419*25f80e16SEugene Zhulenev using Base::Base; 420*25f80e16SEugene Zhulenev }; 421*25f80e16SEugene Zhulenev 422*25f80e16SEugene Zhulenev } // namespace 423*25f80e16SEugene Zhulenev 424*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 425*25f80e16SEugene Zhulenev // Convert async.yield operation to async.runtime operations. 426*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 427*25f80e16SEugene Zhulenev 428*25f80e16SEugene Zhulenev class YieldOpLowering : public OpConversionPattern<async::YieldOp> { 429*25f80e16SEugene Zhulenev public: 430*25f80e16SEugene Zhulenev YieldOpLowering( 431*25f80e16SEugene Zhulenev MLIRContext *ctx, 432*25f80e16SEugene Zhulenev const llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions) 433*25f80e16SEugene Zhulenev : OpConversionPattern<async::YieldOp>(ctx), 434*25f80e16SEugene Zhulenev outlinedFunctions(outlinedFunctions) {} 435*25f80e16SEugene Zhulenev 436*25f80e16SEugene Zhulenev LogicalResult 437*25f80e16SEugene Zhulenev matchAndRewrite(async::YieldOp op, ArrayRef<Value> operands, 438*25f80e16SEugene Zhulenev ConversionPatternRewriter &rewriter) const override { 439*25f80e16SEugene Zhulenev // Check if yield operation is inside the outlined coroutine function. 440*25f80e16SEugene Zhulenev auto func = op->template getParentOfType<FuncOp>(); 441*25f80e16SEugene Zhulenev auto outlined = outlinedFunctions.find(func); 442*25f80e16SEugene Zhulenev if (outlined == outlinedFunctions.end()) 443*25f80e16SEugene Zhulenev return rewriter.notifyMatchFailure( 444*25f80e16SEugene Zhulenev op, "operation is not inside the outlined async.execute function"); 445*25f80e16SEugene Zhulenev 446*25f80e16SEugene Zhulenev Location loc = op->getLoc(); 447*25f80e16SEugene Zhulenev const CoroMachinery &coro = outlined->getSecond(); 448*25f80e16SEugene Zhulenev 449*25f80e16SEugene Zhulenev // Store yielded values into the async values storage and switch async 450*25f80e16SEugene Zhulenev // values state to available. 451*25f80e16SEugene Zhulenev for (auto tuple : llvm::zip(operands, coro.returnValues)) { 452*25f80e16SEugene Zhulenev Value yieldValue = std::get<0>(tuple); 453*25f80e16SEugene Zhulenev Value asyncValue = std::get<1>(tuple); 454*25f80e16SEugene Zhulenev rewriter.create<RuntimeStoreOp>(loc, yieldValue, asyncValue); 455*25f80e16SEugene Zhulenev rewriter.create<RuntimeSetAvailableOp>(loc, asyncValue); 456*25f80e16SEugene Zhulenev } 457*25f80e16SEugene Zhulenev 458*25f80e16SEugene Zhulenev // Switch the coroutine completion token to available state. 459*25f80e16SEugene Zhulenev rewriter.replaceOpWithNewOp<RuntimeSetAvailableOp>(op, coro.asyncToken); 460*25f80e16SEugene Zhulenev 461*25f80e16SEugene Zhulenev return success(); 462*25f80e16SEugene Zhulenev } 463*25f80e16SEugene Zhulenev 464*25f80e16SEugene Zhulenev private: 465*25f80e16SEugene Zhulenev const llvm::DenseMap<FuncOp, CoroMachinery> &outlinedFunctions; 466*25f80e16SEugene Zhulenev }; 467*25f80e16SEugene Zhulenev 468*25f80e16SEugene Zhulenev //===----------------------------------------------------------------------===// 469*25f80e16SEugene Zhulenev 470*25f80e16SEugene Zhulenev void AsyncToAsyncRuntimePass::runOnOperation() { 471*25f80e16SEugene Zhulenev ModuleOp module = getOperation(); 472*25f80e16SEugene Zhulenev SymbolTable symbolTable(module); 473*25f80e16SEugene Zhulenev 474*25f80e16SEugene Zhulenev // Outline all `async.execute` body regions into async functions (coroutines). 475*25f80e16SEugene Zhulenev llvm::DenseMap<FuncOp, CoroMachinery> outlinedFunctions; 476*25f80e16SEugene Zhulenev 477*25f80e16SEugene Zhulenev module.walk([&](ExecuteOp execute) { 478*25f80e16SEugene Zhulenev outlinedFunctions.insert(outlineExecuteOp(symbolTable, execute)); 479*25f80e16SEugene Zhulenev }); 480*25f80e16SEugene Zhulenev 481*25f80e16SEugene Zhulenev LLVM_DEBUG({ 482*25f80e16SEugene Zhulenev llvm::dbgs() << "Outlined " << outlinedFunctions.size() 483*25f80e16SEugene Zhulenev << " functions built from async.execute operations\n"; 484*25f80e16SEugene Zhulenev }); 485*25f80e16SEugene Zhulenev 486*25f80e16SEugene Zhulenev // Lower async operations to async.runtime operations. 487*25f80e16SEugene Zhulenev MLIRContext *ctx = module->getContext(); 488*25f80e16SEugene Zhulenev OwningRewritePatternList asyncPatterns; 489*25f80e16SEugene Zhulenev 490*25f80e16SEugene Zhulenev // Async lowering does not use type converter because it must preserve all 491*25f80e16SEugene Zhulenev // types for async.runtime operations. 492*25f80e16SEugene Zhulenev asyncPatterns.insert<CreateGroupOpLowering, AddToGroupOpLowering>(ctx); 493*25f80e16SEugene Zhulenev asyncPatterns.insert<AwaitTokenOpLowering, AwaitValueOpLowering, 494*25f80e16SEugene Zhulenev AwaitAllOpLowering, YieldOpLowering>(ctx, 495*25f80e16SEugene Zhulenev outlinedFunctions); 496*25f80e16SEugene Zhulenev 497*25f80e16SEugene Zhulenev // All high level async operations must be lowered to the runtime operations. 498*25f80e16SEugene Zhulenev ConversionTarget runtimeTarget(*ctx); 499*25f80e16SEugene Zhulenev runtimeTarget.addLegalDialect<AsyncDialect>(); 500*25f80e16SEugene Zhulenev runtimeTarget.addIllegalOp<CreateGroupOp, AddToGroupOp>(); 501*25f80e16SEugene Zhulenev runtimeTarget.addIllegalOp<ExecuteOp, AwaitOp, AwaitAllOp, async::YieldOp>(); 502*25f80e16SEugene Zhulenev 503*25f80e16SEugene Zhulenev if (failed(applyPartialConversion(module, runtimeTarget, 504*25f80e16SEugene Zhulenev std::move(asyncPatterns)))) { 505*25f80e16SEugene Zhulenev signalPassFailure(); 506*25f80e16SEugene Zhulenev return; 507*25f80e16SEugene Zhulenev } 508*25f80e16SEugene Zhulenev } 509*25f80e16SEugene Zhulenev 510*25f80e16SEugene Zhulenev std::unique_ptr<OperationPass<ModuleOp>> mlir::createAsyncToAsyncRuntimePass() { 511*25f80e16SEugene Zhulenev return std::make_unique<AsyncToAsyncRuntimePass>(); 512*25f80e16SEugene Zhulenev } 513