DSL Protocols#
FlyDSL uses three structural protocols at compilation and storage boundaries:
JitArgumentdescribes how a Python value enters a compiled JIT function.DslTypeconverts DSL values to and from MLIR SSA values.Storabledefines a DSL type’s byte layout and typed memory access.
They are capabilities, not base classes. A type implements a protocol by providing its hooks, and may implement any combination of the three.
This page connects Python arguments, DSL values, and typed memory. The detailed rules for composites, and storage and allocation live in their respective pages.
JitArgument#
Hook |
Meaning |
|---|---|
|
MLIR argument types contributed to the function signature |
|
stable, hashable argument contribution to the compilation cache key |
|
ordered |
The IR types and final ABI slots must describe the same arguments in corresponding order, and that
order must also agree with DslType reconstruction. Cache signatures should include
compilation-relevant metadata such as type, dtype, rank, address space, alignment, and static
layout, but exclude run-time values and addresses.
Each C-ABI fill(argument, storage) callback writes the current Python argument into preallocated
ctypes storage. It must read each call’s value from argument, rather than capture that value
when the specification is built. __c_abi_spec__() must match the final ABI produced after the
types from __get_ir_types__() pass through MLIR lowering and llvm.emit_c_interface. This is not
necessarily a one-to-one mapping: one MLIR argument may lower to multiple packed ABI slots.
Implementing this hook therefore requires an understanding of the relevant MLIR-to-LLVM lowering and
the generated C interface; a mismatch in slot type or order passes incorrect data to the compiled
function.
JitArgumentRegistry.register(py_type, dsl_type=...) registers an adapter for a raw Python type.
The adapter defines the host contract; dsl_type defines the value visible while tracing. A value
implementing both JitArgument and DslType can be used directly without an adapter.
DslType#
Hook |
Meaning |
|---|---|
|
convert a DSL value into ordered |
|
rebuild the DSL value from those SSA values |
The two hooks form an ordered round trip. Reconstruction accepts replacement SSA values with the
same count, order, and types as extraction produces, such as a new function’s block arguments.
exemplar carries metadata not encoded by the MLIR type; for example, Vector uses it to retain
its logical shape and element type.
A zero-length result is valid and is used by Constexpr values with no run-time representation.
Storable#
Hook |
Meaning |
|---|---|
|
static byte size |
|
required byte alignment |
|
materialize a typed value from an address |
|
write a typed value to an address |
The free functions dsl_size_of, dsl_align_of, peek_from_ptr, and poke_into_ptr dispatch to
these hooks. Storage[T] and the allocators use the same protocol; see Storage and
Allocator.
Python-to-MLIR boundary#
A Python argument and its corresponding DSL value may have different types. For example, a host
tensor is represented by a JitArgument adapter at the Python boundary and by an fx.Tensor
argument inside the JIT function.
The protocols connect these representations:
JitArgumentRegistryassociates a Python type with its host adapter and destination DSL type. A value implementing bothJitArgumentandDslTypecan be used directly.__get_ir_types__()supplies the ordered MLIR parameter types.DslType.__construct_from_ir_values__()rebuilds the corresponding DSL value from the function’s block arguments, using the adapter as an exemplar when metadata is needed.The type sequence returned by
__get_ir_types__()must match the SSA value types expected by the correspondingDslType, in count, order, and MLIR type.At execution,
__c_abi_spec__()supplies the ABI slots and callbacks that fill them from the current Python argument. These slots must match the lowered C interface of those MLIR parameters.
Python calls @jit
│
└─ adapt ─────> JitArgument
│
compilation │ __cache_signature__ -> cache key
│ __get_ir_types__ -> JIT MLIR signature
│
v
JIT function arguments
│ DslType.__construct_from_ir_values__
v
DSL values in JIT body
│ call @kernel: trace its body
├─ get_ir_types --------------------> gpu.func signature
├─ __construct_from_ir_values__ ----> kernel DSL values
└─ .launch(): __extract_to_ir_values__ -> gpu.launch_func operands
execution │ __c_abi_spec__ / fill
v
packed C ABI -> lowered JIT function
MLIR lowering compiles the generated host func.func and device gpu.func into executable code. A
cache hit reuses the compiled artifact without tracing again; fill still reads the current
arguments on every call. Parameters annotated Constexpr[T] are supplied directly to tracing and
affect specialization, but contribute no run-time MLIR parameters or C-ABI slots.
JitArgument is used only at the Python-to-MLIR boundary. The jit-to-kernel boundary is already
inside MLIR construction and therefore uses DslType, not the host C ABI.
Protocol support#
Representative type |
|
|
|
|---|---|---|---|
supported scalar |
yes |
yes |
yes |
|
yes |
no |
no |
storable specialized |
yes |
no |
yes |
|
no |
yes |
no |
|
yes |
yes |
no |
|
yes |
no |
yes |
|
recursive |
recursive |
recursive |
|
no |
no |
recursive |
|
zero SSA values |
special JIT handling |
no |
For a struct, each protocol is supported when all non-Constexpr fields support it. The fields are
processed recursively in declaration order.
The table describes usable capabilities. All three protocols are @runtime_checkable, but
structural isinstance and issubclass checks confirm only that the hooks exist, including hooks
that raise for unsupported operations. Implementations remain responsible for correct ordering, MLIR
types, cache keys, and byte layout.