Skip to content

Latest commit

 

History

History
144 lines (129 loc) · 37.2 KB

File metadata and controls

144 lines (129 loc) · 37.2 KB

backend-llvm — implementation map

The functor DESIGN.md ("Categorical model") → code (ADR-0020). Rows updated WITH the model and the code, in the same change (FRAMEWORK §6.3).

Last: 2026-07-29 · S41 — the emitter's code root changed shape. src/func.rs (7,299 lines) is now the module tree src/func/: mod.rs (the FnEmit/FnAttrs/ FrameLayout declarations, the site predicates, the llt helpers and the free helpers) plus eleven submodules each carrying its own impl<'a> FnEmit<'a> block — core, frame, drive, ops, tile, window, conv, packed, trio, vec, bulk. Every src/func.rs:X row below has been rewritten to the submodule that actually holds X; rows whose symbol set spans modules use src/func/{a,b}.rs form. Behaviour is unchanged and proven so — 159/159 A/B emissions byte-identical. Two citations that were already stale were corrected in the same pass: HEAP_MIN_BYTES moved to profile.rs:heap_min_bytes at S31, and a func.rs:1861 line reference became the symbol it meant (func/ops.rs:update_in_place_source). Also S41: profile.rs gains Machine::{Cpu, Gpu(Gpu)} + TargetProfile::gpu() + the CUDA_ADA profile — see ../backend-nvptx/plans/plan-s41-the-nvptx-leg.md §2.

Previously: 2026-07-25 · S31 — TargetProfile: the six hardcoded machine constants become one named table plus arithmetic (src/profile.rs, plan-s31-target-profiles). tile_j_for, tile_nc_for, TILE_I, TILE_KC and HEAP_MIN_BYTES no longer exist as literals; the KC gate now closes by derivation on a machine whose L2 holds the panel (apple-m: kc = 4096 ≥ every K we run). It is a threshold, not an off-switch — past K=4096 it reopens, into a regime where the derivation disagrees with S30's measurement; kc_nest stays default-OFF. Default profile byte-identical (66 A/B emissions). Previously 2026-07-25 · S29 — the KC k-panel rung (finished, measured a 3× loss at 1024 f32, shipped default-OFF behind EmitOpts::kc_nest), heap lowering (FnEmit::entry_alloc ≥ 256 KB → the mapal_rt_alloc arena), and the llvm half of the time builtin (emit_time_ms) — plan-s29-openblas-levers + plan-time-builtin. Previously 2026-07-24 · S28 — FIR 1-D window rung (the rung-2 dual) + conv2d unrolled micro-kernel (plan-s28-shapes-ladder; the tile rows below). Previously 2026-07-24 · S27 — FMA contraction (product face) + BLAS rung 3 packing + per-width TJ + k-unroll/prefetch (plan-s27-fma-packing; the tile rows below). Previously 2026-07-23 · S26 — tile rung 2: TI register blocking + the fixed-TJ main/remainder split (plan-s26-register-blocking). Previously 2026-07-22 · wave 4 — proven-Index guard elision + the FnAttrs proof refinement (S20 bounds_proof consumer). Previously wave 3 — last-use Update memcpy elision (suggestion #2, plan-last-use §2 rule 4); 2026-07-21 · wave 2 — truthful fn attributes (suggestion #7) + by-reference array call args (suggestion #8, BL5 amendment). mapal-rt has no own component folder — its symbols live here (DESIGN §1 ownership).

Objects (Dat) → code

Object Form / shape Realized at State
TargetText 𝕊 — the emitted .ll translation unit (the String is the artifact) src/lib.rs:emit (the Ok(String) arm) built
EmitError sum ⊕ — Unsupported { feature, loc } (the ✋ cell) ⊕ Internal(String); renderer-free (C3) src/lib.rs:EmitError built
Module skeleton product assembled inline: RT_DECLS externs + Str globals + fn bodies + @main wrapper src/lib.rs:emit (via module::{RT_DECLS, collect_str_globals, emit_str_globals, emit_main_wrapper}) built
StrGlobal product { name, bytes } — one private unnamed_addr constant per Str object src/module.rs:StrGlobal (+ collect_str_globals/emit_str_globals/escape_bytes) built
FnCtx = FnEmit per-fn state: slots : ObjectId ⇀ 𝕊 (partial — erased objects have none), allocas/body buffers, next ordinal counter, borrowed fnames/strings/attrs; byref : (ObjectId × u32 × 𝕊) option (the fn's by-ref input: object, prefix k — capture count for a Map/Fold body, u32::MAX = every top-level Array for a Named fn — by-ref struct text) and ptr_resident : ObjectId ⇒ () (by-ref-array Projs whose slot is an alloca ptr, plus the input itself when a Named fn's whole input is one bare Array); lup : LastUsePlan (the fn's last_use_plan — dead/escape/carried facts, never re-derived) and elided_updates : ObjectId ⇒ () (Update targets sharing their dead source's slot — no alloca of their own); bp : BoundsProof (the fn's bounds_proof — the provably-in-bounds Index set backing the guard elision, computed once in FnEmit::new); (S29) kc_nest : bool (the opt-in KC rung, threaded from EmitOpts) and the heap pair heap_ok : bool (this fn may arena-place its entry blocks — set only for ir.entry()) / heap_used : bool (it did — the teardown debt) src/func/mod.rs:FnEmit built
TargetProfile (S31) Cmp config — { name, vec_bytes, vec_regs, acc_vecs_per_row, nc_tiles, l2_bytes, heap_min_bytes }: machine facts plus two policy ratios, nothing program-specific. ADR-0032 D4's placement knobs made data. Selected by name via EmitOpts::target (default generic), resolved once in emit_with_opts; an unknown name is an EmitError::Internal, never a silent fall back to the default. Every field is value-invariant — proven, not asserted, by differential_zen3_profile_is_value_invariant (zen3 re-tiles TJ 16→32 / TI 4→2 and still runs byte-equal to the interp oracle at -O0/-O2) src/profile.rs:TargetProfile (:20) · GENERIC (:103) / APPLE_M (:116) / ZEN3 (:128, untested on hardware) · resolve (:140); threaded as FnEmit::profile and carried per-site on TileCtx::{tile_i, tile_kc} built
FnAttrs product of two FuncId ⇒ bool maps — the clean set (no integer Div/Mod, no trap-capable Index/Update — a bounds_proof-proven Index cannot fire, so it does not count — no Print/token, transitively-clean callees) and the loopy set (LoopEnter or a call/body cycle, transitively callerward) src/func/mod.rs:FnAttrs built

Morphisms / passes (Trn) → code

Pass Signature Realized at State
emit &CategoryIr → Result<String, EmitError> (ADR-0020 §1; L3 capability gate on loop_plan(...).is_some() per merge, then skeleton + per-fn walk; deterministic fn names mapal_main/fn{ord}; FnAttrs::analyze pre-pass) src/lib.rs:emit built
FnAttrs::analyze &CategoryIr → FnAttrs — the two fixpoints (clean callerward over Call + Map/Fold body edges; loopy likewise, seeded with LoopEnter fns and self-reaching call cycles — the TrapCaps spirit, suggestions #7); the clean scan holds the fn's bounds_proof so a proven Index is skipped as not trap-capable (S20 wave 4; Update stays counted) src/func/mod.rs:FnAttrs::analyze built
emit_fn = FnEmit::emit (CategoryIr × FuncId) → 𝕊 — entry-block allocas (one per materialized object), %arg prologue store, the topo walk, the ret/ret void epilogue; define internal <sig> + truthful attributes (clean ⇒ readonly nounwind + willreturn when loop-free; a clean bare-ptr by-ref param ⇒ noalias nocapture readonly) src/func/drive.rs:FnEmit::emit (+ walk) built
emit_morphism per topo_order step — the §2 op table dispatch over per-object slots (the piecewise functor application) src/func/ops.rs:FnEmit::emit_morphism (+ helpers emit_arith/emit_compare/emit_call/emit_print/emit_index/emit_update/emit_map/emit_fold/emit_zip/emit_enumerate/emit_iota/emit_fill/emit_time_ms) built
emit_loop per canonical quartet — ADR-0016 guard-first CFG (entry/header/advance/exit/after) over the LoopPlan decide/advance cones src/loops.rs:emit_loop built

Supporting maps (Trn) → code

Item Signature / role Realized at
lower_ty Ty → Option<𝕊> — LLVM type text; None for erased (Unit/IoToken/Str, empty-residual product) src/ty.rs:lower_ty
lower_body_input_ty (Ty × u32) → Option<𝕊> — a Map/Fold body input with the first-k Array components lowered to ptr (by-ref captures, suggestion #6); residual arity and the erased_index remap are unchanged, k = 0 is lower_ty src/ty.rs:lower_body_input_ty (callers FnEmit::{emit, emit_map, emit_fold}; k via FnEmit::body_captures)
lower_named_input_ty Ty → Option<𝕊> — a Named fn's input with EVERY top-level Array (the input itself, or direct product components) lowered to ptr (by-ref call args, suggestion #8 — the capture lowering at k = u32::MAX; nested products-in-products stay by value); scalar-only inputs lower identically to lower_ty src/ty.rs:lower_named_input_ty (callers FnEmit::{emit, emit_call}, module::emit_main_wrapper)
erasure remap residual_arity/erased_index — component→surviving-index remap, derived on demand from the ty (deduce-don't-store, L4) src/ty.rs:{residual_arity, erased_index, residual_tys, component_tys}
slot/operand helpers load_whole (deep-copies through the pointer for a ptr-resident object; assembles the by-value whole of the by-ref input product for escaping uses)/load_component/component_ptr (GEPs the by-ref struct text for the fn input)/store_obj/scratch/field_store — the alloca-slot template (BL1) src/func/mod.rs:FnEmit::*
by-ref array operands array_operand_ptr — an Index/Update/Map/Fold/Zip/Enumerate/call-arg array operand's base address: the forwarded load ptr when the Pair feeder (or bare source) is ptr-resident, a load ptr from the field when the operand is a by-ref Array component of the fn input itself, (S21 WP3b) the feeder's own slot when the feeder is an Array (no staging roundtrip), the ordinary slot/component address otherwise; body_call_arg — the body call's (c₁…cₖ, rest…) scratch, Array-capture fields storing addresses (also emit_call's whole-argument template, every component a "capture") src/func/{core,ops}.rs:FnEmit::{array_operand_ptr, body_call_arg}
aggregate-move discipline (S21 WP3b) an array value NEVER moves as a first-class SSA aggregate: pointer_only_array_component (every out-edge of a Pair-built product reads component k as an address — per-op slot rules for Index/Update/Zip/Map/Fold/Call) ⇒ the staged field and its alloca type become ptr (lower_slot_ty) holding the source alloca's address; value-owed moves (Pair/Proj/Output/Phi/loop init→merge/back-route/exit-payload, escaping Proj targets) go through emit_memcpy (the null-GEP sizeof idiom; dst == src identity skipped — preserves the #2 in-place elision); array Phi = select over the two arm POINTERS + one memcpy src/func/{bulk,core}.rs:FnEmit::{pointer_only_array_component, lower_slot_ty, field_ptr, emit_memcpy, copy_obj, copy_component}
last-use elision update_in_place_source — rule 4's legality for one Update morphism (plan-last-use §2, suggestion #2): the source array object when the plan proves it dead_after the update (uses ranked decide < LoopExit < advance < LoopBack, ¬escapes, ¬carried) and it is not ptr-resident (borrowed caller memory — the explicit veto behind the plan's rule 2); emit_update then skips the llvm.memcpy and inserts the source's slot as the target's (the element store lands in place), the elided_updates pre-pass having minted no alloca for it. None ⇒ the fresh-alloca copy, byte-identical to before src/func/ops.rs:FnEmit::{update_in_place_source, emit_update}
Named call emit_call — top-level Array (components of the) argument by reference per lower_named_input_ty (single surviving array ⇒ the bare address; product ⇒ the scratch template; forwarded ptr-resident feeders), scalar-only arguments by value byte-identical to before src/func/ops.rs:FnEmit::emit_call
trap emit trap_if(cond, kind) — per-site inline trap block calling mapal_trap+unreachable (as-built S13: not one shared trap_bb) src/func/core.rs:FnEmit::trap_if
index guard load_index/guard_index — type-directed extension (u8 zext, signed sext) + range trap; emit_index(m, …) calls guard_index only when bp.proven(m) is false (S20 wave 4 — a proven Index's trap is dead, so the elision emits just the extension + GEP + load; unproven is byte-identical, and emit_update guards unconditionally) src/func/ops.rs:FnEmit::{load_index, guard_index, emit_index}
loop-driver hooks copy_obj/copy_component/load_route_component — init→merge, next→merge, exit-payload→exit, guard load src/func/mod.rs:FnEmit::*
heap lowering (S29) entry_alloc(name, llt, align) — the ONE seam every sizeable entry-block block goes through: the per-object slots (allocate_local_slots), the packed panel (packed_buffer, allocate_frame_packs), and emit_parallel's %Frame block (which runs the same heap_block test inline). Fixed small scratches (scratch, the KC a-panel pack) stay direct allocas — all far below the threshold: alloca below profile.heap_min_bytes = 256 KB on every shipped profile (S31: a TargetProfile field, was the HEAP_MIN_BYTES literal), call ptr @mapal_rt_alloc(i64 bytes, i64 align) at or above it — an alloca result and an arena block are both a ptr, so no getelementptr consumer changes. heap_block records the teardown debt (heap_used); heap_teardown emits the single call void @mapal_rt_free_all() before the entry fn's ret — past mapal_par_finish and past the return value's load, so plan-s29 composition rule 4 holds in both flavors from one emission point. heap_ok is set only for ir.entry() (the recorded ceiling: a big array local to a Named fn or a Map/Fold body keeps its alloca). Sizing is llt_bytes (an LLVM StructLayout walk over the closed ptr/float/double/iN/[n x T]/{…} grammar ty.rs emits, llt_align/llt_fields the helpers); anything unrecognized sizes to 0 and stays on the stack — the conservative direction src/func/core.rs:FnEmit::{entry_alloc, heap_block, heap_teardown} + {llt_bytes (:388), llt_align (:418), llt_fields (:437)}, TargetProfile::heap_min_bytes (src/profile.rs:41), FrameLayout::struct_llt (:201); decls src/module.rs:HEAP_DECLS, gated in src/lib.rs:emit_with_opts on the emitted text containing @mapal_rt_alloc (the call IS the requirement — no re-derived predicate to drift)
time emission (S29; S36 placement) emit_time_mscall double @mapal_time_ms() + store_obj. Under the parallel flavor the read is a pinned task (mapal-ir path_plan), so this text lands in that task's @task{id} body while the host spine emits the pin{id}_entries wait → mapal_par_checkmapal_par_run_pinned trio at the read's topo position (walk_filtered's pinned injection) — no emitter change was needed, the pinned-task machinery already existed for trap-capable calls. The source IoToken erases and the (IoToken, f64) target residual-lowers to the bare double (arity-1 residual IS its component, ty.rs:lower_ty), so the call result IS the target object's value and no pair materializes; the call's position in the block IS the ordering the token models. FnAttrs needs no new arm — a TimeMs-bearing fn owns token-typed objects, which the existing token rule already keeps attribute-free src/func/ops.rs:FnEmit::emit_time_ms (:2362), dispatched from emit_morphism's Operation::TimeMs arm (:2053); declared unconditionally in src/module.rs:RT_DECLS (NOT gated on perf_timing like PERF_DECLS)
@main wrapper closed-entry wrapper: void call, or scalar return printed through mapal-rt (BL8); the (open-entry) call argument's type is the entry fn's by-ref signature (lower_named_input_ty, suggestion #8 — array components ptr, nulled by zeroinitializer); print_call places zeroext after the type (as-built S13) src/module.rs:{emit_main_wrapper, print_call}

mapal-rt runtime symbols (DESIGN §1; own crate, rows owned here)

Symbol Signature Realized at
mapal_print_{i32,i64,u8,bool,f32,f64} extern "C" fn(v, newline)Display (== interp render) + flush; declared i8/i1 zeroext on the emitter side crates/mapal-rt/src/lib.rs (print_fn! macro → emit)
mapal_print_str unsafe extern "C" fn(*const u8, usize, newline)from_raw_parts a Str global (never data) crates/mapal-rt/src/lib.rs:mapal_print_str
mapal_trap extern "C" fn(u32) -> !0=div_zero/1=index_oob; stderr message, exit(101); declared noreturn on the emitter side (suggestion #7) crates/mapal-rt/src/lib.rs:mapal_trap
mapal_time_ms (S29) extern "C" fn() -> f64 — ms since TIME_EPOCH, one process-lifetime Instant (OnceLock) shared by every call, so two reads are non-decreasing and their difference is real elapsed ms; the same monotonic clock mapal_perf_begin/mapal_perf_end use crates/mapal-rt/src/lib.rs:{mapal_time_ms, TIME_EPOCH}
mapal_rt_alloc / mapal_rt_free_all (S29) extern "C" fn(i64 bytes, i64 align) -> *mut u8 / extern "C" fn() — the heap-lowering arena: one Mutex<Vec<(usize, Layout)>> registry over std::alloc (addresses as usize — a raw pointer is not Send and the pool shares the arena), blocks uninitialized exactly like the alloca they replace, allocation failure aborts (handle_alloc_error — the emitted program has no OOM path; mapal_trap's exit-101 is for language traps). free_all drains and deallocates. ponytail-marked ceiling: one global mutex + free-everything teardown is enough only because the emitter arena-places the entry fn's blocks alone — allocation happens a handful of times in the prologue, never in a hot loop; per-allocation lifetimes need mapal_rt_free(ptr) + an emitter-side last-use point crates/mapal-rt/src/lib.rs:{mapal_rt_alloc, mapal_rt_free_all, ARENA}

Test / harness → code

Item Realized at
golden .ll snapshots (10 examples + micro arith/update/two-loops + the last-use elision pins — update_inplace_carried_loop (the matmul4-class loop form emits NO llvm.memcpy call, suggestion #2) and update_memcpy_kept_when_not_dead (escape-via-Pair-field and by-ref ptr-resident sources keep the copy) + ADR-0027 capture map/fold/one-kernel-matmul — the matmul golden pins the by-ref ptr capture shape, suggestion #6, and the Named fn's { ptr, ptr, ptr } by-ref call shape, suggestion #8; micro_update pins the bare-ptr array input; fir pins the by-ref Named signature + forwarded-pointer Index + the S20 elision pins — proven_index_guard_elision (the matmul-cell-class body: ZERO icmp slt/sge for the proven affine index, the guard present for the unproven sibling) and proven_index_fn_clean_attrs (the proof-clean cell body readonly nounwind willreturn; the bare-ptr param noalias nocapture readonly); determinism; nested-loop Unsupported pin; exit-only-payload-once pin) tests/golden_ll.rs (+ tests/snapshots/)
compile-and-run differential (examples raw+rewritten; two sequential loops; traps exit-101; u8 ABI; ≥256-case closed testgen sweep raw+rewritten; matmul loop-driven Update; captures; computed exit payloads; by-ref call-arg escaping uses) tests/differential.rs (testgen via #[path] include of mapal-rewrite/tests/testgen)
sepia-at-N perf baseline (-O0/-O2 vs interp; ignored-by-default) tests/perf_baseline.rs
heap lowering pins (S29) — structural: the arena ABI declared, NO alloca %Frame, the frame one call ptr @mapal_rt_alloc(i64 800024, i64 8) block at LLVM's own sizeof(%Frame) (the size operand is pinned deliberately — an under-count is a silent heap overflow, not a loud failure), exactly ONE mapal_rt_free_all and it follows mapal_par_finish; the n=64 twin below the threshold gains not even a declaration (the negative control that keeps every other golden byte-identical). Compile-and-run: above/below the threshold, byte-equal to the interp oracle at -O0 AND -O2 (the fold reads every cell, so a short block corrupts rather than passes quietly) tests/golden_ll.rs:{heap_src (:1071), golden_heap_lowered_frame (:1099)} · tests/differential.rs:differential_heap_lowered_arrays (:2036)
time pins (S29; S36) — structural: the extern declared, exactly two mapal_time_ms calls in the module and one pinned task per read, each mapal_par_run_pinned preceded by a mapal_par_wait whose entry count is > 0 at t0 and strictly greater at t1 (the fence: t1 − t0 is the work written between the reads, generation excluded), at least one mapal_par_dep naming t0's task as the before argument (the after-edge: the bracketed work cannot start before the read), and the fsub after both reads (the host cone: a clock value's consumers never race the host's write). Compile-and-run: the bracket changes no answer (stdout prefix byte-equal to the untimed twin) and elapsed parses as a finite f64 ≥ 0 at -O0/-O2 under both the default pool and MAPAL_PAR=1 — no upper bound is ever asserted (a wall-clock bound is a flake, not a test) tests/golden_ll.rs:time_bracket_fences_the_tasks_it_brackets (:1218) · tests/differential.rs:differential_time_bracket (:2156)
arena unit pin (mapal-rt) — two distinct blocks at the requested alignment, EVERY byte written through (a short allocation corrupts the heap silently — exactly what the emitter's size arithmetic risks), then freed and the registry empty crates/mapal-rt/src/lib.rs (#[cfg(test)] arena_alloc_is_usable_and_freed)
mapal-rt render-parity table (4080.0, 5.375, -0.0, NaN, inf, u8 255, i64 extremes) crates/mapal-rt/src/lib.rs (#[cfg(test)] render_parity)
parallel orchestrator (S24) — plan gate + acyclic check + body-site map src/lib.rs:emit (path_plan_is_acyclic, parallel_body_sites, mark_body_closure)
parallel mapal_main (frame layout/GEPs, task registration, checkpoint/pinned injection, finish) src/func/{core,drive,frame}.rs:FnEmit::{emit_parallel,build_frame_layout,materialize_frame_slots,slot}; HostEmit/CheckpointEmit/PinnedEmit/FrameLayout
task fns (Split range loops, Seq chains/folds/pure loops) + host/task walk filter src/func/{core,drive}.rs:FnEmit::{emit_task,walk_filtered,bulk_bounds}; GuardFlavor
speculate-and-order guards (record + dummy-zero continue; watermarks; site topo) src/func/{core,ops}.rs:FnEmit::{record_trap,emit_watermark,task_site,emit_task_div} + task arms in emit_index/emit_update/emit_arith
checkpoint injection (earliest task-reading host glue; pre-LoopEnter hoist for effectful loops — S24 review find) src/func/mod.rs:checkpoint_injection, HostEmit::pre_loop, walk_filtered LoopEnter arm
packed wait-entry constants (task<<32)|threshold src/func/mod.rs:wait_global
scheduler runtime (pool, rank-seeded deques + stealing, help-first waits, trap flag CAS-min, watermarks, pinning, MAPAL_PAR, GRAIN=4096) crates/mapal-rt/src/lib.rs (mapal_par_begin/task/pin/dep/launch/wait/check/trap/watermark/run_pinned/finish, Pool, Run)
the deduced task DAG itself (paths, deps, ranks, transitive trap sites, thresholds, pinning, effectful-loop exclusion) crates/mapal-ir/src/algo.rs:CategoryIr::path_plan (+ fn_trap_capabilities, WaitEntry)
R-PAR live pins (big-N split parity; trap stdout-prefix order; env matrix 1/8/unset; run-twice) tests/differential.rs:differential_parallel_{bign,trap_order,env_matrix,run_twice}
parallel structural pins (frame/task/ckpt shapes; speculating fold body; watermark; pre-loop wait order) tests/golden_ll.rs:{golden_parallel_matmul_cap,parallel_scalar_guard_publishes_watermark,parallel_effectful_loop_waits_before_entry}
tile emission (S26 rung 2 + S27 rung 3 + S28 window/conv rungs + S29 KC rung) — the register micro-kernel at recognized sites (both flavors via bulk_bounds; per-cell op/operand/k-order exact): the gate site.rows > 1 && site.b.ci == 0 (the record's row-invariance cashed — no mapal-ir change) routes multi-row sites to TI=profile.tile_i() (=4 on every shipped profile bar zen3) register blocking — the i axis split into head boundary rows (TI=1, signed jw clip) / TI-blocked interior full-window rows (split loops, never masked) / tail rows (TI=1), the j axis a constant-TJ main body + one runtime-tj remainder tile per region, acc one flat [TILE_I*TJ x elem] entry-block scratch, per k: TI scalar a-loads + ONE b load per (k, lane) reused across the TI chains. S27: TJ is per-width (tile_j_for: f32→16, f64→8 — the f64 register-file fit); 2-D gated sites read b through a packed j-tile-major panel (packed[jt·(K·TJ) + k·TJ + lane], 64-aligned, remainder lanes zero-padded never-read; packing_site = the TI gate) — packed in the site-owning wrapper task before its nested mapal_par_begin(1) slice dispatch (parallel flavor; happens-before by construction), or inline before the nest (sequential + Seq-task sites — pack-per-iteration is the loop-carried-b correctness rule); the TI-blocked constant-TJ main body unrolls k ×2 (trailing single step on odd K; ascending per-cell k order exact) + llvm.prefetch of the next packed k-line; ungated 1-D sites (rows == 1 — FIR/attention-O) keep the rung-1/2 unpacked nest byte-stable. EmitOpts::contract (product face, default off): the tile-kernel chain emits fmul contract/fadd contract (float sites only) — single-rounding FMA license per-instruction (the LLVM≥14 driver-flag-does-not-retrofit fact, verified); the conformance face stays bit-exact. S28 (plan-s28-shapes-ladder): two rungs cash the record further — B (the 1-D window rung, the rung-2 DUAL): window1d_site (site.rows == 1 && site.b.ck == 1 && site.b.ksplit.is_none()) dispatches at the top of emit_tiled_map (before the rung-2 gate) to emit_tiled_map_blocked_1d: full blocks of TI·TJ lanes, never masked (jb + TI·TJ ≤ hi); acc [TI·TJ x elem]; per-subrow seed splat; k loop ×2-unrolled iff K % 2 == 0 (the trio shape); ONE scalar a load per k shared across the TI subrows' constant-TJ lane loops (a is the invariant read, b slides — shared/varying roles swapped vs rung 2: matmul shares b across rows, FIR shares a across lane-blocks); per-subrow stores; per-cell k-ascending. The [lo,hi) window needs no [0,C) clip (rows == 1 collapses the row loop — the task range IS the window); the sub-block remainder is the TI=1 emit_tile_j_split discipline (constant-TJ main + one runtime-tj tile); non-window 1-D sites keep the rung-1 nest byte-for-byte (the negative control). A3 (the conv micro-kernel — the k-split record constant-folded): conv_site (a.ksplit.is_none() && b.ksplit.is_some() && b.ck == 0 && a.clane == 0 && b.clane == 1) dispatches first to emit_tiled_map_conv/emit_tile_conv_tile (+ ConvTileCtx, the TileCtx sibling minus k_ctr): rung-1 row idiom (slice row range + signed per-row jw clip); per (row, j-tile) the (kq, kr) tap nest fully unrolled (kq in 0..K/div outer, kr in 0..div inner = k-ascending), per tap a constant-index a load and a b vector load at b_row + (cq·kq + cr·kr) + j0 + lane — the tap offset compile-time, zero sdiv/srem in the nest; constant-TJ main + one runtime-tj remainder; mul_a_first/add_acc_first/contract respected; TI=1 (row blocking a recorded ceiling). Composition rule 3 wired: the emit_map site filter retargeted — the untiled body-call fallback is kept ONLY for non-conv ksplit sites (`(a.ksplit.is_none() && b.ksplit.is_none())
emission options + compute timer (S25; S27 +packing/+contract; S29 +kc_nest) src/lib.rs:{EmitOpts,emit_with_opts} (tiling + packing default-on; contract default-OFF — the conformance/product face split; kc_nest default-OFF — a measured 3× loss at 1024 f32 on M4 Pro, kept because it was designed against box-scale traffic where it is unmeasured; bit-exact either way, so a pure performance tailor in ADR-0032's sense; emit = defaults); src/module.rs:{PERF_DECLS,PREFETCH_DECL,HEAP_DECLS}; mapal_perf_begin/end brackets in FnEmit::emit/emit_parallel_flow_main; example flags --perf/--no-tile/--no-pack/--contract (examples/emit.rskc_nest has no flag: it is API-level opt-in only, set by the KC tests; the S29 A/B was run by flipping the default in-session)
tile pins (re-pinned S27 — packed nest: pack loop + packed-base b-loads + per-width TJ + unroll + prefetch in the 2-D snapshots f32 AND f64; contract flags present iff contract=true; 1-D byte-stable through S27; tiled-vs-oracle + tiled-vs-untiled + packed-vs---no-pack parity at -O0/-O2; f64 TJ=8 main/remainder/unroll-tail paths covered; the Seq-task hole regression — loop-carried b packs per iteration inside the loop task, multi-path entry asserted; the fn-strip pin — a map body calling a small pure helper flattens and tiles. S28: tile_nest_shape_conv (structural, golden_tile_map_shape_conv: the [TJ] acc alloca; no per-cell body call; ZERO sdiv/srem — the taps constant-fold; 9 constant-index w loads + 9 tap FMAs per tile body; tap offsets 18..38 as constant adds; 11 constant-TJ lane loops on the main path; ONE runtime-tj select) and tile_nest_shape_1d re-pinned DELIBERATELY with structural assertions (the FIR nest becomes the TI·TJ=64 block loop + [TI·TJ] alloca + no row-loop udiv + per-subrow seed/stores + ×2-unrolled k body and single-k tail + ONE tj select); the 2-D matmul snapshots stay byte-identical (the regression control); differential gains the FIR N % (TI·TJ) ≠ 0 remainder + par-split-mid-block cases and conv2d at side 16 / 20 (j remainder) / 92 (mid-tile GRAIN splits)). S29 KC pins — all opt-in via EmitOpts{kc_nest: true}, so every pre-existing tile golden/differential is untouched (the byte-identity claim enforced, not asserted): golden_tile_map_shape_kc (structural + snapshot tile_nest_shape_kc: the jb min(jb0 + NC, C) clip, the store i64 128 kc-loop init, the last panel's min(kc + 128, K = 300) clip, seed splat ONLY in the peeled kc==0 sweep (12 subrow loops), 36 out GEPs = 2 spills + 1 reload per (i-block, j-tile), and the packed panel + ×2 k unroll + prefetch retained) + 5 differential cases over the remainder geometry (K % KC ≠ 0, a mid-panel case, C % NC ≠ 0, a MAPAL_PAR split, f64), each byte-equal to the untiled emission and the interp oracle at -O0/-O2 via the shared emit_kc_and_untiled tests/golden_ll.rs:{tile_nest_shape,tile_nest_shape_f64,tile_nest_shape_1d (re-pinned S28 + structural),tile_nest_shape_conv (S28, structural),untiled_map_shape,contract flag golden} · tests/differential.rs:{differential_tiled_matmul, differential_tiled_fir, differential_tiled_fir_remainder :806, differential_tiled_fir_split :857, differential_tiled_conv2d :965, differential_tiled_conv2d_remainder :985, differential_tiled_conv2d_split :1008, differential_tiled_matmul_r5_c20_k7, differential_tiled_matmul_r6_c32_k5, differential_tiled_matmul_r6_c20_k5_f64, differential_tiled_matmul_loop_carried_pack :1330, differential_tiled_matmul_via_helper_fn :628} (+ shared assert_tiled_parity) · S29 KC: tests/golden_ll.rs:golden_tile_map_shape_kc (:670) (snapshot tests/snapshots/golden_ll__tile_nest_shape_kc.snap) · tests/differential.rs:{emit_kc_and_untiled :952, differential_tiled_matmul_kc_32x32x200 :1435, differential_tiled_matmul_kc_middle_panel :1496, differential_tiled_matmul_kc_c540 :1559, differential_tiled_matmul_kc_split :1605, differential_tiled_matmul_kc_f64 :1664}

Notes / divergences

DESIGN as-built deltas (all marked (as-built S13) in DESIGN §1/§2/§4): per-site inline trap blocks instead of one shared trap_bb; guard_index emits the two-sided signed compare on the zext'd i64 for every index type (semantically equal to the uge-only u8 text); call-site zeroext after the type; perf top-N 65536 with the 262144 escape hatch.

BL1 amendment (2026-07-25, S29 — DESIGN BL9): the slot scheme is no longer "every materialized object gets an alloca" without qualification — in the ENTRY function an entry-block block of ≥ profile.heap_min_bytes (256 KB) is a mapal_rt_alloc arena block instead, released by one mapal_rt_free_all before that fn's ret. Every non-entry fn (Named, Map/Fold body) and every smaller block is unchanged, so no program that fits today moves a byte. Recorded ceiling: a big array local to a called fn still hits the stack — those run an unbounded number of times, so arena placement needs mapal_rt_free(ptr) + LastUsePlan-driven free points, which the emitter does not compute yet (plan-s29 Ceilings).

BL5 amendment (2026-07-21, suggestion #8): the internal ABI is no longer "aggregates by value" for arrays — top-level Array (components of) a Named fn's input travel as ptr (observably read-only parameters), products/scalars still by value; nested products-in-products stay by value (recorded limitation). internal linkage and the @main wrapper are unchanged.