| # Tokens | |
| [TOC] | |
| ## Overview | |
| Intuitively, a *token* value is a pointer to an operation (via an OpResult) | |
| or a pointer to a region (via an entry block argument). A token cannot be | |
| forwarded: a token def-use chain cannot be obscured by ops with forwarding | |
| semantics such as `arith.select` or `cf.br`. This allows you to always walk | |
| back from a use and say "this token came from *that* specific op". The exact | |
| structural contract is specified in the | |
| [LangRef section on tokens](LangRef.md#token-type). | |
| A token is an SSA value that has the builtin token type. The token type is | |
| parameterless, opaque and carries no runtime data. Apart from the structural | |
| contract specified in the LangRef, tokens are like any other SSA values. | |
| ## Design Rationale | |
| The token type allows operations to refer to another operation without a new | |
| parallel def-use system for operations. It reuses the existing def-use | |
| machinery for SSA. It introduces no changes to the generic op syntax, the | |
| bytecode infrastructure or core C++ APIs around `Operation`. | |
| As with regular def-use chains, a token def-use chain is unidirectional. A | |
| token use points to the token's definition and not the other way around. | |
| Transformations can remove the use of a token without having to touch or | |
| inspect the definition of the token. | |
| Because tokens are SSA values, they cannot cross `IsolatedFromAbove` region | |
| boundaries. This is intentional: it allows passes to process isolated regions | |
| concurrently without racing on def-use chains. When a token-like dependency | |
| must cross such a boundary, another mechanism must be used (e.g. a symbolic | |
| reference using an attribute). | |
| ## ODS Integration | |
| Tokens are excluded from the default `AnyType` predicate, so an op that has | |
| not opted in cannot accept a token as an arbitrary operand or result. This | |
| restriction prevents tokens from being accidentally passed as operands with | |
| forwarding semantics. | |
| Two predicates are provided in `CommonTypeConstraints.td`: | |
| | Predicate | Accepts | Use when … | | |
| | ------------------ | ------------------------------------ | ----------------------------------------------------------------------| | |
| | `AnyType` | any non-token type | the default; matches the historical meaning of "any type" pre-tokens. | | |
| | `Token` | only the builtin `TokenType` | the op specifically takes a token operand/result. | | |
| Example: | |
| ```tablegen | |
| def MyProduceOp : MyDialect_Op<"produce"> { | |
| let results = (outs Token:$token); | |
| } | |
| def MyConsumeOp : MyDialect_Op<"consume"> { | |
| let arguments = (ins Token:$scope, AnyType:$value); | |
| } | |
| ``` | |
| ODS automatically adds `TokenProducerTrait` when an op declares a `Token`-typed | |
| result, and `TokenConsumerTrait` when it declares a `Token`-typed operand. The | |
| traits must be listed manually when tokens appear as entry block arguments and | |
| for ops that are not defined in ODS. | |
| Region entry block arguments of `token` type are also token producers and | |
| require the parent operation to define `TokenProducerTrait`. Token block | |
| arguments in non-entry blocks are rejected. | |
| ## Examples | |
| ### Non-forwarding Semantics | |
| The [LangRef](LangRef.md#token-type) requires that a token never appears as a | |
| forwarded value. For example, you cannot use a token like this: | |
| * a forwarded result or operand of a `CallOpInterface` op; | |
| * an argument or result type of a `FunctionOpInterface` op; | |
| * a successor operand of a `BranchOpInterface` op; | |
| * a block argument of a non-entry block; | |
| * a forwarded operand to or from any region of a `RegionBranchOpInterface` | |
| op (iter-args, region results, or yielded values); or | |
| * the result of any op that selects or merges values it does not understand | |
| (e.g. `arith.select`). | |
| ### ODS-based Verification: Tokens Rejected in `AnyType` Positions | |
| `scf.yield` operands have forwarding semantics. A token cannot be yielded from | |
| a branch or a loop. | |
| ```mlir | |
| // error: 'scf.if' op result #0 must be variadic of any non-token type, | |
| // but got 'token' | |
| %t = scf.if %cond -> token { | |
| %a = my.token.produce : token | |
| scf.yield %a : token | |
| } else { | |
| %b = my.token.produce : token | |
| scf.yield %b : token | |
| } | |
| ``` | |
| `scf.if`'s results are declared with `Variadic<AnyType>` and `scf.yield`'s | |
| operands likewise use `AnyType`. Because `AnyType` excludes tokens, both | |
| `scf.if` and `scf.yield` fail verification. |