Skip to content

Latest commit

 

History

History
49 lines (40 loc) · 12.7 KB

File metadata and controls

49 lines (40 loc) · 12.7 KB

System suggestions (category-theory derived)

Roll-up of every components/<name>/suggestions.md, highest payoff first (ADR-0017). Each row cites the FRAMEWORK rule it applies; detail lives in the linked component file. Applying one = a normal plan → implement cycle; applied rows move to the session log (see changelog below).

Open / parked

# Component Rule (§) Change Status Detail
1 backends (all three) §4.4/§7.4 strategy 2-category Shared Backend contract + TargetText type, fixed by ADR before the first backend DONE (S12): ADR-0020 — contract-by-convention emit(&CategoryIr) -> Result<String, EmitError> + shared mapal-rt runtime; flagged for Sapir review ADR-0020
2 lower §5 one source of truth Generic dfs_cycles util for detect_cycles/find_cycle Deferred (YAGNI watch) — two call sites with genuinely different on-back-edge behavior; a third graph-cycle check triggers it lower
3 interp §5 one source of truth Confine the numeric-width dispatch to one seam Refuted on vet (S09), deferred — premise overstated: only num_lt/num_le share the 5-way shape; arith already seams via int_arith!/float_arith! (different bodies), as_int is integer-only. Revisit only if a width is ever added post-M1 (IN7) interp
4 interp §5 deduce-don't-store (perf) Thread in_scc/topo_order from eval_fn into run_loop/derive_plan Deferred — perf store without profile evidence (HANDOFF §7.2 step 6); matches the S08 optional-hardening item interp
5 cli §5 define each boundary once One declared Diagnostic target, one renderer Soft — revisit when mapal-cli is built (Session-06 audit) cli
6 lower §5 one source of truth Route emit_fanout's return-position no-value case through ChainCtx::RetValue (as seq/L1611 does, S11) instead of the generic L1306 fall-through Open (small) — found by the ADR-0019 WP2 review; pre-existing, deliberately left in-scope-minimal lower
7 rewrite §5 / §9.2 / §4.3 P4 headroom: precise DCE, constant dedup via replay channel, layer-2 naturality pass, generic-SCC replay Open (post-P4, S12) — all strictly R1-safe extensions of shipped conservative choices rewrite
8 backend-cuda §5 deduce-don't-store / §4.5 Law 1 Remaining headroom: region-v2 emission (#0 — the top item), by-value device products (#1), batched trap checks (#3), scalar forwarding (#4 — subsumed by regions), grid-stride/geometry (#5), pinned memory (#6), CDP (#7), nvcc -O3 row (#8), SoA/tiling (#9), parallel Fold (#10 — oracle ADR), NVRTC (#11), arena v1.1 (#18a), invariant hoisting (#16 — WP-D, next), minimal-emission residue (handle aliases · product-Inline braced literals · print token-product local) Open (S22) — S22 discharged: #15 wrap/unwrap chains (minimal emission WP-B/WP-C — mapal_ir::emission_plan driving DevEmit+FnEmit; d_fn3 = one return expression); S20/S21 discharged: captures, #17 (+S21 17b noted), #12, #13, #14/14b, #18 v1.0, #19a, #2 backend-cuda
9 backend-llvm §5 deduce-don't-store Remaining headroom: heap lowering (the last BL1 face — the 8 MB stack ceiling on the sepia-class alloca shape), dead enumerate-elem elimination, nested products-in-products by-ref (recorded limitation) Open (S21) — S21 discharged: #3 array-fill primitive (ADR-0029 iota/fill/widen — N≥512 benches unblocked) and WP3b first-class aggregate-move elimination (pointer-only staging + llvm.memcpy; matmul256 clang -O2 OOM → 0.08 s/57 MB). S20 discharged: #2 Update elision, #6 by-ref captures, #7 fn attrs, #8 by-ref call args backend-llvm
10 ir + backends §5 one-source-of-truth / §4.4 strategy block_plan — the backend-generic blocking schedule query (Sapir direction, S27 close, asked twice: "shouldn't it be backend generic?"). tile_plan already carries the backend-generic legality (bit-exact interleaving, affine reads, row-invariance); the blocking schedule — which dims split, panel walk order, residence/reuse structure — is today hand-rolled inside the llvm emitter (jt-outer panel residence, TI/TJ, k-unroll). That schedule is the same shape every backend instantiates against its own tier (llvm: L2/registers · cuda: smem/mma fragments · verilog: PE-array dims/BRAM); only the sizes are Loc facts. Extract mapal_ir::block_plan (schedule tree over the tile record, tier sizes as parameters) when cuda consumes tile_plan — the second consumer is the consolidation trigger (rule of three; extracting before it is premature abstraction §5); llvm's emitter then re-derives its nest from the query. Done-when: llvm + cuda both consume block_plan; the emitters hold only per-Loc constants + instruction selection Open (S27) — direction committed; gated on cuda tile_plan consumption (S28 agenda 3) backend-llvm

| 11 | backend-cuda + ir | §4.4 strategy 2-category / §5 one source of truth | The second consumer — cuda consumes tile_plan. Verified 2026-07-25: tile_plan has exactly one consumer (backends/llvm/{lib,func}.rs; zero hits in backends/cuda/src/). It landed S25; the last cuda session was S23, so every rung since (register blocking, FMA, packing, panel residence, k-split, window, conv, KC) has been written against a single Loc. Two consequences: the project's load-bearing claim ("the deduction transfers to every backend") is architectural assertion rather than measurement, and nothing tests the ADR-0032 genericity contract — a cache-hierarchy assumption can migrate into a "generic" query with no gate firing. Minimum leg: smem tiling on one already-recognized matmul site, backend-owned warp-shaped factors, existing differential duty as the gate; mma explicitly out of scope (precision face, ADR-0032 D1/D3). A negative result is a successful discharge — it names the missing fact | Open — sequencing decided by Sapir (2026-07-25): CPU to full advantage first, then GPU. The leg is therefore the named exit condition of the CPU phase, not a gate on the next rung. Interim guard (ADR-0033 D2): each CPU rung's plan doc records, in three lines, which record fields it consumes, its cuda realization named against the record (or cpu-local if none), and any machine fact the record does not carry. Blocking open question: the written bar for "full advantage" (ADR-0033 Q1) | ADR-0033 | | 12 | backends (all) + bench harness | §5 deduce-don't-store / ADR-0032 D4 | Placement constants searched, not set. The ladder is two things treated as one: geometry (which reads broadcast vs stride, which axes split, nest order) — deduced, exact, backend-independent, and proven portable across shapes (matmul → FIR → conv2d, S28, one affine rule); and constants (TILE_J=16, TI=4, TILE_KC=128, NC=TJ×32, unroll ×2, prefetch, GRAIN) — hand-set literals from manual sweeps (S26: "TI sweep 2/4/8 → 4"), which are facts about a cache hierarchy, not about the program. Local M4 Pro (NEON/14T) and the EPYC box (zen3/AVX2/61T) run the same numbers. The residual OpenBLAS gap is therefore not evidence the geometry is wrong. Fix: constants behind a per-Loc table (never emitter literals) + one generic tuner over the recorded facts (flow tune, offline, cached, deterministic builds). The tuner is also a high-volume emitter fuzzer, since every candidate is value-invariant and gated by the existing differential duty | Open (S30) — extends ADR-0032 D4 with how the tables get values. Best landed after #11 so the tuner is written against two Locs (same rule-of-three as #10). Subsumes backend-llvm suggestion #15 (adaptive GRAIN) as one knob among many | ADR-0034 | | 13 | ir + lower + interp + rewrite | §4.3 / ADR-0028 (same associativity machinery) | scan — the loop/fold middle class. S27b's LiftLoops rescues loops that are secretly map- or fold-shaped; the residue is not small and is not exotic. fib is neither (carried state, non-associative, intermediates are the output). The general shape between fold and "arbitrary loop" is prefix computation — and it covers IIR filters and recurrences, the direct sibling of the FIR window rung that won its table at S28, plus running normalizations, EMAs, online-softmax-shaped work. Written as loop, all of it is opaque to every deduced query: no site, no lane analysis, no reuse reading. Written as scan { acc, x -> … } with a declared combine, it has a known parallel geometry on every backend (log-depth tree: SIMD prefix + block scan on CPU, warp shuffle on CUDA, pipelined chain on FPGA) — a second geometry family beside tiling, which strengthens rather than qualifies the genericity thesis. Sequential semantics is the oracle definition; the tree realization is gated on ADR-0028's associativity recognizer and ADR-0032's reassociation contract (its first non-precision consumer) | Open (S30) — small and self-contained (one op, one interp arm, one lower builtin, sequential backend arms); needs no coproducts/modules/dynamic arrays, so it can land inside the CPU phase. Upstream question Sapir flagged open — whether surface loop survives at all once map/fold/scan exist — is not decided by this row | ADR-0036 | | 14 | runtime | §5 deduce-don't-store | Work carries an Arc<Run> — 2 atomic refcount ops per slice to re-state a fact the protocol already implies (the run outlives every item it queued: finish is help_until(remaining == 0)) | Open (S50) | runtime #1 | | 15 | runtime | §5 one source of truth / §4.5 Law 4 | take_any's miss path locks every lane in turn — no shared record of which lanes hold work, so each thread rediscovers it by polling all of them. Pool::pending is that record for the spin loop and the steal path does not use it | Open (S50) | runtime #2 | | 16 | runtime | §6.6 the doc is the spec | The six ARCHITECTURE §4 composition rules have no automated pin. Rule 1 (unlock before publish) was violated for all of P2 and surfaced only because P1 made the host fast enough to observe it | Open (S50) | runtime #3 | | 17 | backend-llvm | §6.6 undocumented exceptions rot | path_plan_is_acyclic silently demotes a cyclic plan to sequential emission — the failure mode that hid every chained matmul for nine sessions. Enforce the total-DAG contract or make the demotion loud; the ponytail: note at lib.rs:258 already says so | Open (S50) — S49 removed the sibling-merge cycle class, not the general one | runtime #4 | | 18 | runtime / backend-llvm | §4.2 placement / §6.3 reconcile in the same change | DispatchCost::dispatch_ns is a property of the RUNTIME, measured on a program, consumed by the EMITTER, with no mechanism keeping the two in agreement. It was correct at 30 µs and wrong four changes later, costing three cells 5–20%. Runtime self-calibration at pool creation would make it a measurement rather than a written-down number | Open (S50) | runtime #5 |

Applied (changelog)

Session 09 (2026-07-16 — see the session log):

  • lowerresolve_ty/TypeTable::resolve duplicate TyKind ⇀ Ty trees consolidated into tys.rs:resolve_tykind; the two callers pass only their resolve_named seam (§5).
  • ir — §5.1 typing-table golden oracle: validate.rs::typing_table_golden:: edge_type_ok_matches_design_5_1, 85 rows transcribed from DESIGN §5.1, test-only (two-realization independence preserved). All rows agree — no doc/code drift found.
  • syntaxLineIndex owned whole-source String copy → LineIndex<'a> borrow (§5 deduce-don't-store); line_col values unchanged.

System-wide reductions

None open. The Session-06 reduction audit (categorical-model.md §7) ran the §3 procedure over twelve clusters: consolidations already executed were ratified, and the three tempting cross-component merges (two SourceLocs, IrError/IrViolation, surface-vs-IR Ty) are justified twos — do not collapse them.