TileFoundry Spec — tir (@prim_func imperative IR)¶
TIR is the imperative target IR. A @tilefoundry.prim_func body parses
into TIR. TIR has no value return; effect-form Ops carry
the work, structural Stmts carry control flow.
- Container:
tir.PrimFunction(name, params, body, output_count, target).bodyis aSequential; the function returns no value. - Stmt tree: function bodies are nested Stmts only. Exprs appear
inside Stmt fields (e.g.
LetStmt.value,For.start). - Effect Ops (
Copy,Fill,Cast,Mma,ReLU,Reduce) are value-class Ops registered with@register_op; in Stmt position they are invoked asEvaluate(op, args)(§1.4). - Value Ops (
AllocTensor,MemorySpan,PtrOf,TensorView) are anchored byLetStmtso their resultVarhas stable identity. - No HIR Ops reach TIR. A program is TIR before the pass pipeline (passes) reads it.
1. TIR Stmt hierarchy¶
1.1 Stmt¶
class Stmt:
"""Provide the abstract base for every TIR statement.
Attributes:
loc: attribute; Optional non-semantic debug location.
"""
loc: str | None = None
- constraints:
- the abstract base of every TIR Stmt subclass; HIR has no
Stmt.
flowchart TB
Stmt["<b>Stmt</b>"]
Sequential["<b>Sequential</b> (Stmt)"]
CtrlStmts["<b>control-flow stmts</b><br/>For / While / If / MeshScope / LetStmt / Return"]
PrimFunction["<b>PrimFunction</b> (Stmt)"]
EvaluateStmt["<b>Evaluate</b> (Stmt)<br/>invokes an Op or function symbol"]
Stmt --> Sequential
Stmt --> CtrlStmts
Stmt --> PrimFunction
Stmt --> EvaluateStmt
1.2 Structural Stmts (tir.stmts)¶
class Sequential(Stmt):
"""Contain a statement sequence.
Attributes:
body: attribute; Statements in execution order.
"""
body: tuple[Stmt, ...]
class LetStmt(Stmt):
"""Bind a variable to an expression before a nested body.
Attributes:
var: attribute; Bound variable.
value: attribute; Bound expression.
body: attribute; Nested statements.
"""
var: Var
value: Expr
body: Sequential
class For(Stmt):
"""Represent a counted loop.
Attributes:
induction_var: attribute; Loop variable.
start: attribute; Initial value.
stop: attribute; Exclusive bound.
step: attribute; Increment.
body: attribute; Loop body.
"""
induction_var: Var
start: Expr
stop: Expr
step: Expr
body: Sequential
class While(Stmt):
"""Represent a conditional loop.
Attributes:
cond: attribute; Loop condition.
body: attribute; Loop body.
"""
cond: Expr
body: Sequential
class If(Stmt):
"""Represent conditional control flow.
Attributes:
cond: attribute; Branch condition.
then_body: attribute; Taken body.
else_body: attribute; Untaken body.
"""
cond: Expr
then_body: Sequential
else_body: Sequential
class MeshScope(Stmt):
"""Scope a mesh binding over a body.
Attributes:
mesh: attribute; Compile-time mesh.
binding: attribute; Lexical mesh binding.
body: attribute; Scoped statements.
"""
mesh: Mesh
binding: Var
body: Sequential
class Return(Stmt):
"""Terminate a value-less primitive function."""
- constraints:
-
the structural (control-flow / binding) Stmt family; bodies are
Sequential. -
MeshScope.meshcarries theMeshobject; thebindingVarscopes the mesh insidebody.
1.3 PrimFunction¶
class PrimFunction(Stmt):
"""Contain one effect-only TIR function.
Attributes:
name: attribute; Function name.
params: attribute; Parameters, with trailing outputs.
body: attribute; Function body.
output_count: attribute; Number of trailing output parameters.
target: attribute; Compilation target for this function.
metadata: attribute; Non-semantic function-level analysis records.
"""
name: str
params: tuple[Var, ...]
body: Sequential
output_count: int = 1
target: Target = field(default_factory=default_target)
The DSL spelling prim_func(output_count=N) records the trailing output
parameters; omitting it means one output.
- constraints:
- itself a
Stmt, not a separate top-level node; returns no value.verify_prim_functionenforces the rules below.
visitor_registry.verify.verify_prim_function(fn, *, module_fns=()) enforces:
- Param homogeneity. All parameters' layouts MUST be uniformly
ShardLayoutor uniformly non-ShardLayout; mixing is rejected. - Fresh
Varidentity. The sameVarobject MUST NOT be bound by more than oneLetStmt/For/MeshScopeacross the function. Parameters seed the bound set. - Lexical uses. A
VarMUST be visible in the scope that binds it. Let values and loop bounds are checked before their binders enter scope; branch, loop, and mesh bindings do not escape. Every variant uses its own parameters. Expression fields, including intrinsic statements and Return, MUST contain IR nodes; lowering recipes are rejected. Op, PrimFunction, and SymbolRef callables are permitted. LetStmttyping.LetStmt.var.typeMUST equal the typeinfer ofLetStmt.value.AllocTensorplacement.Call(AllocTensor, ...)MAY only appear directly asLetStmt.value. Nesting it inside any other Expr is rejected.MeshScopemesh in scope. Any embeddedShardLayoutMUST reference a mesh on the active TIRMeshScopetraversal cache, the composition of that stack, or a parameter'sShardLayout.mesh.Forbound coordinates. AMeshCoordread byFor.start/.stop/.stepMUST name a literal in-range axis of a mesh bound by an enclosingMeshScope.- Retained function calls follow core-ir §2.1. Verification requires strictly equal argument types/counts and rejects recursion.
Evaluate.callable. Whencallableis aSymbolRef(§2.1), module-level resolution MUST find exactly onePrimFunctionof that name in the enclosingModule,argslength MUST match the resolved callee'sparams, and theSymbolRef.typeMUST equal the resolved callee'sCallableType. Whencallableis anOp, it MUST pass the samecheck_callused by ScheduleOp type inference, candidate admission, and lowering. Its declared execution mesh MUST match the enclosing mesh and its operands MUST be reachable from that scope. For atom calls, operand patterns apply to the shared projected issue tiles. Every input operand MUST match itsParamDef.patternand every declaredbetweenrelation MUST hold. A per-Op verifier registered via@register_verify_stmt(Op)MAY impose additional rules that the declaration does not express. An effect Op with neither an input pattern nor abetweenrelation MUST register such a verifier; an Op stating no contract is rejected.
1.4 Evaluate¶
class Evaluate(Stmt):
callable: Op | SymbolRef | PrimFunction | ModuleList | Call
args: tuple[Expr, ...] # the callable's operands in ParamDef / parameter order
- constraints:
- TIR's single Stmt-position wrapper for a no-result invocation; verify and
lowering dispatch on
type(callable).
The callable is one of:
- an effect-form
Op(e.g.tir.memory.Copy,tir.cuda.nn.Mma,tir.tensor.Reduce,tir.Launch§2.3).argsare the Op's operands inParamDeforder. Device-instruction verification uses the sharedcheck_call: match the execution mesh, project instruction tiles, check operand patterns and declaredbetweenrelations, then invoke the optional leaf verifier. A context-dependent operand pattern MAY resolve itself against the callable before matching. HostLaunchinstead checks its target, arguments, and launch extents as a function invocation. -
a
SymbolRef(§2.1) — a reference to a calleePrimFunctionin the enclosingModule.argsfollow the callee's parameter order, the finaloutput_countpositions binding output buffers; the callee is resolved uniquely at module level (§1.3). -
a retained
PrimFunction, single-slotModuleList, or computedCall(TupleGetItem, (ModuleList, index))target, following the same instance resolution as HIR (core-ir §2.1). These device calls remain in one Module tree; they MUST NOT becomeSymbolRefor launches. Printers MUST emithelper(...),child(...), andlayers[i](...)respectively.
Scheduled lowering follows passes §7.1.
Per-Op verify and codegen handlers, when present, are keyed by Op type and
receive the Op together with args; an Op callable carries no result, so its
Call form is unit-typed.
The value-producing counterpart is the Call(Op, args) Expr
(core-ir.md §2.1): it has a non-Unit result
type and is anchored by LetStmt. Evaluate is the unit-typed,
Stmt-position form and the only Stmt-position invocation wrapper.
Effect Op vs. control Stmt. A callable that is a single
unconditional invocation — an effect Op or a function SymbolRef —
is expressed as Evaluate(callable, args). A construct that carries
its own control flow stays a first-class Stmt, not an Evaluate
callable. Abort (§1.7) is a terminator.
1.5 Sync¶
Sync is a mesh-scoped barrier. It is an effect-form op (tir.sync.Sync),
authored T.sync(m), and appears in Stmt position wrapped by Evaluate
(§1.4) like any other effect op. The surface is only
T.sync(m) / T.sync(m[slice]) — there is no m.sync() receiver form.
class Sync(Op):
"""Effect form; mesh-scoped barrier op ``tir.sync.Sync``, authored ``T.sync(m)``.
Attributes:
mesh: attribute; the (possibly sliced) mesh the barrier synchronizes.
"""
mesh: Mesh
- constraints:
- a mesh-scoped barrier; in Stmt position it is wrapped by
Evaluate. The participant set, barrier mapping, and named-barrier id rules are below.
mesh — the participating threads¶
meshis the (possibly sliced) meshSyncsynchronizes.T.sync(m)synchronizes the whole mesh;T.sync(m[1:3, :])synchronizes the constant sub-mesh selected by the slice.- A mesh slice is a compile-time descriptor.
m[...]is evaluated at parse time viaMesh.__getitem__into a sub-Meshwhoselayoutis aComposedLayoutrecording the participating sub-box (the affine "mesh scope" caseimage(c) = offset + outer(c)): the selected per-axis extents over the parent strides inouter, the slice origin (linear thread index of the first participant) inoffset, identityinner. An un-sliced mesh'slayoutis a plainLayout. A sliced mesh is still aMesh; the slice never becomes an IR/SSA value. - The participant set is derived through the existing layout algebra
(
shard.md): the participating linear thread indices areoffset + outer(coord)overouter's domain (the plainlayoutatoffset 0for an un-sliced mesh);baseis the minimum,count = size(outer), and the block domain is the product of the topology extents.classify/participationare the single source of truth shared by verify and codegen. - Legal-slice verification. A sliced mesh is accepted only if its
ComposedLayoutlayoutreconstructs as a constant slice of an enclosing full meshe: same strides, per-axis sub-extents bounded bye's shape, an offset that decomposes into in-range per-axis starts, and the full topology tuple + names equal — the proof rebuildse[key]and compares, so a forged slice cannot pass. A full mesh (plain-Layoutlayout) is accepted only by equality with an enclosing mesh.
Supported slices and the barrier mapping¶
The participant set MUST be a single contiguous thread interval [base,
base+count). Verify MUST reject (never broaden or split):
- a non-contiguous slice (e.g. a lane subset spanning warps);
- a cross-warp range that is not warp-aligned (
baseandcountnot both multiples of 32); - a dynamic / inconsistent / unsupported-topology mesh.
A valid participant set maps to exactly one hardware barrier:
| participant set | barrier |
|---|---|
whole block, more than one warp (base==0, count==domain) |
__syncthreads() |
| whole block that is one warp | __syncwarp() |
| a contiguous lane subset within one warp | __syncwarp(mask) under a participant predicate |
| a warp-aligned contiguous multi-warp subset | a named bar.sync <id>, <count> under a participant predicate |
the full mesh over the cta topology (all CTAs of the grid) |
the grid-wide software barrier (runtime §2.6) |
Codegen MUST guard the __syncwarp(mask) and bar.sync cases with the
participant predicate base <= tid < base+count (tid =
program_id<thread>()): a non-participant thread MUST NOT execute the barrier,
and every participant MUST execute the same id and count.
The first four rows synchronize threads within one block; their participant
set is the contiguous thread interval above. A mesh whose topologies are all the
cta topology instead synchronizes CTAs across the grid — program_id<cta>
ranges over the launch's blocks — and maps to the grid-wide software barrier.
Only the full cta mesh participates: a cta slice (a subset of CTAs) has no
supported barrier and MUST be rejected at verify. The grid barrier's correctness
requires every CTA of the launch to be co-resident; that co-residency is the
launch's occupancy contract, not something the barrier can enforce. The
grid-barrier device helper and its counter protocol are specified in
runtime §2.6.
Named-barrier id allocation¶
A sub-CTA bar.sync MUST carry a named-barrier id, allocated implicitly during
codegen, per kernel. Id 0 is reserved for the whole-CTA barrier; sub-CTA syncs
draw ids from 1..15. Each emitted bar.sync MUST take the next free id; a
sync op node emits once, so a loop body reuses its id. A kernel requiring more
than 15 distinct named barriers MUST error; an id MUST NOT be reused across
distinct sync sites.
Design rationale¶
A barrier's scope is a compile-time constant — which threads take part — so the mesh, and any slice of it, is a compile-time descriptor rather than an SSA value, and the barrier kind is derived from that set by one shared routine so verify and codegen cannot disagree.
1.7 Abort¶
- constraints:
-
a terminating effect Op on believed-unreachable paths, anchored in Stmt position by
Evaluate. -
The CUDA emitter renders
Abortas__trap();in device contexts andassert(false);in host contexts so a runtime hit is loud rather than silent.
1.8 @intrinsic — user-defined effect Stmts¶
# example
@intrinsic
def <name>(<param>: Expr, ...) -> None: ... # decorated function's signature defines the synthesized Stmt subclass; its body becomes the verifier
- constraints:
- synthesises a Stmt subclass, registers the body as its verifier, and wires
parser dispatch under the snake-case name; parameters are annotated
Exprand the return annotation isNone.
2. TIR Expr and callable constructs¶
2.1 SymbolRef¶
class SymbolRef(Expr):
"""Name a primitive-function callee.
Attributes:
name: attribute; Canonical callee name.
nested: attribute; Child module slot path from the current Module.
"""
name: str
nested: tuple[str, ...] = ()
- constraints:
- a leaf
Exprnaming a callee as anEvaluate/Launchtarget; resolution is module-level and unique. Per-field rules below.
SymbolRef is a leaf Expr naming a callee PrimFunction as a call
target: the callable of an Evaluate(SymbolRef, args)
(§1.4) function invocation and args[0] of a Launch
(§2.3).
name¶
- MUST be the canonical name of a
PrimFunctionin the enclosingModule(core-ir.md §1), exactly as stored inPrimFunction.name. It MAY be a generated / mangled specialization name.
nested¶
- MUST name child module slots relative to the caller's current
Module. An empty path selects the current module's own functions. Resolution MUST traverse those slots and find one function namednamein the reached module; it MUST NOT search descendants by an unqualified name. Missing slots or functions MUST report the full requested path. Verify and codegen MUST use the same slot-path resolver.
type¶
- MUST be the resolved callee's IR-level
CallableType(types §7):parametersare the calleeparamstypes in order;return_typeisUnitType(types §6) — a TIRPrimFunctionreturns no value, its outputs are trailing params (§1.4). - MUST be set at construction from the callee in hand; a
SymbolRefwith a deferred or unresolved type MUST NOT enter constructed IR.Expris frozen and verify MUST NOT mutate IR, so verify only checkstypeagainst the resolved callee (§1.3) — it never back-fills.
Resolution starts in the caller's Module and traverses nested child slots.
A unique lookup in the reached Module
(core-ir.md §1) MUST map name to exactly
one PrimFunction; zero or more than one match is an error. Functions with
that name in other modules do not participate in this lookup.
Specialization variants each carry a distinct canonical
PrimFunction.name, so a SymbolRef to a variant resolves
unambiguously; the unmangled dispatcher is represented by its prototype and
variants, not by a SymbolRef. Local typeinfer does not resolve a SymbolRef; it
carries its type directly.
2.2 ShapeOf¶
class ShapeOf(Expr):
"""Produce one runtime tensor extent.
Attributes:
param: attribute; Enclosing primitive-function tensor parameter.
axis: attribute; Tensor axis.
"""
param: Var
axis: int
def shape_of(param: Var, *, axis: int) -> ShapeOf: ... # exported by tilefoundry.dsl
shape_of(x, axis=0) is the authored and printed spelling of this TIR
expression. The parser MUST accept it, and printing followed by re-import
MUST preserve it. It is distinct from HIR tf.shape_of.
- constraints:
typeis a rank-0i32TensorType(scalar): one runtime tensor extent.paramMUST resolve to a parameterVarof the enclosingPrimFunction;axisMUST be a valid axis index ofparam.type.- the type is the only record that an axis is open. A
PrimFunctionMUST NOT carry a second parameter standing for an extent its own types already state as aDimVar; the convention expands the tensor parameter into a pointer plus one extent per open axis (codegen §4.4), so no parameter name is reserved and a user parameter named<something>_shape_<n>is an ordinary parameter.
2.3 TIR Ops¶
Value Ops MUST be anchored by LetStmt.value — their result Var is the only
handle. Effect Ops appear in Stmt position as Evaluate(op, args)
(§1.4). Each Op's full contract lives here, in its catalog entry
below; code carries only a one-line purpose docstring
(SPEC-RULES).
The canonical inspection surface covers every Op in this catalog. Printing and re-importing a TIR program MUST reach a fixed point: value Ops remain assignment forms, effect Ops remain statement forms, and enum-valued attributes use their named enum members with the owning enum imported by the printed program.
TensorType.storageis aStorageKind(types §2). A memory-resident TIR tensor MUST carry a concrete level; the unmaterializedumat(types §2) is an HIR-only value and MUST already be materialized to a concrete level in TIR — it never appears there.Reshard(hir §1.3) does not appear in TIR. A layout change is aTensorViewhere and allocates and copies nothing; a storage change is aLetStmt(AllocTensor)plusEvaluate(Copy, ...).
Memory Ops (tir.memory.*)¶
AllocTensor¶
class AllocTensor(Op):
"""Value form; allocate a tensor, anchored by ``LetStmt.value``.
Attributes:
tensor_type: attribute; the allocated tensor's result type.
"""
tensor_type: TensorType
LetStmt.value.
MemorySpan¶
class MemorySpan(Op):
"""Value form; re-interpret a memory region as a typed tensor.
Attributes:
x: input; the memory region being re-interpreted.
"""
x: Tensor
PtrOf¶
class PtrOf(Op):
"""Value form; take the device address of a tensor.
Attributes:
tensor: input; the tensor whose device address is taken.
"""
tensor: Tensor
PointerType(tensor.dtype, tensor.storage); it does not preserve
the tensor's shape or layout in the pointer type.
TensorView¶
class TensorView(Op):
"""Value form; construct a logical tensor over a typed pointer.
Attributes:
pointer: input; a ``PointerType`` value or an smem byte offset.
dtype: optional attribute; element type stated for a numeric address.
storage: optional attribute; storage stated for a numeric address.
layout: attribute; the view descriptor.
shape: attribute; logical shape, optionally inherited from ``PtrOf``.
"""
pointer: object
dtype: str | None = None
storage: StorageKind | None = None
layout: object
shape: tuple | None = None
shape is authoritative. If omitted, it is inherited only
when the input syntax is exactly T.ptr_of(tensor), from that tensor's
TensorType.shape. No other pointer provenance and no layout is inspected
to guess a shape.
- An integer input is a byte offset from the kernel's dynamic shared-memory
base. It MUST state dtype, storage="smem", and shape; booleans and
non-integer numeric addresses are invalid.
- A PointerType input MAY restate dtype or storage, but any stated value
MUST equal the pointer descriptor.
- Trailing coordinate inputs are not supported on pointer views.
- T.ptr_of MAY point at an allocated ShardTensor; the view rebuilds over
the engine pointer rather than reusing the existing shard layout.
Apple AMX contracts are in tilefoundry.ir.tir.amx.instructions:
T.amx_load_x and T.amx_load_y transfer 32 FP16 values from global memory
to registers; T.amx_fma16 accumulates their outer product into a 32-by-32
FP32 tile; T.amx_store transfers that tile to global memory. The accumulator
uses Layout((32, (16, 2)), (32, (1, 16))) to describe physical Z row order.
These operations execute on the amx mesh and require amx_outer_product.
Native wrappers for the corresponding loads, FMA and physical Z-row stores,
plus AMX state enable/disable, live in include/tilefoundry/runtime/amx/instructions.h.
CPU tensor kernels in tilefoundry.ir.tir.cpu.tensor explicitly select host
implementations for the tensor operations used by hybrid CPU/AMX schedules.
They execute in a single core participant scope, read tensor operands and
write an explicit global-memory destination. Attributes and output types are
checked against the corresponding HIR operation. Access relations, evaluation
and arithmetic costs retain that operation's semantics; these are software
kernel contracts rather than AMX ISA operations.
These contracts require the Target's cpu_tensor capability; they MUST NOT
appear among CUDA or topology-free CPU target instructions.
Copy and Fill use a shared mesh wildcard, specialized from the Target's
MemoryExecutionFacts. They require a contiguous run and sharded register
operands on that same level. The module's Target Facts validate topology names
and extents; memory declarations do not enumerate backend topology names.
Copy¶
class Copy(Op):
"""Effect form; byte-equivalent copy between two tensors.
Attributes:
src: input; Copy source.
dst: input; Copy destination.
"""
src: Tensor
dst: Tensor
execution_mesh: MeshPattern
src declares READ; dst declares WRITE.
- both operands are whole-byte tensors in gmem, smem, or rmem, with equal
dtype. Their storages MAY be equal; same-storage copy is still a byte move.
- src and dst MUST have equal logical shapes for every storage direction.
Copy's access relation pairs the same logical coordinate on both ends.
A caller changing shape MUST state that shape in its source or destination
view; Copy does not infer a reshape from the runtime's enumeration order.
- execution_mesh is the mesh declaration shared by every rmem operand's
ShardLayoutPattern.mesh.
Fill¶
class Fill(Op):
"""Effect form; broadcast a scalar value into a tensor.
Attributes:
tensor: input; destination tensor.
value: input; rank-0 scalar broadcast into ``tensor``.
"""
tensor: Tensor
value: Tensor
execution_mesh: MeshPattern
tensor declares WRITE; value declares READ and MUST be scalar.
- a nonconstant value's dtype MUST equal the destination dtype. Constant zero
is convertible and MAY use the parser's default scalar dtype.
- execution_mesh is the same mesh referenced by tensor when it is rmem;
the scalar carries no shard mesh.
Cast¶
class Cast(Op):
"""Effect form; convert a register tile to ``dtype``."""
src: Tensor
dst: Tensor
dtype: DType
execution_mesh: MeshPattern
src declares READ; dst declares WRITE; both are rmem tensors.
- the operands have equal shapes and distinct dtypes.
- dtype is required and equals the destination dtype.
- both operand shard layouts reference the same execution_mesh declaration.
NN Ops (tir.nn.*)¶
TiledMma¶
class TiledMma(Op):
"""Effect form; matrix-multiply-accumulate ``acc += lhs @ rhs``.
Attributes:
acc: input; accumulator fragment.
lhs: input; left-hand operand fragment.
rhs: input; right-hand operand fragment.
atom: attribute; required compile-time ``MmaAtom`` declaration.
execution_mesh: attribute; optional warp-aligned execution mesh.
"""
acc: Tensor
lhs: Tensor
rhs: Tensor
atom: MmaAtom
execution_mesh: Mesh | None = None
acc += lhs @ rhs; per-target PTX lowering lives in
target, the atom calling convention in
§2.3.
- acc declares READ | WRITE; lhs and rhs declare READ.
ReLU¶
class ReLU(Op):
"""Effect form; pointwise ``max(src, 0)`` written into ``dst``.
Attributes:
src: input; input tensor.
dst: input; destination tensor.
"""
src: Tensor
dst: Tensor
src declares READ; dst declares WRITE; both are rmem tensors. A tile held elsewhere
is moved in and out by Copy, which schedule inserts from this declaration.
Tensor Ops (tir.tensor.*)¶
Reduce¶
class Reduce(Op):
"""Effect form; generic axis reduction dispatched by the ``kind`` tag.
Attributes:
src: input; reduction source.
dst: input; reduction destination.
workspace: input; optional staging buffer sized by lowering.
axes: attribute; reduced-axis tuple.
keepdim: attribute; whether reduced axes remain with extent one.
kind: attribute; ``ReduceKind`` tag.
"""
src: Tensor
dst: Tensor
workspace: Tensor | None = None
axes: tuple
keepdim: bool
kind: ReduceKind
src declares READ, dst WRITE, and workspace READ | WRITE. src and dst may
live in any storage; workspace is smem, because every warp posts a partial there and reads
the others back.
- both dtypes are f32, f16, or bf16.
- axes and keepdim determine the destination shape. Kept axes have extent one;
otherwise they are removed.
- src and dst carry a ShardLayout over one thread mesh; src's layout carries no swizzle
and no offset.
- Reduce carries no dispatch parameter; runtime selects the strategy.
- workspace is present only when lowering sizes cross-warp staging.
- workspace is rank 1 and holds at least one slot per warp of src's thread
mesh, ceil(threads / 32) over the mesh's static positive extents. Its access
relation reaches slots [0, ceil(threads / 32)) from every iteration; which
warp uses which slot is the runtime's choice.
- All forms lower to the single public runtime entry
tilefoundry::ops::reduce<Op, Axes>(src, dst[, workspace]).
- Sharded extents and tiers are derived inside the runtime; this TIR declaration does not expose
the runtime's Plain tier.
Dot¶
dst = sum(lhs * rhs) in one statement, and not an elementwise followed by a
Reduce: materialising the product first would cost a register per element of
the row, which is what makes that pair the wrong spelling here (runtime
§2.6).
class Dot(Op):
"""Effect form; fused multiply-contract over the axes the meshes contract.
Attributes:
lhs: input; left operand.
rhs: input; right operand.
dst: input; the destination cell.
workspace: input; optional shared staging buffer, one slot per warp.
"""
lhs: Tensor
rhs: Tensor
dst: Tensor
workspace: Tensor | None = None
lhs and rhs declare READ; dst declares WRITE; workspace declares READ | WRITE.
lhs, rhs, and dst may live in any storage; workspace is smem.
- No axes attribute. Reduce names its axes because
ops::reduce<Op, Axes> takes them as a template argument; the axes Dot
contracts are the ones the operands' meshes already contract, so restating
them at the call site would be a second source for one fact.
- lhs and rhs contract over the same number of local elements: the fold
walks one operand's length and indexes the other with it. Their global
shapes may differ, and in the canonical matrix-vector call they do — a row
of the matrix is split over the mesh while the vector is broadcast.
- dst is one cell. A contraction leaves a total and every participant leaves
holding it, so a wider destination is not a wider result but cells the op
never writes.
- workspace is smem, and is present only in the form that contracts across
the block: with none the contraction lives inside a warp, with one each warp
posts a partial into a slot. Dot carries no dispatch parameter; the runtime
selects the tier.
- A workspace requires lhs to carry a ShardLayout. Both the count of
warps to fold and the barrier to fold behind come off that mesh, so a
workspace beside a plain operand asks for a block contraction with nothing
saying which block.
- The operands need not agree in dtype: accumulation is f32 whatever is
loaded.
- Both forms lower to the single public runtime entry
tilefoundry::ops::dot(lhs, rhs, dst[, workspace]).
Generic kind-tagged effect Ops (tir.arith)¶
Binary / Unary are effect-form Ops that dispatch on a kind enum rather than
per-op classes; they appear as Evaluate(op, args). BinaryKind /
UnaryKind / ReduceKind are compiler-wide tag enums shared across HIR and
TIR; lowering preserves the kind value without re-mapping. Their owning
definitions are core-ir §4.
Binary, Unary, Where, Clamp, Cast, ReLU, and Reduce are target-neutral
instruction declarations (OpCapability(None)) with registered access
relations, so schedule discovery and an explicit tf.schedule consume the same
contracts as TIR verification. The five elementwise declarations use the
thread mesh named by their rmem operands. Reduce instead walks the source
coordinates and collapses its declared axes into the destination; its optional
workspace is an operand only when the call supplies it.
Clamp¶
class Clamp(Op):
"""Effect form; clamp a source tensor into a destination.
Attributes:
min_val: attribute; Lower bound.
max_val: attribute; Upper bound.
src: input; Source tensor.
dst: input; Destination tensor.
"""
min_val: float
max_val: float
src: Tensor
dst: Tensor
- constraints:
srcdeclaresREAD;dstdeclaresWRITE; both are rmem tensors. A tile held elsewhere is moved in and out byCopy, which schedule inserts from this declaration.srcanddstMUST carry the same dtype; the effect writesmin(max(src, min_val), max_val)elementwise intodst.
Binary¶
class Binary(Op):
"""Effect form; pointwise binary operation ``dst = lhs <kind> rhs``.
Attributes:
lhs: input; left-hand operand.
rhs: input; right-hand operand.
dst: input; destination operand.
kind: attribute; ``BinaryKind`` tag.
"""
lhs: Tensor
rhs: Tensor
dst: Tensor
kind: BinaryKind
lhs and rhs declare READ; dst declares WRITE; all three are rmem tensors. A tile held
elsewhere is moved in and out by Copy, which schedule inserts from this declaration.
- Both lhs and rhs MUST support right-aligned broadcasting, including
missing axes, size-1 axes, and rank-0 scalars. dst.shape MUST equal the
broadcast of their shapes. Operand order MUST be preserved for every kind.
- Lowers to the binary runtime family without per-kind TIR classes.
Where¶
Where(cond, lhs, rhs, dst) writes lhs where cond is true and rhs
otherwise. Its capability is target-neutral and its operands are registers
on the executing thread mesh. cond MUST have bool dtype; the data branches
and destination MUST have the same dtype. All three inputs use right-aligned
broadcasting, and dst MUST have their broadcast shape. HIR Where lowers to
this effect-form instruction. This stage provides no CUDA emitter for it.
Arange¶
T.arange(dst, start=0, step=1) writes start + i * step into coordinate
i of a one-dimensional i32 or i64 register destination. It is a
WRITE-effect instruction available on every target. start and step are
static integers; a symbolic HIR start MUST be refused during candidate
admission with a reason naming start. Its schedule evaluator returns the
same typed sequence. It uses the ordinary registered-op parser and printer.
This stage provides no CUDA emitter for it.
Unary¶
class Unary(Op):
"""Effect form; pointwise unary operation ``dst = <kind>(src)``.
Attributes:
src: input; input operand.
dst: input; destination operand.
kind: attribute; ``UnaryKind`` tag, including rsqrt.
"""
src: Tensor
dst: Tensor
kind: UnaryKind
src declares READ; dst declares WRITE; both are rmem tensors. A tile held elsewhere
is moved in and out by Copy, which schedule inserts from this declaration.
- Lowers to the unary runtime family without per-kind TIR classes.
Launch¶
Effect Op for a host-side launch of a device kernel (CPU entry only, no value);
the callee SymbolRef and grid/block extents flow through the Evaluate args,
the non-grid/block launch config through the Op attributes.
The authored launch-attribute descriptors are owned by
tilefoundry.ir.tir.launch:
class CudaLaunchAttr(IntEnum):
"""Authored selector for a CUDA launch attribute."""
...
class LaunchAttrs:
"""Carry authored launch attribute selector/value pairs.
Attributes:
entries: attribute; Selector/value pairs interpreted by target lowering.
"""
entries: tuple[tuple[CudaLaunchAttr, object], ...] = ()
CudaLaunchAttr identifies the CUDA launch-attribute values carried by
LaunchAttrs.entries; CUDA target lowering interprets them and rejects
unsupported values. These are authored-IR selectors, not a target registration
API. Launch geometry is derived inside codegen and emitted into the generated
host entry; it is not part of the Launch schema and is not carried as runtime
metadata.
class Launch(Op):
"""Effect form; host launch of a device kernel, producing no value.
Attributes:
cluster: attribute; optional cluster extents.
dynamic_smem: attribute; dynamic shared-memory byte count.
stream: attribute; optional stream handle.
attrs: attribute; remaining ``LaunchAttrs`` launch configuration.
"""
cluster: tuple | None = None
dynamic_smem: int = 0
stream: object | None = None
attrs: LaunchAttrs = LaunchAttrs()
# Evaluate(Launch(...), (SymbolRef(callee), grid_x, grid_y, grid_z, block_x, block_y, block_z, *forwarded_args))
- constraints:
- appears only in a CPU (host) entry body; grid/block extents are launch config, not kernel parameters. Per-arg / per-attribute rules below.
Launch appears only in a CPU (host) entry body, as Evaluate(Launch(...),
args) with args = (SymbolRef(callee), grid_x, grid_y, grid_z, block_x,
block_y, block_z, *forwarded_args):
- callee:
args[0]MUST be aSymbolRef(§2.1) resolving to a devicePrimFunctionwith a CUDA target, or a HIRFunctionby its root-relative selector (core-ir §1). The host-entry pass writes and checks a HIR callee's forwarded args (passes §7.3); verification resolves the selector and does not check them again. - grid / block:
args[1:7]are the grid then block extents in the fixed ordergrid_x, grid_y, grid_z, block_x, block_y, block_z. Each is anExpr— aConstantfor a static extent, aShapeOf(§2.2) for a launch-provided (dynamic) one, or a dim-arithmeticCallover those. They are launch configuration, not kernel parameters: the device observes geometry throughgridDim/blockIdx(the codegenprogram_dim/program_shapeaccessors), never as arguments. - forwarded args: the remaining
argsbind the callee's parameters, one each, in declaration order. An extent the callee's types leave open is not among them: it travels with the pointer the convention expands that parameter into (codegen §4.4), read off the tensor the host holds. - attributes:
cluster,dynamic_smem,stream, andattrscarry the non-grid/block launch configuration. Acluster/stream/attrsvalue the active CUDA target does not support MUST be rejected in target lowering.
Execution meshes¶
Target admission metadata uses OpCapability. Its optional declaration and
attribute fields bind a named variant declaration back to its registered Op;
consumers enumerate those records directly from the Op registry. Capability
metadata does not control presentation: instruction reports group by the Op
schema's category and sort by its name within each category.
An instruction Op reserves the class-body name execution_mesh for the mesh
that executes one issue. A fixed instruction declaration MAY state a concrete
Mesh; an Op with a caller-selectable frame MAY state a ParamDef whose
pattern is a MeshPattern; and an element-wise Op MAY point directly at the
same MeshPattern used by its operand shard declarations. A declaration
MAY use an OrPattern of mesh patterns when it supports distinct execution
topologies: Sync accepts thread or CTA meshes; its leaf verifier retains
the complete-grid requirement for CTA synchronization. These are the supported
forms. An instruction whose execution_mesh has another form MUST be
rejected by name rather than treated as running on arbitrary threads.
An operand that carries a ShardLayout and executes element-wise MUST use that
same execution-mesh pattern in ShardLayoutPattern.mesh. Unsharded gmem and
smem operands carry no holder mesh; the execution declaration does not invent
one for them.
Declarative MMA atoms and T.tiled_mma¶
An MMA instruction is a target-owned MmaAtom declaration. The declaration
class states its authored parameters, required physical execution mesh, target
capability, and the TensorPattern read for each A, B, and C role. An
instance binds the parameters for one call; it does not carry a second copy of
concrete fragment layouts that could drift from those patterns.
class MmaAtom(Printable):
namespace: str
execution_mesh: Mesh
capability: str
A: TensorPattern | SwitchPattern
B: TensorPattern | SwitchPattern
C: TensorPattern | SwitchPattern
parameters: tuple[ParamDef, ...]
bindings: dict[str, object]
mesh: Mesh | None
def role(self, role: str) -> TensorPattern: ...
def execution_mesh_pattern(self) -> MeshPattern: ...
def print(self, printer: PrinterBase, ctx=None) -> str: ...
def variants(
parameters: tuple[ParamDef, ...],
*,
vary_defaulted: bool,
values: Callable[[ParamDef], tuple] | None = None,
) -> tuple[dict, ...]: ... # ir/pattern/utils.py
- constraints:
parametersMUST preserve declaration order. Construction MUST reject an unknown binding and a value refused by itsParamDef.pattern; an omitted parameter MUST take the value implied by earlier bindings or its declared default, and otherwise construction MUST fail.bindingsthen holds every parameter, in declaration order.- A string bound to a
DTypeparameter MUST resolve throughDType.from_nameat construction, sodtype="bf16"andDType.bf16bind the same value; whether that dtype is one the declaration takes is still itsParamDef.pattern's answer. An Enum parameter takes only its Enum members; no string spelling of a member is accepted. variants(declaration.parameters, vary_defaulted=..., values=...)MUST return every binding of the parameters, in declaration order, that their patterns admit under the earlier bindings. It enumerates bindings; it does not build atoms or infer parameters from operand types. Enum parameters take their members in declaration order andDTypeparameters every declared dtype;valuessupplies the others, which stay unbound without it or when it answersNone, while an empty answer admits no binding. A parameter with a default is varied only whenvary_defaultedis true. No parameters give one empty binding.schedule candidatescalls it withvary_defaulted=Falseand bindsintparameters to1..the site's largest extent; CUDAMatMultyping calls it withvary_defaulted=Trueand leavesintparameters unbound.- Role-pattern modes MUST follow the logical row-major correspondence in
semantic-analysis §3.1:
A
(M, K), B(K, N), and C(M, N). Split modes MUST place each element on the thread specified by PTX; strides retain their physical values. Repeat modes belong to their logical axes, and operand tiles remove those axes' outer digits rather than a shared prefix of the entire layout. MmaAtomisPrintable(core-ir §2.4):print(printer, ctx)is the atom's importable DSL source. It states every binding in declaration order, implied ones included, writes an Enum as<namespace>.<Enum>.<member>, and hands every other value, the mesh included, toprinter.print(value, ctx), so aDTypeprints as its name and imports land in the caller's context. The atom never builds a printer. A value with no Python form fails explicitly.repr(atom)is a local diagnostic with the same bindings and is not an import surface.role("A"),role("B"), androle("C")MUST resolve the declaration's role pattern under the instance bindings. The logical TIR orientation is always A(M,K), B(K,N), C(M,N); each role pattern separately states the fragment's physical arrangement.- The registered
T.tiled_mmaaccess relation is the sole source of the map from each operand axis to the operation's work axes. Atom declarations state operand shapes and arrangements, but MUST NOT repeat that axis map. execution_mesh_pattern()MUST require the declaration's exact participant count at an aligned offset.mesh, when present, binds the atom to one concrete frame and MUST match the active frame at verify.capabilityMUST use the emitted instruction-family name and be present in the active CUDA architecture'scapabilities.resourceMUST name the execution engine with an_enginesuffix. These are separate axes: support for an instruction family does not itself state an engine rate.
The public declarations are T.cuda.sm80.Mma() (BF16 16x8x16, F32
accumulator, register A/B/C over one warp) and
T.cuda.sm90.Wgmma(n=..., dtype=..., form=..., a_major=..., b_major=..., mesh=...)
(dtype is bf16, f16 or fp8e4m3; 64 x n x k over one warpgroup with
an F32 accumulator, where k is 32 bytes of dtype). dtype has no default.
a_major and b_major default to Major.MN. A 16-bit B may be either major
and a 16-bit A read from registers is K-major; an fp8e4m3 A and B are
K-major only, so that dtype implies both and an explicit Major.MN is
refused. Form and Major live beside Wgmma under T.cuda.sm90.
Calling convention¶
Load, compute, and store are separate effect statements under an enclosing
MeshScope (§1.2). T.tiled_mma has exactly
three input operands plus one required compile-time atom attribute:
accMUST matchatom.role("C")and is read-write.lhsMUST matchatom.role("A")and is read-only.rhsMUST matchatom.role("B")and is read-only.- A, B, and C shapes MUST be
(M,K),(K,N), and(M,N)respectively; operands MUST use the atom's declared dtypes and fragment arrangements. TiledMmagenerates its execution-mesh declaration from the atom's lane shape and strides, preceded by zero or more group modes captured byStarPattern. The whole matched layout MUST be forward and injective, and its offset MUST be aligned to the atom's participant count. Operand meshes retain the atom's exact lane axes.- The active physical mesh MUST satisfy
atom.execution_mesh_pattern(). If the atom carriesmesh=..., its affine offset and ordered lanes MUST equal the active frame.
There is one TIR MMA op: T.tiled_mma. There is no optional-atom or bare-MMA
path. Per-target emission dispatches on the atom (target); the
current CUDA emitter accepts the SM80 declaration and rejects WGMMA because
this stage provides no WGMMA runtime emitter.
Async copy Ops (tir.async.*)¶
Non-blocking cp.async gmem→smem staging for warp-specialized pipelines: a
producer issues copies, groups them, and a consumer waits on the group queue.
CopyAsync¶
ASYNC_WIDTHS = (4, 8, 16)
class CopyAsync(Op):
"""Effect form; async gmem→smem copy, non-blocking.
Attributes:
src: input; gmem staging source.
dst: input; smem staging destination.
index: optional third positional input; rank-1 i32/i64 row indices.
fill: attribute; optional out-of-range row value.
"""
src: Tensor
dst: Tensor
index: Tensor | None = None
fill: float | None = None
execution_mesh: MeshPattern
cp.async; a CUDA architecture that admits
this op lists that emitted ISA family in its hardware document.
- Lowers to tilefoundry::ops::copy_async(src, dst).
- src is gmem and dst is smem, with the same dtype. Each layout MUST
admit the same width from ASYNC_WIDTHS and MUST walk the same tile
mode at step 1. For a ShardLayout, vector width is read from the whole
tile arrangement; its other strides ensure every participant's start is
aligned. Two split layouts compare their tile modes only when their mesh
and shard attrs are identical.
- With index, only dim 0 is gathered: dst[r] = src[index[r]].
It is the third positional input: T.copy_async(src, dst, index, fill=0).
Each of src, dst, and index MUST have a plain layout or a pure
Broadcast ShardLayout, with no Split or Partial attributes. Verification
MUST reject other distributions and identify the operand. This restriction
does not apply to transfers without an index.
The index MUST be rank-1 i32 or i64, and dst MUST have shape
(index.shape[0], *src.shape[1:]). Without index, fill MUST be None.
With a stated fill, an index below zero or at least src.shape[0]
MUST fill the whole destination row without reading the source.
- Indexed transfers MUST partition destination vectors among executing
threads. Vectors MUST stay inside a row; their width is chosen statically
from inner contiguous runs and row-stride alignment, using ASYNC_WIDTHS.
A valid row uses cp.async; an invalid row with zero fill uses the
instruction's zero-fill source-size form, while a nonzero fill uses
st.shared with the destination dtype's repeated fill pattern.
- Indexed schedules produce one CopyAsync instruction, whether the
index names all selected rows or the author schedules one index slice
per loop iteration. Lowering MUST NOT split rows or introduce predicate
operands. Schedule evaluation MUST agree with dim-0 IndexSelect,
including its out-of-range fill behavior. Indexed gather has no TMA form.
- A later read of dst is ordered by CpAsyncCommit followed by
CpAsyncWait.
- execution_mesh states the issuing threads. The gmem and smem operands are
unsharded and therefore declare no separate holder mesh.
CpAsyncCommit¶
- constraints: - LaterCpAsyncWait counts committed groups.
CpAsyncWait¶
class CpAsyncWait(Op):
"""Effect form; wait until at most ``n`` committed groups remain in flight.
Attributes:
n: attribute; most-recent committed groups allowed to remain in flight.
"""
n: int = 0
n is a non-negative compile-time count.
- n = 0 drains every outstanding committed group.
CopyAsyncBulk¶
A staging copy whose completion lands on an mbarrier, and not a tier of
CopyAsync: there every thread issues its own load and a commit closes the
group, so the thread that issues is the thread that waits; here a consumer can
wait for a tile it did not fetch.
Which instruction carries it is the runtime's choice from the operand shard
layouts, not something this op names: a contiguous run takes cp.async.bulk,
anything else takes an element path (runtime
§2.6).
Carrying that on the op would be codegen selecting a tier, which
§2.3 forbids.
The tensor forms (cp.async.bulk.tensor.Nd) take a host-encoded TensorMap in
place of a size, which is a different operand list rather than a different tier,
and are outside this op.
class CopyAsyncBulk(Op):
"""Effect form; gmem→smem staging copy completing on an mbarrier.
Attributes:
src: input; gmem source tile.
dst: input; smem destination tile.
barrier: input; smem mbarrier the completion lands on.
"""
src: Tensor
dst: Tensor
barrier: Tensor
src is gmem, dst is smem, barrier is smem.
- src and dst agree in dtype and shape; the copy moves bytes and does not
convert them.
- Nothing blocks: the copy may still be in flight when the issuing thread
reaches the next statement.
- Consumers wait with MBarrierWaitParity. The arrival that declares the
transferred bytes is the implementation's, issued on the same instruction as
the copy; a caller pairing this with its own MBarrierArriveExpectTx would
be declaring a count the op already knows.
- Lowers to tilefoundry::ops::copy_async_bulk(src, dst, bar)
(runtime §2.6). barrier is a tensor here
because that is what TIR names a piece of shared memory with, and a word to
the runtime, so the emitted call hands over the word's own address.
CopyAsyncTensor¶
T.copy_async_tensor declares the SM90 tensor-map form separately from
CopyAsyncBulk: its operands describe a tensor-map global layout and the box
landed in shared memory, rather than carrying an explicit mbarrier operand.
- constraints:
- Exactly one end is gmem and one is smem; dtype and logical shape agree.
- The global end is a static tensor-map layout of at most five dimensions, with one contiguous mode and every other byte stride a multiple of 16. The shared end is an at-most-five-dimensional box, each extent at most 256, optionally using a 32-, 64-, or 128-byte TMA swizzle.
- Both ends walk the same tile modes. The execution mesh is one aligned warp, preceded by zero or more size-one modes with arbitrary strides. The whole matched layout MUST be forward and injective; its offset is a multiple of 32.
- The declaration requires the target's
tmacapability. CUDA codegen MUST reject it until host-encoded tensor-map construction exists; this stage does not silently lower it to another copy instruction.
LdMatrix¶
- constraints:
srcis a shared-memory(16, 16)bf16 tile anddstis exactly the register A fragment declared byT.cuda.sm80.Mma(); their dtype and shape agree under the ordinaryCopyverifier.- One canonical SM80 warp issues the operation. It requires the target's
tensor_corecapability and lowers totilefoundry::ops::ldmatrix. - The destination layout is the atom's declaration.
Barrier object Ops (tir.sync.mbarrier_*)¶
A Hopper mbarrier is a 64-bit shared-memory word carrying an arrival count, a
transaction-byte count and a phase parity. It is not Sync
(§1.5): Sync is a whole-mesh rendezvous every participant reaches,
while these let a producer signal completion of work the consumer did not
perform — which is what an asynchronous copy needs, since the thread that issues
one is not the thread that waits for it.
Each lowers to its instruction, not to a runtime entry, and the runtime
publishes no ops:: entry for any of them: an mbarrier is a shared-memory
word, so nothing here reads a ShardLayout and none of it is an op
(runtime §2.6). Each entry below names the
mbarrier.* instruction its emitter writes at the call site, together with the
generic-to-shared conversion the instruction takes — they name .shared::cta
explicitly rather than leaving the assembler to redo that window conversion on
every use.
The group is what a CopyAsyncBulk ring needs and no more: arm the word, arrive on it
declaring bytes, wait on its phase, release it. A bare mbarrier.arrive is
absent because ops::copy_async_bulk issues its own for the strided tier, and a bare
mbarrier.expect_tx because nothing pairs with it.
MBarrierInit¶
class MBarrierInit(Op):
"""Effect form; arm a barrier for a fixed number of arrivals.
Attributes:
barrier: input; smem barrier object.
arrive_count: attribute; arrivals that complete one phase.
"""
barrier: Tensor
arrive_count: int
barrier is smem; the instructions take a shared-window address.
- arrive_count is a positive compile-time count. A phase needing zero
arrivals is complete before anything is produced, which makes every
consumer's wait a no-op.
- One thread initialises, and a Sync covering every thread that will use the
barrier separates this from the first arrival or wait.
- Lowers to mbarrier.init.shared::cta.b64, written at the call site with
arrive_count as an inline operand: the runtime publishes no entry for it
(runtime §2.6).
MBarrierArriveExpectTx¶
class MBarrierArriveExpectTx(Op):
"""Effect form; arrive and declare asynchronous bytes in one instruction.
Attributes:
barrier: input; smem barrier object.
tx_bytes: attribute; bytes the paired copy delivers to this phase.
"""
barrier: Tensor
tx_bytes: int
barrier is smem and tx_bytes is a positive compile-time count.
- The phase completes when both the arrivals and the byte count are satisfied,
so one wait covers a copy the waiting thread did not issue.
- tx_bytes MUST equal the bytes the paired copy delivers. A phase expecting a
different count never completes, and that failure presents as a hang rather
than as a wrong value.
- This is not paired with a CopyAsyncBulk, which declares its own bytes on the
instruction that issues the copy. It belongs to a producer issuing one
itself.
- Lowers to mbarrier.arrive.expect_tx.shared::cta.b64, with the arrival
token discarded: consumers wait on the phase parity, not on a token handed
between threads. The runtime publishes no entry for it; ops::copy_async_bulk
writes its own for the bulk tier.
MBarrierWaitParity¶
class MBarrierWaitParity(Op):
"""Effect form; block until the barrier's phase parity reaches a value.
Attributes:
barrier: input; smem barrier object.
phase: input; the parity waited for.
"""
barrier: Tensor
phase: Tensor
barrier is smem.
- The parity alternates 0, 1, 0, ... across successive completions, which is
what lets a fixed ring of barriers serve a pipeline of any length: stage t
of a ring of n waits on parity (t // n) & 1.
- Lowers to a single mbarrier.try_wait.parity.shared::cta.b64 under a C++
loop, not a PTX one: a label inside inline asm is emitted once per
instantiation and collides as soon as two of them land in one translation
unit, and try_wait already parks the warp in hardware for a bounded
interval, so the loop is not a busy spin on the issue pipe. The runtime
publishes no entry for it.
- phase is a value and not an attribute — the parity is a function of the
stage index — and lowers through the same scalar-expression renderer as
If.cond, so a loop induction variable or a constant reaches the
instruction and anything else is refused at codegen.
MBarrierInvalidate¶
class MBarrierInvalidate(Op):
"""Effect form; release the barrier's shared-memory word.
Attributes:
barrier: input; smem barrier object.
"""
barrier: Tensor
barrier is smem.
- Lowers to mbarrier.inval.shared::cta.b64, written at the call site: the
runtime publishes no entry for it.