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