TileFoundry Spec — core_ir¶
Defines the shared node algebra — Module / Expr / IRMetadata / Op /
Call / Var / Constant / Tuple — that both HIR and TIR consume.
core_ir is the shared node algebra layer, not a standalone IR: HIR and TIR
each extend it with their own Function container and their own Op / Stmt
subclasses. Types carried by Expr.type are defined in types;
the distributed layout layer is shard; Stmt is not here — it
lives only in tir §1 as a TIR-only base class.
flowchart TB
Module["<b>Module</b>"]
Expr["<b>Expr</b>"]
IRMetadata["<b>IRMetadata</b>"]
Op["<b>Op</b>"]
Call["<b>Call</b>"]
Var["<b>Var</b>"]
Constant["<b>Constant</b>"]
Tuple["<b>Tuple</b>"]
Expr --> Var
Expr --> Constant
Expr --> Tuple
Expr --> Call
Expr -. metadata .-> IRMetadata
Call -. target .-> Op
Module.functions is hir.Function | tir.PrimFunction; the
heterogeneous container is described in §1. HIR
Function is an Expr subclass, TIR PrimFunction is a Stmt
subclass — the diagram above intentionally does not draw an edge
from Module to Expr to avoid implying that all functions are
Exprs.
1. Module¶
class Module:
"""Contain one execution domain and its owned module tree.
Attributes:
name: attribute; Definition name, independent of instance slot keys.
functions: attribute; Heterogeneous HIR and TIR function container.
entry: attribute; Optional public entry-function name.
children: attribute; Slot keys mapped to shared ModuleList objects.
target: attribute; Declared hardware target, or None to inherit.
topologies: attribute; Declared hierarchy, None to inherit, or an empty tuple.
metadata: attribute; Target and compiler-option metadata.
methods: attribute; Plain Python orchestration methods.
"""
name: str
functions: tuple[hir.Function | tir.PrimFunction, ...]
entry: str | None = None
children: dict[str, "ModuleList"] = field(default_factory=dict)
target: Target | None = None
topologies: tuple[Topology, ...] | None = None
metadata: dict[str, object] = field(default_factory=dict)
methods: Mapping[str, object] = field(default_factory=dict)
def weights(self) -> Mapping[str, TensorType]: ...
def resolve_target(self) -> Target: ...
def effective_topologies(self) -> tuple[Topology, ...]: ...
def resolve_topology(self, name: str) -> Topology: ...
def owns(self, function: object, *, derived: bool = False) -> bool: ...
def load(self, resource) -> "LoadedModule": ...
def prepare(
self, raw, out_dir: str, *, device: str = "cpu"
) -> None: ...
- constraints:
- the top-level compilation unit (parser output; pass input/output);
constructing a
Moduleseals its functions. - a
Moduleis the execution domain of the functions it owns: it, not they, declares theTargetand the orderedTopologyhierarchy. childrenmaps instance slot keys toModuleListobjects. Definitions and function bodies remain shared; placing a definition creates no copy and no parent backlink. A class-bodyModulebecomes a single-element, non-indexed slot; a class-bodyModuleListis held as authored.- a child MAY declare its own Target; holding it does not type its bodies
again. A call of a child function is typed
from the callee body under the callee's effective Target with the call's
own argument types (hir §1.1); that writes the
caller's
Call.typeand nothing in the child. owns(function)MUST use identity and accept the Module's direct functions and their specialization variants. Withderived=True, it MUST also follow a rebuilt function's recorded origin (hir §2) for the whole chain, not only its first edge — a rebuild may itself be rebuilt; equal copies and same-name functions from another Module MUST remain unowned.
A Module is a static definition and execution-context container, not a dynamic
invocation. Owning a Function, attaching a child Module, or declaring a
Target or Topology hierarchy never itself begins or counts an invocation;
Python-to-HIR entry and HIR-to-HIR device calls are governed by
hir §1.1.
parse_module(see parser §2) returns aModule.- A bare
@func/@prim_funcbecomes an implicit single-functionModulewhoseentryis set to that function. A function that declares execution context of its own is therefore already aModule. - Same-module
@prim_funccalls resolve through the module's symbol table, not Python closures. topologiesis the execution domain's complete ordered hierarchy.with Mesh(("cta",), layout=...)inside a function body names one of the effective levels and creates a lexical mesh binding.
Effective context. target and topologies record what a Module
declares, not what it resolves to; a visitor carries the effective declarations in its current ValuationContext,
without copying them onto shared definitions or Functions.
- constraints:
topologies = Nonedeclares nothing and inherits the nearest instance declaration;topologies = ()declares an explicitly topology-free domain; an explicit tuple replaces the inherited hierarchy whole rather than extending it. A declared tuple MUST NOT repeat a level name.resolve_topology(name)returns the single declared level with that exact name and MUST fail, naming the levels that are available, when there is none.metadatacarries target / compiler-option configuration, never semantic topology / mesh information.- A
childrenkey is the class-body attribute name.Module.nameremains the definition name. Instance resource paths append that key, followed by the element index for an indexed slot, e.g.layers.3.input_norm.w. - Each slot has its own object identity; a definition may occupy several
single slots or occur repeatedly in a
ModuleList, sharing Functions. AModuleListobject MUST NOT be attached under two different keys. Rebuilding a Module with the same slot key MUST remain valid. - Lists and tuples of Modules in a class body MUST be refused with guidance
to use
ModuleList(definition, N). methodscollects plain Python functions (orchestration methods, e.g.forward/init_caches; full collection rule in parser §3). A function name, a child module name, and a method name MUST be disjoint at oneModule's own level — all three resolve through the same attribute surface (§1.1 below), so a name used by more than one would be ambiguous.weightsis a derived property, not a stored field: each access unions every function'sConstTensorparams (Var.is_const), in (function order, param order); the same name in two functions MUST carry an identicalTensorType, or the access raises. There is nostatesfield or persistent-state concept in the IR — a tensor that must survive across steps (e.g. a KV cache) is an ordinaryTensorparam the caller passes in and receives back explicitly (runtime §1.3).- Constructing a
Moduleseals its functions: each base function and its specialization variants are finalized. Variants may be added to a base only during authoring, before the base enters aModule; once sealed, adding a variant is an error (hir.md §1.1).
Target inheritance¶
A Module's target declaration belongs to its execution domain and is resolved by the visitor from the callee declaration or the inherited caller context.
- constraints:
resolve_target()MUST return this Module's declared target. Inherited context belongs to the visitor'sValuationContext, not the shared Module.- Callee context inheritance and call compatibility follow hir §1.1.
- A root Module with no target declaration MUST be refused
when its target is resolved. Its root Module MUST declare one, for example
with
target=CudaTarget("nvidia.h200_sxm"). - A Target declaration MUST be a constructed Target instance. A string MUST be refused rather than resolved or constructed.
resolve_target()MUST return the exact instance this Module declared; it MUST NOT reconstruct or replace an equal value.- Declaring a Module MUST NOT require a target; a shared definition can inherit execution context when a visitor enters it through a caller.
Default step¶
A Module's optional entry determines whether it exposes a default execution
step.
- constraints:
- When
entryis present, it MUST name a function infunctions.entry_function()MUST return that function, andtilefoundry.build(...)and the emitter MUST start there. Other functions enter the output only when reachable fromentry. - When
entryis omitted (None), the Module MUST have no default step.entry_function()and an unqualified evaluator call MUST be refused, naming the Module's functions and explaining that one is selected by name. Each function remains reachable by name. A Module that composes children in an orchestration method has no single step to nominate; that host Python method is invoked by name rather than selected as an evaluator target. - A bare
@func/@prim_funcMUST become an implicit single-function Module whoseentrynames that function.
1.1 Function access¶
A Module mirrors the model it describes: a caller reaches a kernel, a child
component, or an orchestration step through the same attribute surface.
Each name maps to at most one entry among Module.functions,
Module.children, and Module.methods. Within functions,
shape-specialization variants live inside that entry's Function.variants
(hir.md §1.1), never as separate same-name entries.
- constraints:
- A function name, child-module name, and method name MUST be mutually disjoint at one Module's own level.
mod.lookup(name)returns the function namedname; it raises unless exactly one function matches. This is the canonical name-resolution contract (e.g. for aSymbolRefcallee), searches only this Module's ownfunctions, and always returns theFunction/PrimFunctionnode itself.mod.entry_function()returns the function named byentry.- Python attribute access
mod.<name>resolves, in order, a function, a child module, or a method named<name>, and MUST raiseAttributeErrorwhen none match or when more than one same-kind entry shares the name. A function name resolves to theFunction/PrimFunctionnode itself, likelookup; an HIRFunctionis callable as oneevaluator.evaluatestep. AModuleis likewise callable when it declares anentry, with one argument for every declared parameter. A child module name resolves to that childModulefor a non-indexed slot or itsModuleListfor an indexed slot.ModuleListsupports Python iteration and indexing over its shared definitions. A method name resolves to the class-body function bound like an instance method (m.forward(...)). Names beginning with_are never functions, modules, or methods and resolve by normal attribute rules. This lets a module read like the model it mirrors —decoder.layer0.attention.
1.2 Definition call paths¶
tilefoundry.ir.module_utils.list_call_paths(root) enumerates each declared
function once by identity, in DFS first-instance order. It returns a
dict[call_path, ValuationContext], including HIR Functions and PrimFunctions. A
call path is a dot-separated string. The frame records the current Module,
function, effective target, and effective topologies; shared definitions use the first
path for generated names, including indexed segments such as layers.0.run.
1.3 ModuleList¶
class ModuleList(Expr):
"""Hold one instance slot without copying its definitions.
Attributes:
key: attribute; Attachment key assigned by the containing Module.
elements: attribute; Shared definitions in instance order.
indexed: attribute; Whether source calls require an element index.
"""
key: str | None
elements: tuple[Module | type[RuntimeModule], ...]
indexed: bool
def __init__(self, definition, count=MISSING): ...
- constraints:
- A
ModuleListis an Expr leaf carryingkey,elements, andindexed. Its key is assigned on attachment; its elements MUST be non-empty. Semantic Module slots MUST contain only Module definitions. Runtime twin declarations use RuntimeModule subclasses through the same list surface (runtime §1.3). A supplied count MUST be a positive integer, excluding bool. One Module without a count creates a non-indexed slot; a count or a tuple creates an indexed slot, preserving element order and repeated references. - Construction and attachment MUST NOT resolve element entries. A container
without an entry MAY occupy a slot. Reading
.typeor type inference projects each entry's signature with ConstTensor parameters omitted:CallableTypefor a single slot,TupleType(CallableType, ...)for an indexed slot. Calling a container without an entry MUST report the usual no-entry error. - A constant index MUST be in
[0, N). A variable index MUST be an integer scalar and all elements MUST reference the same definition by identity. - A single slot MUST NOT be indexed; an indexed slot MUST NOT be called without an index. Only the instance entry may be called, not other methods.
TIR symbol resolution uses resolve_symbol(module, name, nested=...): each
nested component MUST select a child slot of the current node, and name
MUST select exactly one function in the reached node. Empty nested stays in
the current module. Failures MUST include the complete path; same-named
functions in different child modules are distinct symbols.
2. Expr¶
class IRMetadata:
"""Describe one immutable expression annotation."""
def resolve(self, ctx) -> IRMetadata: ...
class BindingMetadata(IRMetadata):
"""Describe an authored SSA binding name.
Attributes:
name: attribute; Authored binding name.
"""
name: str
class SourceSpanMetadata(IRMetadata):
"""Describe an authored source range.
Attributes:
file: attribute; Source file.
line: attribute; Starting line.
column: attribute; Starting column.
end_line: attribute; Optional ending line.
end_column: attribute; Optional ending column.
"""
file: str
line: int
column: int
end_line: int | None = None
end_column: int | None = None
- constraints:
-
the immutable base of every typed annotation stored on an
Expr: typed fields and an optionalresolve(ctx)interpretation. The default returns the same record. Analysis supplies its context; environment-dependent records use that context's bound Scope. Core metadata does not define a capability provider or declare requirements. Core MUST NOT import analysis to interpret metadata. Text and JSON rendering belong to inspection (inspection §2.8); a record MUST NOT carry a rendering, a key name, or a family name of its own. -
BindingMetadatais the authored SSA label. The parser maps explicit DSLloc=syntax and inferred assignment names to this metadata; there is no parallelExpr.locfield. SourceSpanMetadatarecords the parser source range before type inference. Its file, line, and start column identify the physical authored file position; the start column is one-based.end_column, when present, is the physical source-file offset using Python AST's exclusive-end convention.
class Expr:
"""Provide the mutable base for every typed expression.
Attributes:
type: attribute; Expression type.
metadata: attribute; Typed expression annotations.
"""
type: Type
metadata: tuple[IRMetadata, ...] = ()
- constraints:
- base of every expression node; concrete subclasses are dialect-owned, not
introduced per Op (value-producing Ops appear as
Callnodes). typeandmetadatamay be updated by the authorised typing and analysis passes; expression equality remains structural and metadata is excluded.metadatacontains onlyIRMetadatavalues and contains at most one value of each exact concrete metadata class; invalid entries or duplicate classes raiseVerifyError, includingSourceSpanMetadataorBindingMetadatacontext when it is available.metadataMUST NOT participate in expression equality, hashing, or repr.
def get_metadata(expr: "Expr", cls: type[T], *, ctx=None) -> T | None: ...
def remove_metadata(expr: "Expr", cls: type[IRMetadata]) -> "Expr": ...
def attach_metadata(expr: "Expr", value: IRMetadata) -> None: ...
def detach_metadata(expr: "Expr", cls: type[IRMetadata]) -> None: ...
def binding_name(expr: "Expr") -> str | None: ...
def describe_expr(expr: "Expr") -> str: ...
def diagnostic_location(expr: "Expr") -> str | None: ...
def source_metadata(expr: "Expr") -> tuple[IRMetadata, ...]: ...
- constraints:
get_metadataandremove_metadatamatch an exact concrete class, not subclasses, and never mutate the input expression.get_metadatareturns the unique matching definition record orNone. Withctx, it returnsrecord.resolve(ctx); context-independent records return themselves. Resolving MUST NOT mutate the shared definition record.remove_metadatareturns a copy without the matching value; when the class is absent it returns the input expression unchanged.attach_metadataanddetach_metadataperform those exact-class updates in place for passes that annotate the caller's IR. Attaching replaces any existing value and appends the new value after the retained metadata.binding_namereturns the authored SSA label.diagnostic_locationprefers a source span and falls back to that label.source_metadatacopies only binding/span metadata when a compiler pass synthesizes a replacement expression.describe_exprreturns one diagnostic line with the source span when present, the binding name or<unnamed>, and the Call target or Expr class.
Expr always carries a type. The runtime class of Expr.type is
one of TensorType / TupleType / UnitType
(types §2 / types §4
/ types §6). Concrete Expr subclasses are
not introduced per Op — value-producing Ops appear as Call nodes
whose target carries the Op instance. Multi-output Ops produce a
single Call whose type is TupleType; consumers project a single
field through the tuple_get_item Op (a regular registered Op; no
dedicated Expr subclass).
Dialect-specific Expr subclasses are owned by their dialect specs, not
here: HIR owns Function, LoopRegion, and MeshRegion
(hir §1),
and TIR owns SymbolRef and other TIR-specific Expr constructs
(tir). The similarly-shaped TIR statement keeps the name
MeshScope (tir §1.2) on purpose,
not by oversight: it binds a Var over a Sequential body, whereas
MeshRegion folds a region into one value.
2.1 Call¶
class Call(Expr):
"""Represent an Op invocation.
Attributes:
target: attribute; Op, Function, instance slot, or computed instance target.
args: attribute; Input expressions in parameter order.
"""
target: Op | hir.Function | ModuleList | Call
args: tuple[Expr, ...]
- constraints:
Call(target=Function)supplies every parameter in the current instance. A single-slot target is aModuleList; an indexed instance target isCall(TupleGetItem, (ModuleList, index)). Instance calls supply only the entry's non-ConstTensor parameters. Implicit constants come from that instance's resource path.- Generic traversal MUST visit a Call-valued target before its args; it MUST NOT traverse Function targets or the definitions held by ModuleList leaves.
- Entering an instance call MUST read the definition from the current
module's
children[key].elements[index]. The target supplies only the key and index, so rebuilt module trees remain authoritative. - a value-form
Callis anchored byLetStmtin TIR; a Stmt-position effect invocation isEvaluate(op, args). - A
CallMUST NOT appear as a top-level Stmt directly. - Normally,
len(args)MUST equal the number ofkind="input"ParamDefs ontarget. A sole input annotatedTuple[T]describes a variadic sequence, and every flattened argument corresponds to that ParamDef. - Each argument MUST satisfy its corresponding input ParamDef's pattern / typeinfer rule.
2.2 Var / Constant / Tuple¶
class Var(Expr):
"""Represent a named value.
Attributes:
name: attribute; Value name.
type: attribute; Declaration-side type.
is_const: attribute; Whether this is an external constant parameter.
"""
name: str
type: Type
is_const: bool = False
class Constant(Expr):
"""Represent a literal value.
Attributes:
value: attribute; Literal payload.
"""
value: object
class Tuple(Expr):
"""Represent a value-level aggregate.
Attributes:
elements: attribute; Aggregate elements in field order.
"""
elements: tuple[Expr, ...]
- constraints:
Tupleis anExprin the IR graph, distinct from theTupleTypeit carries.Var.is_constMUST be preserved for HIR function parameters and MUST mark an external constant tensor parameter without embedding a tensor payload or changing itsTensorType.
2.3 Op¶
Op is a value class (it describes an op's signature and attributes)
— not an Expr subclass. A Call carries an Op instance in its
target field. The custom-op mechanism is declared here: parameters are
ParamDef class attributes discovered through reflection, and a class is
registered with @register_op.
class ParameterInfo:
"""Describe the public reflection view of one Op parameter.
Attributes:
name: attribute; Declared parameter name.
kind: attribute; Whether the parameter is an input or attribute.
type: attribute; Declared Python annotation.
"""
name: str
kind: Literal["input", "attribute"]
type: object
class Op:
"""Describe an operation signature and its attribute values."""
def params(cls) -> list[ParameterInfo]: ...
def __init__(self, **attrs): ...
- constraints:
- a value class describing an op's signature/attributes, not an
Exprsubclass; aCallcarries anOpinstance intarget; most Ops are pure. - resource-introducing Ops (e.g.
tir.memory.AllocTensor) with positional-identity requirements MUST be anchored by aLetStmt(see tir §2.3).
class MemoryEffect(Flag):
READ = auto()
WRITE = auto()
class ParamDef:
"""Declare one Op input or attribute.
Attributes:
kind: attribute; Whether the parameter is an input or attribute.
annotation: attribute; Python value-family annotation.
pattern: attribute; Optional input-type predicate.
optional: attribute; Whether None is accepted.
default: attribute; Call-site default or the required-value sentinel.
effect: attribute; Declared storage effect, or None when undeclared.
"""
kind: Literal["input", "attribute"]
annotation: type = field(default=object)
pattern: Pattern | None = None
optional: bool = False
default: Any = MISSING
effect: MemoryEffect | None = None
- constraints:
- a single Op parameter descriptor; the order of input-kind ParamDefs fixes
Call.argsposition. MemoryEffectis aFlagwithREADandWRITE; an input may declare either or both.Nonemeans undeclared, while the zero flag is invalid. Attributes cannot carry a memory effect.
Example:
# example
@register_op
class Binary(Op):
lhs = ParamDef(kind="input", pattern=is_ranked_tensor())
rhs = ParamDef(kind="input", pattern=is_ranked_tensor())
kind = ParamDef(kind="attribute", annotation=BinaryKind)
@register_op
class ReduceSum(Op):
input = ParamDef(kind="input", pattern=is_ranked_tensor())
axis = ParamDef(kind="attribute", annotation=int)
keepdims = ParamDef(kind="attribute", annotation=bool, default=False)
Instantiation:
- An Op with no
kind="attribute"parameter returns the same singleton from eachOp()call. - An Op with attributes (e.g.
Binary,ReduceSum) carries the attribute values on the instance:Binary(kind=BinaryKind.ADD)/ReduceSum(axis=1, keepdims=True).
Surface aliases (@register_alias)¶
A surface alias is a registry entry that has no IR class of its
own; instead, its OpSchema.builder callback constructs a target
Op with some attributes pre-fixed. Aliases let several user-callable
surface names share a single kinded IR class without exposing the
kind=... attribute at the call site.
# Registry coordinates select the surface bucket; ``params`` reuses the
# target Op's static ParamDef references.
@register_alias(dialect="...", category="...", name="...", params=[...])
def builder() -> Op:
"""Return a target Op with attributes pre-fixed; takes attribute kwargs only."""
- constraints:
- alias schemas have no IR class (
OpSchema.op_class is None) and prepend to the schema bucket so they win first-match resolution.
Properties:
paramsreuses the staticParamDefreferences of the target Op. The alias never re-declares ParamDef structures.buildertakes attribute kwargs only; input args still flow intoCall.argsvia the parser. For a value-form alias whose builder fixes every attribute (e.g.addfixeskind), the builder takes zero kwargs.- Aliases prepend to the schema bucket so they win first-match
resolution.
_first_op_classskips alias entries so anyop_class-keyed legacy lookup transparently sees the real class registered for the same name (orNonewhen there is none).
# example
@register_alias(dialect="tf", category="math", name="add",
params=[Binary.lhs, Binary.rhs])
def _add() -> Op:
return Binary(kind=BinaryKind.ADD)
Input position and form¶
The order of kind="input" ParamDefs determines Call.args
position: call.args[i] corresponds to the i-th input ParamDef
from op.params().
An Op is value-form when its Call produces an observable
result the IR consumes — Call.type is then TensorType or
TupleType. An Op is effect-form when it performs an in-place
effect (e.g. tir.memory.Copy / tir.cuda.nn.TiledMma) and produces no
readable value (UnitType, types §6); in Stmt position
it appears as Evaluate(op, args)
(tir §1.4).
2.4 Printable and PrinterBase¶
class PrinterBase(ABC):
"""A printer: the one entry that writes a value as source text."""
@abstractmethod
def print(self, value, ctx=None, indent: str = "") -> str: ...
class Printable(ABC):
"""A value that writes itself, handing the values it holds to *printer*."""
@abstractmethod
def print(self, printer: PrinterBase, ctx=None) -> str: ...
- constraints:
- Both live in
ir/core/inspection.pyand import no printer, so an IR value can print itself without anirtoinspectiondependency, and a concrete printer depends on these interfaces rather than on the value's class. PrinterBase.printis a printer's one public entry. A printer hands aPrintableitself and the caller'sctx; it does not recognise a value by a method name it happens to have.Printable.printwrites the value and prints every value it holds withprinter.print(value, ctx), so imports and names land in the caller's context.
3. Pattern¶
Pattern is the reusable predicate carrier shared by parser dispatch
and specialization dispatch.
class Pattern:
"""Carry a reusable dispatch predicate."""
def match(self, subject, captures=None) -> Match | None: ...
- constraints:
- shared by parser dispatch (
ParamDef.pattern) and specialization dispatch (Function.specializations/PrimFunction.specializations). - a successful match returns a truthy
Matchcarrying named captures; failure isNone.
The implementation is split by responsibility under ir/pattern/:
pattern.pydefinesPatternand the composable classesOrPattern,AndPattern,SequencePattern,SwitchPattern,RangePattern,LayoutPattern,SwizzlePattern,ComposedLayoutPattern,MeshPattern,ShardLayoutPattern,TensorPattern,WildcardPattern, andStarPattern.predicates.pydefines the computed-condition basePredicate; integerTermexpressions and BooleanFormulapredicates; and the named arrangement predicatesForward,Injective,WholeVectors,PlainArrangement,BoxDims, andTensorMap; and the tensor predicateRanked. It also owns theis_ranked_tensor(**fields)andis_scalar_tensor(**fields)factories.match.pyowns the publicPatternMatcherand structuralRefusalevidence. An unstated (None) pattern field admits any value. Pattern nodes carry data only; declaration text and refusal text belong to inspection (inspection §2.9).constraint.pyowns cross-operandConstraint,DistinctConstraint,SameConstraint, andSameModesConstraintvalues.utils.pyowns specialization naming and dimension lookup.
Pattern.match(subject, captures=None) is the public facade: it creates one
PatternMatcher, which dispatches along the pattern class's MRO and owns the
bindings, matched-node memo, deferred predicates, and branch rollback for that
match. Nested patterns stay in that matcher instead of creating their own
capture dictionaries. Effect-Op verification reuses one matcher across all
operands of a call, so later operands see bindings established by earlier ones.
An unnamed WildcardPattern() admits any value. A named
WildcardPattern(name) also binds that value; every occurrence of the same
name must match the same value. Named wildcards form integer Term
expressions with +, -, *, //, and %; comparisons form a Formula,
and &, |, and ~ combine formulas. In, Table, Bits, ForAll,
Sum, and Count cover membership, lookup, dtype width, and tuple captures.
Python truth testing and chained comparisons are rejected; callers must use
the formula operators rather than and, or, or Python in.
Predicate.holds(subject, bindings) returns true, false, or None when names
needed by the predicate remain unbound. Ground formulas are evaluated at the
pattern node that owns them, so a failed formula can reject an alternative.
Only unresolved formulas are deferred; after structural matching they are
compiled into one OR-Tools CP-SAT model. An infeasible model is a mismatch,
while an UNKNOWN solver result remains explicitly unknown rather than being
reported as false. A refusal records the failed pattern, its subject,
and a snapshot of the bindings; inspection decides how to render that
evidence. TensorPattern matches fields in the order dtype, storage, shape,
then layout. When an unbound SwitchPattern rejects every branch, the refusal
records that switch itself rather than a failed pattern inside one branch.
Structural refusals render the subject and pattern; predicate refusals render
the predicate that actually failed, including the false conjunct of a formula.
The hand-written predicates in
predicates.py read the layout itself, including its coalesced runs and
algebraic properties; Formula predicates instead read names bound by
structural patterns.
LayoutPattern optionally matches a bare Layout's nested shape and
strides, then applies its tuple of predicates. ComposedLayoutPattern,
ShardLayoutPattern, and TensorPattern likewise apply their own predicate
tuples after their structural fields match. Omitting both layout structural
fields leaves the structure unconstrained and lets predicates read through
supported composed or sharded forms. Forward() and Injective() express the
corresponding computed properties; they are not implicit.
When both structural fields are present, they are matched as paired CuTe modes
while preserving their authored nesting. A StarPattern(p) in the same
position of both fields consumes zero or more modes and applies p to every
consumed mode; each tuple may contain at most one star. Bindings below a star
are tuples, and a name may not occur both below and outside a star.
LayoutPattern.from_layout(layout, ...) constructs the exact bare or composed
pattern for an authored arrangement, preserving its nested structure and the
explicitly supplied predicate table. Forward(per_mode=True) and
Injective(per_mode=True) check each top-level mode independently.
ComposedLayout(None, 0, L) and L are equivalent for both LayoutPattern
and ComposedLayoutPattern: a bare layout supplies an identity inner and zero
offset to a composed pattern. An omitted ComposedLayoutPattern.inner remains
unconstrained and therefore admits any inner, including a swizzle. To require
no swizzle, state SwizzlePattern(0, 4, 3); its zero-bit variant also matches
an identity (None) inner. The three swizzle fields may themselves be
patterns.
MeshPattern rejects any supplied arrangement predicate that exposes
per_mode=False, because each mesh level uses its own numbering space; an
empty predicate table is allowed. It never changes the supplied pattern
implicitly. Predicates on a surrounding ComposedLayoutPattern instead
check the whole matched layout. Execution-mesh declarations may admit leading
modes with StarPattern; operand mesh declarations keep their exact axis
structure because shard attrs refer to those axes.
A wildcard in MeshPattern.topologies selects the innermost execution level
and binds its one-name tuple. Reusing that wildcard in an operand mesh requires
the same level; valid topology names and extents come from the module's Target
Facts, as specified in target §11.
ShardLayoutPattern names the same layout, attrs, and mesh fields as
ShardLayout: layout and mesh are nested patterns, while attrs remains
an exact structural value. Its mesh pattern may use the bare/composed identity
equivalence above when stating sliced forms.
Two consumer surfaces:
- Parser dispatch —
ParamDef.pattern(§2.3) is matched against an argument'sExpr.typeduring overload resolution. Subclasses used:TensorPattern(shape?, dtype?, storage?, layout?)(any rank), andAndPattern(parts)(conjunction). Two factories construct convenience patterns:is_scalar_tensor(**fields)returnsTensorPattern(shape=(), **fields)for rank-0 tensors;is_ranked_tensor(**fields)returns aTensorPatternwith aRankedpredicate and MUST reject rank-0. A tensor rank is stated by givingshapethat many positions; wildcard positions constrain only the sequence length. An omitted shape admits any rank. During selection,umatsatisfies any concrete storage requirement because its placement is undecided; lowering MUST materialize it before TIR. During effect-Op verification, input patterns match inParamDeforder against one shared capture environment, so a later operand can require a value captured by an earlier operand. - Specialization dispatch — patterns appearing in
hir.Function.specializations(hir.md §1.1) andtir.PrimFunction.specializationsdescribe which runtime shape range a variant covers. The HIR→TIR lowering inspects each pattern's fields directly; it does not callmatch.
3.1 RangePattern¶
class RangePattern(Pattern):
"""Match a closed integer range, optionally naming a dimension.
Attributes:
dim_var: attribute; Specialization dimension name, or empty otherwise.
lo: attribute; Optional inclusive lower bound.
hi: attribute; Optional inclusive upper bound.
"""
dim_var: str = ""
lo: int | None = None
hi: int | None = None
- constraints:
- At least one of
loandhiMUST be stated. Each stated bound MUST be a plainint(boolis rejected), and two stated bounds MUST satisfylo <= hi.RangePattern(lo=k)andRangePattern(hi=k)express the one-sided relations formerly represented by separate pattern classes. - A specialization states both bounds and a non-empty
dim_var: the name of theDimVarthe range applies to. The lowering resolves it to a runtimeShapeOf(param, axis)by walking the enclosing function signature. - A two-sided interval is closed
[lo, hi]; a single-point range is[k, k]. match(value)succeeds for anintvalueviff every stated bound admits it. Thedim_varfield does not participate inmatch.- The pattern references a
DimVarby name only. The envelope of the named dim lives on theDimVar(name, lo, hi)itself (see types.md §4); theRangePatterncarries the per-variant sub-range. Envelope containment (pattern ⊆ DimVar envelope) is checked in signature context — by the@tilefoundry.funcvalidator and the HIR→TIR lowering — not byRangePattern.__post_init__.
4. Shared operation kinds¶
class BinaryKind(enum.Enum):
"""Enumerate pointwise binary operation kinds shared by HIR and TIR."""
ADD = "add"
SUB = "sub"
MUL = "mul"
DIV = "div"
FLOOR_DIV = "floor_div"
MOD = "mod"
MIN = "min"
MAX = "max"
EQ = "eq"
NE = "ne"
LT = "lt"
LE = "le"
GT = "gt"
GE = "ge"
AND = "and"
OR = "or"
class UnaryKind(enum.Enum):
"""Enumerate pointwise unary operation kinds shared by HIR and TIR."""
NEG = "neg"
ABS = "abs"
RSQRT = "rsqrt"
CAST = "cast"
NOT = "not"
RELU = "relu"
SQUARE = "square"
EXP = "exp"
LOG = "log"
CEIL = "ceil"
ROUND = "round"
EXP2 = "exp2"
LOG2 = "log2"
class ReduceKind(enum.Enum):
"""Enumerate reduction operation kinds shared by HIR and TIR."""
MEAN = "mean"
SUM = "sum"
ABS_MAX = "abs_max"
MAX = "max"
- constraints:
- These enums MUST be the single shared kind vocabulary carried by HIR and TIR generic operations; lowering MUST preserve a kind without remapping it.
- The member names and string values above are the complete built-in sets.