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