Introduces a new parameterless, opaque, builtin SSA value type, `!token`. A token cannot appear in a value-forwarding position such as `cf.br`, `arith.select`, `scf.for` iter-args or function call/return. Walking back from any token use reaches the producing operation without crossing such a boundary. Tokens carry no runtime data and cannot constant-fold. This contract is enforced by changing the default ODS `AnyType` predicate to exclude tokens. In addition, token-producing and token-consuming ops must opt in by implementing the `ProducesToken` and `ConsumesToken` traits. The IR verifier checks for the presence of these traits. Note: CSE, DCE, hoisting, and `OperationEquivalence` remain unchanged. A stronger contract (e.g., uniqueness, arity, paired lifetime), if desirable based on the semantics of token-producing and token-consuming ops, can be expressed at the op level via existing mechanisms (side effects, block arguments, traits, attributes), keeping the type orthogonal to operation-level transformations. This mirrors LLVM's `token` type. Other changes: * Builtin bytecode: new entry for `TokenType`. * AsmParser / AsmPrinter: `token` keyword for the textual format. * Remove `LLVMTokenType` from the LLVM dialect and use the builtin `TokenType` instead. * Async dialect: references to `async::TokenType` are qualified to disambiguate from the new builtin `TokenType`. * Documentation: new `mlir/docs/Tokens.md` covering the structural contract, design rationale, and ODS integration. No changes to `Operation`, the generic op syntax, the bytecode op encoding, or core C++ APIs around `Operation`. This commit is in preparation of adding support for breaking exit from regions. (E.g., early exit from loops.) RFC: https://discourse.llvm.org/t/rfc-add-a-builtin-token-type-to-mlir/90706 Assisted-by: claude-opus-4.7-thinking-high --------- Co-authored-by: Mehdi Amini <joker.eph@gmail.com>
4.3 KiB
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.
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:
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 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
CallOpInterfaceop; - an argument or result type of a
FunctionOpInterfaceop; - a successor operand of a
BranchOpInterfaceop; - a block argument of a non-entry block;
- a forwarded operand to or from any region of a
RegionBranchOpInterfaceop (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.
// 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.