Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Interface Vtables and Dispatch

Abstract

MLIR interfaces let generic code ask semantic questions about a dialect object without knowing its concrete C++ class. In tileiras every concrete op, type, attribute, and dialect carries a sorted array of (TypeID, concept*) pairs — its InterfaceMap — and lookup is a 16-byte-pitch binary search keyed on the TypeID address documented in TypeID Sentinels and Anchors. The concept block is a small vtable whose first slot is always the concept's own destructor and whose remaining slots are the methods the interface defines; layout-type interfaces expose shape and coordinate-mapping callbacks, view types expose element type and memory space, copy-atom types expose value shape and copied bits, and MMA-atom types expose A/B/C types plus the verifier callback. This page documents the entry layouts, the binary-search lookup, and the registration shim that installs implementations during dialect load.

InterfaceMap Layout

Each object that participates in interface dispatch (op, type, attribute, dialect) carries one InterfaceMap: a flat sorted array of 16-byte entries — the same pitch the OperationName slot banks use — sorted ascending on the TypeID address.

typedef struct InterfaceEntry {        /* 16 B */
    /*+0x00*/ TypeID         *id;      /* interned TypeID sentinel — Idiom 1 or Idiom 2 */
    /*+0x08*/ InterfaceConcept *concept; /* small vtable; first slot is the concept's dtor */
} InterfaceEntry;

typedef struct InterfaceMap {
    /*+0x00*/ InterfaceEntry *entries;
    /*+0x08*/ uint32_t        size;     /* live count; entries are dense, no tombstones */
    /*+0x0C*/ uint32_t        cap;
} InterfaceMap;

No tombstones: interfaces are write-once after dialect load. A registration phase runs during addInterfaces<> and the resulting map is never mutated again for the life of the context. The sort key is the raw TypeID address, and binary search is well-defined because Idiom-1 sentinels are link-time constants and Idiom-2 qwords are Meyers-cached on first use — every address compared against is stable by the time lookup runs.

Concept Vtable Shape

An InterfaceConcept is a small vtable. Slot 0 is the concept's destructor (so the map can free the concept on context teardown); the remaining slots are the per-interface methods in declaration order. Slot counts vary by interface — copy-atom carries 4 methods, MMA-atom 5, layout-type 6 — but the prefix is fixed.

typedef struct InterfaceConcept {
    /*+0x00*/ void (*dtor)(InterfaceConcept *self);   /* concept teardown, always populated */
    /*+0x08*/ void (*method0)(/* iface-specific signature */);
    /*+0x10*/ void (*method1)(/* iface-specific signature */);
    /* ... */
} InterfaceConcept;

The method-pointer entries are per-implementer thunks generated at dialect-init time. They adapt the generic (void *concrete, ...) calling convention used by the dispatch helper to the concrete C++ member function on the implementing class. Dialect conversion's populateConvertToLLVMConversionPatterns and the per-type printAssembly callbacks both follow this shape.

Lookup Algorithm

Lookup is a stride-16 binary search over the entries array. On a miss it returns NULL, which every caller treats as "capability not supported":

InterfaceConcept *interface_map_lookup(const InterfaceMap *map, TypeID *id) {
    uint32_t lo = 0, hi = map->size;
    while (lo < hi) {
        uint32_t mid = lo + ((hi - lo) >> 1);
        TypeID  *k   = map->entries[mid].id;
        if (k == id) return map->entries[mid].concept;   /* hit — pointer-identity */
        if ((uintptr_t)k < (uintptr_t)id) lo = mid + 1;
        else                              hi = mid;
    }
    return NULL;                                          /* miss — capability absent */
}

The comparator is raw pointer order, not a structural property of the interface — correct because insert sorts by the same address comparison. Insert holds the invariant with a one-shot lower_bound plus an in-place memmove:

void interface_map_register(InterfaceMap *map, TypeID *id, InterfaceConcept *concept) {
    uint32_t pos = lower_bound_typeid(map, id);
    if (pos < map->size && map->entries[pos].id == id) {
        /* replace — dialects may rebind an interface */
        map->entries[pos].concept = concept;
        return;
    }
    if (map->size == map->cap) grow_entries(map);         /* arena-backed; doubles cap */
    memmove(&map->entries[pos + 1], &map->entries[pos],
            (map->size - pos) * sizeof(InterfaceEntry));
    map->entries[pos].id      = id;
    map->entries[pos].concept = concept;
    ++map->size;
}

Replace-in-place is rare but legal — a downstream dialect can override an interface installed by an upstream dialect. A target dialect, for example, may rebind ConvertToLLVMInterface for a base op-class that the standard dialect already registered.

Dispatch Helper

Every generic caller funnels through the same one-line dispatch helper to ask "does this object implement this interface, and if so, run method N":

InterfaceConcept *get_interface(void *concrete, TypeID *id) {
    InterfaceMap *map = object_interface_map(concrete);   /* per-class accessor */
    return interface_map_lookup(map, id);
}

/* Typical call site — generic code asks for a capability without knowing the class. */
unsigned get_num_warps(Operation *op) {
    InterfaceConcept *c = get_interface(op, &qword_AgentLikeOpInterface_TypeID);
    if (c == NULL) return 0;
    typedef unsigned (*GetNumWarpsFn)(Operation *);
    GetNumWarpsFn fn = (GetNumWarpsFn)((void **)c)[1];    /* slot 1 — first real method */
    return fn(op);
}

((void**)concept)[0] is always the concept destructor; ((void**)concept)[1] is the first declared method. A caller that knows the interface knows the slot index of the method it needs — there is no runtime name-to-slot resolution.

Dialect Interfaces

Dialect interfaces describe behavior owned by an entire dialect rather than a single operation or type.

InterfaceContract
Assembly interfaceProvides aliases and preferred SSA names for dialect types and attributes.
Inliner interfaceDecides whether operations, regions, and blocks may be inlined.
Convert-to-LLVM interfacePopulates conversion patterns for dialect lowering to LLVM.
void populate_llvm_patterns(Context *ctx, RewritePatternSet *patterns) {
    for (Dialect *dialect : loaded_dialects(ctx)) {
        ConvertToLLVMInterface *iface = dialect_interface(dialect, CONVERT_TO_LLVM);

        if (iface != NULL) {
            iface->populate_patterns(dialect, patterns);
        }
    }
}

Type Interfaces

Tileiras uses type interfaces to make layout and target-specific atom types composable.

InterfaceTypical implementersQuestions answered
Layout typecute.layout, composed layoutsShape, body, static-ness, coordinate mapping.
View typecute.ptr, cute.memrefElement type, memory space, rank, effective layout.
Pointer typepointer-like CuTe typesMemory space and alignment.
Iterator typedescriptor and iterator typesElement type, layout refinement, projection.
Copy atom typecopy atom descriptorsValue shape, layouts, copied bits, value type.
MMA atom typeSM-specific MMA atomsA/B/C types, shape, operand partitioning, verifier rules.
Printable typepublic textual typesStable type printer implementation.
LogicalResult verify_view_type(Type type) {
    ViewTypeInterface *view = dyn_cast_view_interface(type);

    if (view == NULL) {
        return failure("expected a view type");
    }
    if (view->rank(type) == 0) {
        return failure("view type must have at least one dimension");
    }

    return success();
}

Interfaces let verifiers stay declarative. A pass asks for "view rank" or "MMA atom shape" without knowing every concrete type class that implements the concept.

Operation Interfaces

Operation interfaces express control-flow, scheduling, and producer-consumer behaviour. Each is a concept vtable installed against per-op TypeIDs at op-class registration time.

InterfaceContract
Producer opNames the region that produces async pipeline data.
Region branch terminatorDescribes where a region terminator can transfer values.
Agent-like opExposes agent bodies, group size, and warp allocation.
Constant-like traitMarks an op as a constant for folding and canonicalization.

Marker traits are zero-method interfaces — a single-slot vtable carrying only the destructor, with "present" determined by interface_map_lookup returning non-NULL.

How to Recognize in a Binary

The dispatch helper is the cleanest fingerprint: a function that loads an InterfaceMap* from a per-class slot, runs a stride-16 binary search keyed on a sentinel address from one of the bands catalogued in TypeID Sentinels and Anchors, and returns either NULL or a small vtable pointer is an interface_map_lookup call.

Concrete fingerprints:

  • The qword_5B47028 (PrintableTypeInterface) and qword_5B44600 (LayoutTypeInterface) Meyers slots from the TypeID page are the most frequent keys passed to lookup; any function that loads one of those qwords and then performs a stride-16 search is dispatching against the cute / cute_nvgpu interface set.
  • The replace-in-place behaviour distinguishes interface maps from operation-name slot banks. Operation-name banks are write-once on dialect registration; interface maps occasionally see a pos < size && entries[pos].id == id replace. A function that does the equality probe and then conditionally rewrites entries[pos].concept is registering an override.
  • The concept dtor at slot 0 is the cleanest concept-vs-arbitrary-vtable distinguisher. A 5-slot or 6-slot vtable whose slot 0 is a small function ending in a free or arena-discard call, with the rest being type-dispatched method thunks, is an InterfaceConcept.

Verifier Use

A verifier checking an interface-bearing operand should name the missing capability, never the implementation class.

LogicalResult verify_copy_atom(Type atom) {
    CopyAtomTypeInterface *iface = dyn_cast_copy_atom(atom);

    if (iface == NULL) {
        return failure("expected a copy atom type");
    }

    if (iface->copy_bits(atom) == 0) {
        return failure("copy atom must move at least one bit");
    }

    return success();
}

This wording stays stable even if a later version adds new atom classes.

Consumers

Every generic pass in the binary that asks "does this object support capability X" routes through the dispatch helper above. Pattern application drivers in Pattern Vtables and Shapes consult ConvertToLLVMInterface per dialect during conversion-pattern population. Verifiers consult type-side interfaces such as LayoutTypeInterface and ViewTypeInterface per operand. The scheduler consults AgentLikeOpInterface and LoopLikeOpInterface to identify region-bearing producer/consumer boundaries. The diagnostic engine documented in Diagnostic ABI and Helpers does not depend on the interface map directly, but verifiers that emit diagnostics universally key their messages on the missing interface name rather than on a concrete class.

OpInterface Inventory

The binary exposes sixty-five distinct OpInterface typeinfo strings — every one of them paired with at least one ::Trait shim that registers a concrete implementer into the per-op InterfaceMap. The inventory below groups them by what the dispatcher uses them for; the right column points to the consumer that issues the lookup. None of these counts include the closely related TypeInterface and AttrInterface families, which use the same dispatch primitive but key on type and attribute headers respectively.

FamilyInterfacesPrimary Consumer
Control flowBranchOpInterface, RegionBranchOpInterface, RegionBranchTerminatorOpInterface, WeightedBranchOpInterface, LoopLikeOpInterface, SelectLikeOpInterfacescheduler region traversal and the dominance/CFG analyses
Symbol and callCallOpInterface, CallableOpInterface, SymbolOpInterface, SymbolUserOpInterface, FunctionOpInterface, AnyFunctionOpInterfacesymbol-table cache and the call-graph builder
Async pipelineAsyncOpInterface, AgentLikeOpInterface, ConsumerOpInterface, ProducerOpInterfaceproducer/consumer pipeline analyses in passes/tileas/async-pipeline-family.md
Memory effectsMemoryEffectOpInterface, MemoryConsistencyOpInterface, AllocationOpInterface, BufferizableOpInterface, BufferDeallocationOpInterface, CopyOpInterface, AliasAnalysisOpInterface, AccessGroupOpInterface, DereferenceableOpInterfacealias analysis, bufferization, and the verifier's effect collector
Tile and view shapingXformLayoutOpInterface, RelayoutOpInterface, ViewLikeOpInterface, ShapedDimOpInterface, OffsetSizeAndStrideOpInterface, IndexingMapOpInterface, ReifyRankedShapedTypeOpInterface, BlockStripedOpInterface, TilerOpInterfacelayout materialization and the tile-conversion driver
Subset and destination styleSubsetOpInterface, SubsetExtractionOpInterface, SubsetInsertionOpInterface, DestinationStyleOpInterface, DestructurableAccessorOpInterface, DestructurableAllocationOpInterfacethe bufferization pipeline and SROA-style promotion passes
Cast and type inferenceCastOpInterface, InferTypeOpInterface, RefineTypeOpInterface, FindPayloadReplacementOpInterfacetype refinement during conversion
Promotion (mem2reg style)PromotableOpInterface, PromotableAllocationOpInterface, PromotableMemOpInterface, SafeMemorySlotAccessOpInterfacethe mem2reg-equivalent promotion pass
VectorizationVectorTransferOpInterface, VectorUnrollOpInterface, MaskableOpInterface, MaskingOpInterface, ParallelCombiningOpInterfacevector dialect lowering
Affine memoryAffineReadOpInterface, AffineWriteOpInterfaceaffine analyses retained from upstream MLIR
Floating-point modesRoundingModeOpInterface, FPExceptionBehaviorOpInterfacethe FP-mode threader during lowering
TMA descriptorMakeTmaDescOpInterfacethe TMA descriptor materialization pass; the only nv_tile-specific OpInterface in this row
Conversion and printingConvertToLLVMOpInterface, BytecodeOpInterface, OpAsmOpInterface, OneToOneIntrinsicOpInterfaceLLVM lowering driver and the bytecode/textual printers
Bounds and verificationValueBoundsOpInterface, RuntimeVerifiableOpInterfaceinteger-bounds analysis and the runtime-check insertion pass

Two NVVM-side families deserve a separate mention because they straddle the boundary between op and dialect interfaces: BasicPtxBuilderInterface and PtxBuilderOpInterface together govern how NVVM ops emit inline PTX during lowering. Their ::Trait shims live in the NVVM dialect's interface map and are looked up by the lowering driver per op; the dispatcher table at the head of this page applies unchanged.

A reimplementation can ignore the upstream-MLIR breakdown of public versus internal interfaces: the binary collapses both into a single dispatch primitive, and the only invariant that matters is that every interface that appears in a registration call has exactly one concept-block layout known to every implementer. Adding an interface is a four-step change — declare the concept, register a TypeID sentinel, stamp the ::Trait shim onto every implementer, and document a consumer that runs the lookup — and the consumer must be added because an interface with no consumer wastes 16 bytes of InterfaceMap per op.

Cross-References

TypeID Sentinels and Anchors catalogues the sentinel addresses this map keys on. Operation Layout describes the operation header that owns the per-op InterfaceMap. Storage Uniquer and Context Impl documents the dialect-registration machinery that installs interface implementations on context load. The trait side of nv_tileas verification — closed family of twenty-three OpTrait::nv_tile mixins that run alongside these interfaces — is catalogued in nv_tileas Verifiers — OpTrait::nv_tile Inventory.