Assignment, staging and JIT
These are language contracts and design extensions, not a promise of complete target support. Use the executable examples for current C++ spelling and implementation coverage for admitted lowering.
The device.jit(...) loop below is architectural pseudocode. Current capture and Runtime entry points are documented in the overview and Runtime integration.
C++ staging and JIT
One scoped builder, no builder prefixes
tile_kernel(lambda) retains a typed C++ definition. The JIT/capture entry,
not the wrapper’s construction, installs a thread-local scoped builder,
creates the lambda parameters in ABI order, invokes the lambda once for the
current concrete specialization, verifies stack discipline, and restores the
previous builder. Free operations find the builder from their operands or the
current scope. Constructing the wrapper emits no IR and reads no tensor data.
typed lambda + ordinary C++ configuration captures
|
concrete argument metadata + target
|
create signature parameters
|
execute lambda on the host
|
one concrete candidate TileIR
|
lower / JIT
The scoped builder is construction machinery, not the IR. TileIR owns all regions and values after construction.
Ordinary configuration creates variants
Configuration is ordinary host C++:
for (auto cfg : candidates) {
auto executable = device.jit(make_gemm(cfg), A, B, C);
benchmark(executable, A, B, C);
}
The runtime arguments provide resource metadata, not tensor contents to be captured as constants. Each uncached candidate executes the C++ definition and JITs its resulting TileIR; users need not introduce a staged template type or manually invoke a specialization API. The cache key contains:
target and feature set;
canonical candidate TileIR hash, including the effects of host configuration;
explicit argument specialization guards;
compiler and ABI revision.
The object representation of an arbitrary C++ lambda is never a cache key: padding, pointer captures, and object identity are not stable program semantics. The baseline can simply recapture each candidate and reuse code generation after hashing its canonical IR. Avoiding that frontend work is an optional optimization requiring an explicit stable definition/configuration identity.
For this concrete-specialization path, TensorView::extent returns ordinary
host integer metadata during capture. Shape/stride changes select a different
guarded variant; input data values do not. There is no SymInt type or
requirement to first capture a universal symbolic kernel. A future explicitly
runtime-varying extent path must use ordinary scalar parameters and preserve
the same signature/ABI separation.
A future symbolic family representation may avoid repeated frontend work for very large searches, but it must lower to the same concrete candidate IR. It is an optimization rather than a semantic prerequisite.
Target schedules
The portable kernel may leave execution binding, value distributions, atom selection, and realization variables open. A target schedule or solver fills them under explicit guards.
Expert constraints may pin only what is necessary:
bind logical execution prefix to target axes
require a particular atom family
place a Memory object in a compatible resource class
require alignment, vector width, swizzle, or pipeline engine
Constraints are IR objects with source locations, not arbitrary callbacks hidden from cache keys.