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 treesrc/func/:mod.rs(theFnEmit/FnAttrs/FrameLayoutdeclarations, the site predicates, the llt helpers and the free helpers) plus eleven submodules each carrying its ownimpl<'a> FnEmit<'a>block —core,frame,drive,ops,tile,window,conv,packed,trio,vec,bulk. Everysrc/func.rs:Xrow below has been rewritten to the submodule that actually holdsX; rows whose symbol set spans modules usesrc/func/{a,b}.rsform. 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_BYTESmoved toprofile.rs:heap_min_bytesat S31, and afunc.rs:1861line reference became the symbol it meant (func/ops.rs:update_in_place_source). Also S41:profile.rsgainsMachine::{Cpu, Gpu(Gpu)}+TargetProfile::gpu()+ theCUDA_ADAprofile — 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_KCandHEAP_MIN_BYTESno 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_neststays 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 behindEmitOpts::kc_nest), heap lowering (FnEmit::entry_alloc≥ 256 KB → themapal_rt_allocarena), and the llvm half of thetimebuiltin (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-Indexguard elision + theFnAttrsproof refinement (S20bounds_proofconsumer). Previously wave 3 — last-useUpdatememcpy 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-rthas no own component folder — its symbols live here (DESIGN §1 ownership).
| 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 |
| 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 |
| 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_ms — call 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_check → mapal_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} |
| 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} |
| 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.rs — kc_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} |
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.