1//===- AsyncOps.td - Async operations definition -----------*- tablegen -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This is the operation definition file for Async dialect operations.
10//
11//===----------------------------------------------------------------------===//
12
13#ifndef ASYNC_OPS
14#define ASYNC_OPS
15
16include "mlir/Dialect/Async/IR/AsyncDialect.td"
17include "mlir/Dialect/Async/IR/AsyncTypes.td"
18include "mlir/Interfaces/ControlFlowInterfaces.td"
19include "mlir/Interfaces/InferTypeOpInterface.td"
20include "mlir/Interfaces/SideEffectInterfaces.td"
21
22//===----------------------------------------------------------------------===//
23// Async op definitions
24//===----------------------------------------------------------------------===//
25
26// Base class for the operation in this dialect
27class Async_Op<string mnemonic, list<Trait> traits = []> :
28    Op<AsyncDialect, mnemonic, traits>;
29
30def Async_ExecuteOp :
31  Async_Op<"execute", [SingleBlockImplicitTerminator<"YieldOp">,
32                       DeclareOpInterfaceMethods<RegionBranchOpInterface,
33                                                 ["getSuccessorEntryOperands",
34                                                  "areTypesCompatible"]>,
35                       AttrSizedOperandSegments,
36                       AutomaticAllocationScope]> {
37  let summary = "Asynchronous execute operation";
38  let description = [{
39    The `body` region attached to the `async.execute` operation semantically
40    can be executed concurrently with the successor operation. In the followup
41    example "compute0" can be executed concurrently with "compute1".
42
43    The actual concurrency semantics depends on the dialect lowering to the
44    executable format. Fully sequential execution ("compute0" completes before
45    "compute1" starts) is a completely legal execution.
46
47    Because concurrent execution is not guaranteed, it is illegal to create an
48    implicit dependency from "compute1" to "compute0" (e.g. via shared global
49    state). All dependencies must be made explicit with async execute arguments
50    (`async.token` or `async.value`).
51
52   `async.execute` operation takes `async.token` dependencies and `async.value`
53    operands separately, and starts execution of the attached body region only
54    when all tokens and values become ready.
55
56    Example:
57
58    ```mlir
59    %dependency = ... : !async.token
60    %value = ... : !async.value<f32>
61
62    %token, %results =
63      async.execute [%dependency](%value as %unwrapped: !async.value<f32>)
64                 -> !async.value<!some.type>
65      {
66        %0 = "compute0"(%unwrapped): (f32) -> !some.type
67        async.yield %0 : !some.type
68      }
69
70    %1 = "compute1"(...) : !some.type
71    ```
72
73    In the example above asynchronous execution starts only after dependency
74    token and value argument become ready. Unwrapped value passed to the
75    attached body region as an %unwrapped value of f32 type.
76  }];
77
78  let arguments = (ins Variadic<Async_TokenType>:$dependencies,
79                       Variadic<Async_AnyValueOrTokenType>:$operands);
80
81  let results = (outs Async_TokenType:$token,
82                      Variadic<Async_ValueType>:$results);
83  let regions = (region SizedRegion<1>:$body);
84
85  let hasCustomAssemblyFormat = 1;
86  let skipDefaultBuilders = 1;
87  let hasRegionVerifier = 1;
88  let builders = [
89    OpBuilder<(ins "TypeRange":$resultTypes, "ValueRange":$dependencies,
90      "ValueRange":$operands,
91      CArg<"function_ref<void(OpBuilder &, Location, ValueRange)>",
92           "nullptr">:$bodyBuilder)>,
93  ];
94
95  let extraClassDeclaration = [{
96    using BodyBuilderFn =
97        function_ref<void(OpBuilder &, Location, ValueRange)>;
98
99  }];
100}
101
102def Async_YieldOp :
103    Async_Op<"yield", [
104      HasParent<"ExecuteOp">, NoSideEffect, Terminator,
105      DeclareOpInterfaceMethods<RegionBranchTerminatorOpInterface>]> {
106  let summary = "terminator for Async execute operation";
107  let description = [{
108    The `async.yield` is a special terminator operation for the block inside
109    `async.execute` operation.
110  }];
111
112  let arguments = (ins Variadic<AnyType>:$operands);
113  let assemblyFormat = "($operands^ `:` type($operands))? attr-dict";
114  let hasVerifier = 1;
115}
116
117def Async_AwaitOp : Async_Op<"await"> {
118  let summary = "waits for the argument to become ready";
119  let description = [{
120    The `async.await` operation waits until the argument becomes ready, and for
121    the `async.value` arguments it unwraps the underlying value
122
123    Example:
124
125    ```mlir
126    %0 = ... : !async.token
127    async.await %0 : !async.token
128
129    %1 = ... : !async.value<f32>
130    %2 = async.await %1 : !async.value<f32>
131    ```
132  }];
133
134  let arguments = (ins Async_AnyValueOrTokenType:$operand);
135  let results = (outs Optional<AnyType>:$result);
136
137  let skipDefaultBuilders = 1;
138  let hasVerifier = 1;
139
140  let builders = [
141    OpBuilder<(ins "Value":$operand,
142      CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>,
143  ];
144
145  let extraClassDeclaration = [{
146    Optional<Type> getResultType() {
147      if (getResultTypes().empty()) return None;
148      return getResultTypes()[0];
149    }
150  }];
151
152  let assemblyFormat = [{
153    $operand `:` custom<AwaitResultType>(
154      type($operand), type($result)
155    ) attr-dict
156  }];
157}
158
159def Async_CreateGroupOp : Async_Op<"create_group", [NoSideEffect]> {
160  let summary = "creates an empty async group";
161  let description = [{
162    The `async.create_group` allocates an empty async group. Async tokens or
163    values can be added to this group later. The size of the group must be
164    specified at construction time, and `await_all` operation will first
165    wait until the number of added tokens or values reaches the group size.
166
167    Example:
168
169    ```mlir
170    %size = ... : index
171    %group = async.create_group %size : !async.group
172    ...
173    async.await_all %group
174    ```
175  }];
176
177  let arguments = (ins Index:$size);
178  let results = (outs Async_GroupType:$result);
179
180  let hasCanonicalizeMethod = 1;
181
182  let assemblyFormat = "$size `:` type($result) attr-dict";
183}
184
185def Async_AddToGroupOp : Async_Op<"add_to_group", []> {
186  let summary = "adds and async token or value to the group";
187  let description = [{
188    The `async.add_to_group` adds an async token or value to the async group.
189    Returns the rank of the added element in the group. This rank is fixed
190    for the group lifetime.
191
192    Example:
193
194    ```mlir
195    %0 = async.create_group %size : !async.group
196    %1 = ... : !async.token
197    %2 = async.add_to_group %1, %0 : !async.token
198    ```
199  }];
200
201  let arguments = (ins Async_AnyValueOrTokenType:$operand,
202                       Async_GroupType:$group);
203  let results = (outs Index:$rank);
204
205  let assemblyFormat = "$operand `,` $group `:` type($operand) attr-dict";
206}
207
208def Async_AwaitAllOp : Async_Op<"await_all", []> {
209  let summary = "waits for the all async tokens or values in the group to "
210                "become ready";
211  let description = [{
212    The `async.await_all` operation waits until all the tokens or values in the
213    group become ready.
214
215    Example:
216
217    ```mlir
218    %0 = async.create_group %size : !async.group
219
220    %1 = ... : !async.token
221    %2 = async.add_to_group %1, %0 : !async.token
222
223    %3 = ... : !async.token
224    %4 = async.add_to_group %2, %0 : !async.token
225
226    async.await_all %0
227    ```
228  }];
229
230  let arguments = (ins Async_GroupType:$operand);
231  let results = (outs);
232
233  let assemblyFormat = "$operand attr-dict";
234}
235
236//===----------------------------------------------------------------------===//
237// Async Dialect LLVM Coroutines Operations.
238//===----------------------------------------------------------------------===//
239
240// Async to LLVM dialect lowering converts async tasks (regions inside async
241// execute operations) to LLVM coroutines [1], and relies on switched-resume
242// lowering [2] to produce an asynchronous executable.
243//
244// We define LLVM coro intrinsics in the async dialect to facilitate progressive
245// lowering with verifiable and type-safe IR during the multi-step lowering
246// pipeline. First we convert from high level async operations (e.g. execute) to
247// the explicit calls to coro intrinsics and runtime API, and then finalize
248// lowering to LLVM with a simple dialect conversion pass.
249//
250// [1] https://llvm.org/docs/Coroutines.html
251// [2] https://llvm.org/docs/Coroutines.html#switched-resume-lowering
252
253def Async_CoroIdOp : Async_Op<"coro.id"> {
254  let summary = "returns a switched-resume coroutine identifier";
255  let description = [{
256    The `async.coro.id` returns a switched-resume coroutine identifier.
257  }];
258
259  let results = (outs Async_CoroIdType:$id);
260  let assemblyFormat = "attr-dict";
261}
262
263def Async_CoroBeginOp : Async_Op<"coro.begin"> {
264  let summary = "returns a handle to the coroutine";
265  let description = [{
266    The `async.coro.begin` allocates a coroutine frame and returns a handle to
267    the coroutine.
268  }];
269
270  let arguments = (ins Async_CoroIdType:$id);
271  let results = (outs Async_CoroHandleType:$handle);
272  let assemblyFormat = "$id attr-dict";
273}
274
275def Async_CoroFreeOp : Async_Op<"coro.free"> {
276  let summary = "deallocates the coroutine frame";
277  let description = [{
278    The `async.coro.free` deallocates the coroutine frame created by the
279    async.coro.begin operation.
280  }];
281
282  let arguments = (ins Async_CoroIdType:$id,
283                       Async_CoroHandleType:$handle);
284  let assemblyFormat = "$id `,` $handle attr-dict";
285}
286
287def Async_CoroEndOp : Async_Op<"coro.end"> {
288  let summary = "marks the end of the coroutine in the suspend block";
289  let description = [{
290    The `async.coro.end` marks the point where a coroutine needs to return
291    control back to the caller if it is not an initial invocation of the
292    coroutine. It the start part of the coroutine is is no-op.
293  }];
294
295  let arguments = (ins Async_CoroHandleType:$handle);
296  let assemblyFormat = "$handle attr-dict";
297}
298
299def Async_CoroSaveOp : Async_Op<"coro.save"> {
300  let summary = "saves the coroutine state";
301  let description = [{
302    The `async.coro.saves` saves the coroutine state.
303  }];
304
305  let arguments = (ins Async_CoroHandleType:$handle);
306  let results = (outs Async_CoroStateType:$state);
307  let assemblyFormat = "$handle attr-dict";
308}
309
310def Async_CoroSuspendOp : Async_Op<"coro.suspend", [Terminator]> {
311  let summary = "suspends the coroutine";
312  let description = [{
313    The `async.coro.suspend` suspends the coroutine and transfers control to the
314    `suspend` successor. If suspended coroutine later resumed it will transfer
315    control to the `resume` successor. If it is destroyed it will transfer
316    control to the the `cleanup` successor.
317
318    In switched-resume lowering coroutine can be already in resumed state when
319    suspend operation is called, in this case control will be transferred to the
320    `resume` successor skipping the `suspend` successor.
321  }];
322
323  let arguments = (ins Async_CoroStateType:$state);
324  let successors = (successor AnySuccessor:$suspendDest,
325                              AnySuccessor:$resumeDest,
326                              AnySuccessor:$cleanupDest);
327  let assemblyFormat =
328    "$state `,` $suspendDest `,` $resumeDest  `,` $cleanupDest attr-dict";
329}
330
331//===----------------------------------------------------------------------===//
332// Async Dialect Runtime Operations.
333//===----------------------------------------------------------------------===//
334
335// The following operations are intermediate async dialect operations to help
336// lowering from high level async operation like `async.execute` to the Async
337// Runtime API defined in the `ExecutionEngine/AsyncRuntime.h`.
338
339def Async_RuntimeCreateOp : Async_Op<"runtime.create"> {
340  let summary = "creates an async runtime token or value";
341  let description = [{
342    The `async.runtime.create` operation creates an async dialect token or
343    value. Tokens and values are created in the non-ready state.
344  }];
345
346  let results = (outs Async_AnyValueOrTokenType:$result);
347  let assemblyFormat = "attr-dict `:` type($result)";
348}
349
350def Async_RuntimeCreateGroupOp : Async_Op<"runtime.create_group"> {
351  let summary = "creates an async runtime group";
352  let description = [{
353    The `async.runtime.create_group` operation creates an async dialect group
354    of the given size. Group created in the empty state.
355  }];
356
357  let arguments = (ins Index:$size);
358  let results = (outs Async_GroupType:$result);
359  let assemblyFormat = "$size `:` type($result) attr-dict ";
360}
361
362def Async_RuntimeSetAvailableOp : Async_Op<"runtime.set_available"> {
363  let summary = "switches token or value to available state";
364  let description = [{
365    The `async.runtime.set_available` operation switches async token or value
366    state to available.
367  }];
368
369  let arguments = (ins Async_AnyValueOrTokenType:$operand);
370  let assemblyFormat = "$operand attr-dict `:` type($operand)";
371}
372
373def Async_RuntimeSetErrorOp : Async_Op<"runtime.set_error"> {
374  let summary = "switches token or value to error state";
375  let description = [{
376    The `async.runtime.set_error` operation switches async token or value
377    state to error.
378  }];
379
380  let arguments = (ins Async_AnyValueOrTokenType:$operand);
381  let assemblyFormat = "$operand attr-dict `:` type($operand)";
382}
383
384def Async_RuntimeIsErrorOp : Async_Op<"runtime.is_error"> {
385  let summary = "returns true if token, value or group is in error state";
386  let description = [{
387    The `async.runtime.is_error` operation returns true if the token, value or
388    group (any of the async runtime values) is in the error state. It is the
389    caller responsibility to check error state after the call to `await` or
390    resuming after `await_and_resume`.
391  }];
392
393  let arguments = (ins Async_AnyAsyncType:$operand);
394  let results = (outs I1:$is_error);
395
396  let assemblyFormat = "$operand attr-dict `:` type($operand)";
397}
398
399def Async_RuntimeAwaitOp : Async_Op<"runtime.await"> {
400  let summary = "blocks the caller thread until the operand becomes available";
401  let description = [{
402    The `async.runtime.await` operation blocks the caller thread until the
403    operand becomes available or error.
404  }];
405
406  let arguments = (ins Async_AnyAsyncType:$operand);
407  let assemblyFormat = "$operand attr-dict `:` type($operand)";
408}
409
410def Async_RuntimeResumeOp : Async_Op<"runtime.resume"> {
411  let summary = "resumes the coroutine on a thread managed by the runtime";
412  let description = [{
413    The `async.runtime.resume` operation resumes the coroutine on a thread
414    managed by the runtime.
415  }];
416
417  let arguments = (ins Async_CoroHandleType:$handle);
418  let assemblyFormat = "$handle attr-dict";
419}
420
421def Async_RuntimeAwaitAndResumeOp : Async_Op<"runtime.await_and_resume"> {
422  let summary = "awaits the async operand and resumes the coroutine";
423  let description = [{
424    The `async.runtime.await_and_resume` operation awaits for the operand to
425    become available or error and resumes the coroutine on a thread managed by
426    the runtime.
427  }];
428
429  let arguments = (ins Async_AnyAsyncType:$operand,
430                       Async_CoroHandleType:$handle);
431  let assemblyFormat = "$operand `,` $handle attr-dict `:` type($operand)";
432}
433
434def Async_RuntimeStoreOp : Async_Op<"runtime.store",
435      [TypesMatchWith<"type of 'value' matches element type of 'storage'",
436                     "storage", "value",
437                     "$_self.cast<ValueType>().getValueType()">]> {
438  let summary = "stores the value into the runtime async.value";
439  let description = [{
440    The `async.runtime.store` operation stores the value into the runtime
441    async.value storage.
442  }];
443
444  let arguments = (ins AnyType:$value,
445                       Async_ValueType:$storage);
446  let assemblyFormat = "$value `,` $storage attr-dict `:` type($storage)";
447}
448
449def Async_RuntimeLoadOp : Async_Op<"runtime.load",
450      [TypesMatchWith<"type of 'value' matches element type of 'storage'",
451                     "storage", "result",
452                     "$_self.cast<ValueType>().getValueType()">]> {
453  let summary = "loads the value from the runtime async.value";
454  let description = [{
455    The `async.runtime.load` operation loads the value from the runtime
456    async.value storage.
457  }];
458
459  let arguments = (ins Async_ValueType:$storage);
460  let results = (outs AnyType:$result);
461  let assemblyFormat = "$storage attr-dict `:` type($storage)";
462}
463
464def Async_RuntimeAddToGroupOp : Async_Op<"runtime.add_to_group", []> {
465  let summary = "adds and async token or value to the group";
466  let description = [{
467    The `async.runtime.add_to_group` adds an async token or value to the async
468    group. Returns the rank of the added element in the group.
469  }];
470
471  let arguments = (ins Async_AnyValueOrTokenType:$operand,
472                       Async_GroupType:$group);
473  let results = (outs Index:$rank);
474
475  let assemblyFormat = "$operand `,` $group attr-dict `:` type($operand)";
476}
477
478// All async values (values, tokens, groups) are reference counted at runtime
479// and automatically destructed when reference count drops to 0.
480//
481// All values are semantically created with a reference count of +1 and it is
482// the responsibility of the last async value user to drop reference count.
483//
484// Async values created when:
485//   1. Operation returns async result (e.g. the result of an `async.execute`).
486//   2. Async value passed in as a block argument.
487//
488// It is the responsibility of the async value user to extend the lifetime by
489// adding a +1 reference, if the reference counted value captured by the
490// asynchronously executed region (`async.execute` operation), and drop it after
491// the last nested use.
492//
493// Reference counting operations can be added to the IR using automatic
494// reference count pass, that relies on liveness analysis to find the last uses
495// of all reference counted values and automatically inserts
496// `drop_ref` operations.
497//
498// See `AsyncRefCountingPass` documentation for the implementation details.
499
500def Async_RuntimeAddRefOp : Async_Op<"runtime.add_ref"> {
501  let summary = "adds a reference to async value";
502  let description = [{
503    The `async.runtime.add_ref` operation adds a reference(s) to async value
504    (token, value or group).
505  }];
506
507  let arguments = (ins Async_AnyAsyncType:$operand,
508                       Confined<I64Attr, [IntPositive]>:$count);
509
510  let assemblyFormat = [{
511    $operand attr-dict `:` type($operand)
512  }];
513}
514
515def Async_RuntimeDropRefOp : Async_Op<"runtime.drop_ref"> {
516  let summary = "drops a reference to async value";
517  let description = [{
518    The `async.runtime.drop_ref` operation drops a reference(s) to async value
519    (token, value or group).
520  }];
521
522  let arguments = (ins Async_AnyAsyncType:$operand,
523                       Confined<I64Attr, [IntPositive]>:$count);
524
525  let assemblyFormat = [{
526    $operand attr-dict `:` type($operand)
527  }];
528}
529
530def Async_RuntimeNumWorkerThreadsOp :
531  Async_Op<"runtime.num_worker_threads",
532           [DeclareOpInterfaceMethods<InferTypeOpInterface>]> {
533  let summary = "gets the number of threads in the threadpool from the runtime";
534  let description = [{
535    The `async.runtime.num_worker_threads` operation gets the number of threads
536    in the threadpool from the runtime.
537  }];
538
539  let results = (outs Index:$result);
540  let assemblyFormat = "attr-dict `:` type($result)";
541}
542
543#endif // ASYNC_OPS
544