Skip to main content

fuel_graph/
runtime_fused.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Runtime fused-op registration — the Tier-2 sidecar
3//! (`docs/specs/runtime-fused-op-registration.md`).
4//!
5//! A runtime-registered (JIT-synthesized or import-time) fused op **is** its
6//! region: its identity is a runtime [`FusedOpId`], its recipe is the §3
7//! [`PatternNode`] region kept here, and its `decompose` is that region
8//! re-emitted as primitives — so the recipe principle (total / never-panic /
9//! primitive→self) holds for free, since [`OpTag`] is the functional-primitive
10//! vocabulary only. No kernel field: the kernel binding lives in fuel-dispatch's
11//! `FusedKernelRegistry` (Tier-1 extensible); this sidecar holds only the
12//! graph-side recipe + the optimizer rules built from it.
13//!
14//! v1 scope: **same-shape elementwise** regions (the synthesizer's increment-1
15//! epilogues). Interior shape inference for broadcast/reduction regions is a
16//! follow-up — a re-emitted node takes its first operand's shape/dtype, exact
17//! for type-preserving same-shape ops and rejected-at-registration otherwise.
18
19use std::collections::HashMap;
20use std::sync::RwLock;
21
22use fuel_kernel_seam_types::{OpAttrs, OpTag, PatternNode, matmul_roles};
23
24use crate::registry::{FusedOpId, FusedOpParams};
25use crate::{Graph, Node, NodeId, Op};
26
27/// A runtime-registered fused op's metadata (the graph-side recipe).
28#[derive(Clone, Debug)]
29pub struct RuntimeFusedOpEntry {
30    /// The allocated runtime id (`>= FusedOpId::RUNTIME_FUSED_BASE`).
31    pub id: FusedOpId,
32    /// A human/telemetry name (e.g. `"jit::relu_add::sm89::<hash>"`).
33    pub name: String,
34    /// The §3 region (the subgraph sink) — the op's primitive recipe.
35    pub region: PatternNode,
36}
37
38/// A registration failure — never a panic (build-time validation).
39#[derive(Clone, Debug, PartialEq)]
40pub enum RuntimeFusedError {
41    /// The region's bind indices don't form a contiguous `[0, n)` (the op's
42    /// external inputs).
43    NonContiguousBinds(Vec<u8>),
44    /// The region carries an op with no primitive re-emission (outside the v1
45    /// re-emit vocabulary) — it could not decompose, so we refuse to register
46    /// it (the totality guard).
47    UnRepresentable(OpTag),
48    /// The region contains a matcher-only node (`Any`/`SeeThrough`) — a
49    /// concrete region must be `Op`/`Bind` only.
50    NonConcreteRegion,
51    /// The runtime id space (`u16` above `RUNTIME_FUSED_BASE`) is exhausted.
52    IdSpaceExhausted,
53    /// A shape-relative attr (D2) that can never resolve at ANY shape — a
54    /// STRUCTURAL authoring error caught at registration: a rel field and its
55    /// concrete sibling both set, a bind reference outside the region's bind
56    /// space, `axis_last` on an axis-less tag, or a `Param` reference (no
57    /// param threading until C-4). Value-dependent declines (a `Negative` or
58    /// symbolic-extent result at some particular shape) do NOT reject
59    /// registration — they surface at emit time as a decompose fixpoint (G2).
60    InvalidRelAttrs { tag: OpTag, error: RelAttrError },
61}
62
63static RUNTIME_FUSED_OPS: RwLock<Vec<RuntimeFusedOpEntry>> = RwLock::new(Vec::new());
64
65/// The recipe-identity index for runtime-registered ops: base-map content
66/// hash ([`crate::opt::base_map_hash`]) → the [`FusedOpId`] that first
67/// registered a region hashing to it. A **sibling** to `RUNTIME_FUSED_OPS`,
68/// not a reuse of any index on the STATIC catalog (`FusedOpRegistry`, an
69/// `OnceLock`-frozen struct built at process startup for build-time-known
70/// ids `1..RUNTIME_FUSED_BASE`); runtime ops never populate a
71/// `FusedOpRegistry` instance at all, they live in this module's own
72/// `RUNTIME_FUSED_OPS` global with the disjoint `RUNTIME_FUSED_BASE..` id
73/// space. This index is the
74/// natural home for runtime-region dedup: same lifetime/global-ness as
75/// `RUNTIME_FUSED_OPS`, cleared in the same breath by
76/// `clear_runtime_fused_for_tests`.
77///
78/// `HashMap::new()` isn't `const`, so this can't be a plain
79/// `static … : RwLock<HashMap<..>> = RwLock::new(HashMap::new())` the way
80/// `RUNTIME_FUSED_OPS` is a plain `RwLock::new(Vec::new())` — `Vec::new()`
81/// is `const`, `HashMap::new()` is not. `OnceLock` lazy-inits it instead
82/// (same pattern as `registry.rs`'s `static REGISTRY: OnceLock<..>` and the
83/// per-function `OnceLock` CPU-device singletons in `opt.rs`/`grad.rs`).
84fn hash_index() -> &'static RwLock<HashMap<u64, FusedOpId>> {
85    static IDX: std::sync::OnceLock<RwLock<HashMap<u64, FusedOpId>>> = std::sync::OnceLock::new();
86    IDX.get_or_init(|| RwLock::new(HashMap::new()))
87}
88
89/// Push `arity` uniform placeholder leaves (`Op::Const`, F32 `[1]`, no
90/// storage) onto `g` and return their ids. Uniform + storage-free is
91/// load-bearing: two independently-built graphs' leaves must hash
92/// IDENTICALLY under [`crate::opt::base_map_hash`] (which folds a const's
93/// shape/dtype and silently no-ops on an unpopulated storage slot) for the
94/// dedup comparison to be meaningful. Mirrors
95/// `fuel_dispatch::jit_ingest::push_placeholder_leaves` — that crate
96/// depends on this one (not the other way around), so the few-line helper
97/// is duplicated here rather than shared.
98fn push_placeholder_leaves(graph: &mut Graph, arity: usize) -> Vec<NodeId> {
99    (0..arity)
100        .map(|_| {
101            graph.push(Node {
102                op: Op::Const,
103                inputs: vec![],
104                shape: fuel_ir::Shape::from_dims(&[1]),
105                dtype: fuel_ir::DType::F32,
106            })
107        })
108        .collect()
109}
110
111/// `region`'s structural-identity hash: emit it onto placeholder leaves
112/// (via [`emit_region`]), lower to the primitive base map
113/// ([`crate::opt::lower_to_base_map`]), hash the result
114/// ([`crate::opt::base_map_hash`]). `None` on any structural failure (a
115/// poisoned lock, a rel-attr resolution decline at the placeholder shapes,
116/// or an empty lowering result) — the caller
117/// (`register_runtime_fused`) treats `None` as "hash unavailable" and skips
118/// dedup for this registration, never blocking it.
119///
120/// Every caller in this module runs this AFTER `validate_representable`
121/// already passed for the same region, so `emit_region`'s own panic risks
122/// (an unrepresentable `OpTag`, a `Bind` index out of range) are already
123/// ruled out here — `register_runtime_fused` still wraps the call in
124/// `catch_unwind` as the never-panic contract's last-resort guard for
125/// anything this doesn't anticipate.
126fn region_base_map_hash(region: &PatternNode) -> Option<u64> {
127    let n_binds = region.bind_indices().len();
128    let scalars = vec![0.0; count_scalar_slots(region)];
129    let graph: crate::SharedGraph = std::sync::Arc::new(RwLock::new(Graph::new()));
130    let sink = {
131        let mut g = graph.write().ok()?;
132        let inputs = push_placeholder_leaves(&mut g, n_binds);
133        // Fallible entry: a rel-attr region that declines at the rank-1 `[1]`
134        // placeholder shapes yields `None` — "hash unavailable", dedup skipped
135        // for this registration (allocate-fresh), never a panic.
136        try_emit_region(&mut g, region, &inputs, &scalars).ok()?
137    };
138    let roots = crate::opt::lower_to_base_map(&graph, &[sink]);
139    let root = *roots.first()?;
140    let g = graph.read().ok()?;
141    Some(crate::opt::base_map_hash(&g, root))
142}
143
144/// Register a runtime fused op for `region`, returning its runtime
145/// [`FusedOpId`]. Validates **before** allocating that the region's bind
146/// indices form the op's input list and that every op re-emits to
147/// primitives (totality) — a non-decomposable region is rejected, never
148/// registered.
149///
150/// **Dedup (recipe identity):** a region that is structurally identical
151/// (same [`crate::opt::base_map_hash`] over its primitive lowering) to an
152/// already-registered region resolves to the EXISTING [`FusedOpId`] instead
153/// of minting a duplicate — two calls with the same shape but different
154/// `name`s return the same id, and only the first call's `name`/region is
155/// kept in `RUNTIME_FUSED_OPS`. Never-panic: hashing runs inside
156/// `catch_unwind`; any failure (a poisoned lock, an unanticipated panic) is
157/// treated as "hash unavailable" and simply skips the dedup check —
158/// registration always proceeds to today's allocate-fresh path either way.
159pub fn register_runtime_fused(
160    name: impl Into<String>,
161    region: PatternNode,
162) -> Result<FusedOpId, RuntimeFusedError> {
163    let name = name.into();
164    validate_recipe(&region)?;
165
166    let hash = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
167        region_base_map_hash(&region)
168    }))
169    .unwrap_or(None);
170    if hash.is_none() {
171        eprintln!(
172            "register_runtime_fused: base-map hash unavailable for {name:?}; \
173             registering without dedup (allocate-fresh fallback)"
174        );
175    }
176
177    // Hold the hash index's write lock across the whole check-then-insert
178    // sequence below (not read-then-separately-write) so two concurrent
179    // registrations of the same NEW region can't both miss the lookup and
180    // each mint their own id: the second caller blocks on this lock and,
181    // once it acquires it, observes the first caller's insert.
182    let mut idx = hash_index().write().unwrap();
183    if let Some(h) = hash
184        && let Some(&existing) = idx.get(&h)
185    {
186        return Ok(existing);
187    }
188
189    // The Vec length under the write lock is the allocator: id = BASE + index,
190    // so the index is always `id - BASE` with no allocate/push race.
191    let mut w = RUNTIME_FUSED_OPS.write().unwrap();
192    let raw = FusedOpId::RUNTIME_FUSED_BASE as usize + w.len();
193    if raw > u16::MAX as usize {
194        return Err(RuntimeFusedError::IdSpaceExhausted);
195    }
196    let id = FusedOpId(raw as u16);
197    w.push(RuntimeFusedOpEntry { id, name, region });
198    drop(w);
199
200    if let Some(h) = hash {
201        idx.insert(h, id);
202    }
203
204    Ok(id)
205}
206
207/// The region (recipe) for a runtime fused op, or `None` if `id` is not a
208/// registered runtime op.
209pub fn runtime_region(id: FusedOpId) -> Option<PatternNode> {
210    if !id.is_runtime() {
211        return None;
212    }
213    let idx = (id.0 - FusedOpId::RUNTIME_FUSED_BASE) as usize;
214    RUNTIME_FUSED_OPS
215        .read()
216        .unwrap()
217        .get(idx)
218        .map(|e| e.region.clone())
219}
220
221/// A runtime op's name (telemetry / `op_short_name` routing).
222pub fn runtime_name(id: FusedOpId) -> Option<String> {
223    if !id.is_runtime() {
224        return None;
225    }
226    let idx = (id.0 - FusedOpId::RUNTIME_FUSED_BASE) as usize;
227    RUNTIME_FUSED_OPS
228        .read()
229        .unwrap()
230        .get(idx)
231        .map(|e| e.name.clone())
232}
233
234/// All registered runtime ops — the optimizer iterates this to build a fusion
235/// rule + a lowering rule per runtime op (`RuleRegistry::default_rules` /
236/// `lowering_only`).
237pub fn runtime_entries() -> Vec<RuntimeFusedOpEntry> {
238    RUNTIME_FUSED_OPS.read().unwrap().clone()
239}
240
241/// **TEST-ONLY.** Clear the metadata sidecar AND the recipe-identity
242/// `hash_index` in the same breath. Because the Vec length *is* the id
243/// allocator (`id = BASE + index`), clearing restarts allocation — any
244/// sidecar keyed by prior runtime ids MUST be cleared alongside it or a
245/// reused id resolves stale data. This was already true for
246/// `fuel_dispatch::runtime_fused_kernels::clear_runtime_fused_for_tests`'s
247/// kernel sidecar (call that one, not this, from dispatch-level tests) and
248/// is now ALSO true for `hash_index`: leaving a stale `hash → old_id`
249/// entry after a clear would let a later registration's dedup lookup
250/// return an id that no longer names the region it was hashed from (the
251/// slot at that index now holds whatever the NEXT registration after the
252/// clear pushed there). Adopting tests share one process, so callers must
253/// also serialize with any other adopting test (dd-shapes coordination,
254/// 2026-07-08: the hook alone races). `#[doc(hidden)] pub` rather than
255/// `#[cfg(test)]` because adopting tests live in downstream crates, which
256/// compile this crate without `cfg(test)`.
257#[doc(hidden)]
258pub fn clear_runtime_fused_for_tests() {
259    RUNTIME_FUSED_OPS.write().unwrap().clear();
260    hash_index().write().unwrap().clear();
261}
262
263// ---- the region → primitive re-emit (the runtime op's `decompose`) ---------
264
265/// Project a region [`OpTag`] (+ its [`OpAttrs`]) back to a primitive [`Op`].
266/// The inverse of `jit::op_to_tag`, over the **full first-order re-emit
267/// vocabulary** (Convergence Increment A): every non-basis-gap, non-`Fused`
268/// op — elementwise, comparison, `Where`, `Cast`, shape/layout
269/// (Transpose/Permute/Reshape/BroadcastTo/(Un)squeeze/Slice/Concat/Flip/Roll/
270/// Pad/Triu/Tril), reductions (SumDim/MaxDim/MeanDim/ReduceSumTo/ReduceMaxTo/
271/// CumSum/SumAll/MaxAll/MinAll/MeanAll), `MatMul`, `Iota`, and indexing (IndexSelect/
272/// Gather/IndexAdd/ScatterAdd) — plus the **`Op::Scan` multi-output structural
273/// terminal** (Increment C, B1): `Scan` (params ride the `scan_*` carriers, body
274/// sub-graph rides the operands), its `ScanPlaceholder` body holes, and the
275/// `View` slot projection (`view_slot`) that reads the scan's `output_views`
276/// bundle at emit time — and the scalar-carrying `MaskedFill` (Increment C
277/// carriers, A2 — fill value on `attrs.scalars[0]`, dtype on `cast_dtype` when
278/// present else a provisional F32 that `emit` re-resolves to operand[0]'s dtype)
279/// and `PowI` (A3 — the i32 exponent on `scalars[0]`). Structural params are
280/// decoded from the (extended) [`OpAttrs`]. Returns `None` (an honest miss,
281/// rejected at registration) for ops with no first-order re-emission: `Clamp`
282/// (no two-scalar carrier yet), fused/basis-gap tags, and any tag whose required
283/// attrs are unset (e.g. `Iota` with no `target_shape`, `Scan` with no
284/// `scan_bound`, `View` with no `view_slot`, `PowI` with no exponent, or
285/// `MaskedFill` with no fill value / a dtype `Scalar` cannot represent).
286fn tag_to_op(tag: OpTag, attrs: &OpAttrs) -> Option<Op> {
287    use OpTag as T;
288    use fuel_ir::DType;
289    use std::str::FromStr;
290    Some(match tag {
291        T::Add => Op::Add,
292        T::Sub => Op::Sub,
293        T::Mul => Op::Mul,
294        T::Div => Op::Div,
295        T::Maximum => Op::Maximum,
296        T::Minimum => Op::Minimum,
297        T::Pow => Op::Pow,
298        T::Rem => Op::Rem,
299        T::Neg => Op::Neg,
300        T::Abs => Op::Abs,
301        T::Sqr => Op::Sqr,
302        T::Sqrt => Op::Sqrt,
303        T::Rsqrt => Op::Rsqrt,
304        T::Recip => Op::Recip,
305        T::Exp => Op::Exp,
306        T::Log => Op::Log,
307        T::Sin => Op::Sin,
308        T::Cos => Op::Cos,
309        T::Tanh => Op::Tanh,
310        T::Sigmoid => Op::Sigmoid,
311        T::Silu => Op::Silu,
312        T::GeluTanh => Op::GeluTanh,
313        T::Gelu => Op::Gelu,
314        T::Relu => Op::Relu,
315        T::Erf => Op::Erf,
316        T::Step => Op::Step,
317        T::Floor => Op::Floor,
318        T::Ceil => Op::Ceil,
319        T::Round => Op::Round,
320        T::Sign => Op::Sign,
321        // Scalar-param ops: the value rides `attrs.scalars` (the slot snapshot;
322        // live-value substitution via the `extract:` path is a follow-up).
323        T::AddScalar => Op::AddScalar(*attrs.scalars.first()?),
324        T::MulScalar => Op::MulScalar(*attrs.scalars.first()?),
325        // PowI (Increment C carriers, A3): the i32 exponent rides
326        // `attrs.scalars[0]` as an f64 — an EXACT round-trip for every i32
327        // (`|n| < 2^53`), reconstructed via `as i32`. This is the same carrier
328        // the §6.19 wire already commits to (the `to_canonical_bytes` PowI arm
329        // serializes `scalars`) and mirrors `MaskedFill`'s baked-scalar posture:
330        // the exponent is a BAKED pattern constant, NOT an open cursor slot
331        // (`scalar_slot_arity(PowI) == 0`), so the recipe author supplies it and
332        // the emitter never draws it from the params projection. An unset value
333        // is an honest miss (`None`), never a defaulted `PowI(0)`.
334        T::PowI => Op::PowI(*attrs.scalars.first()? as i32),
335
336        // --- Convergence Increment A: the full first-order set ---
337        // Comparison (dtype→U8 handled by primitive_shape, not here).
338        T::Equal => Op::Equal,
339        T::Ne => Op::Ne,
340        T::Lt => Op::Lt,
341        T::Le => Op::Le,
342        T::Gt => Op::Gt,
343        T::Ge => Op::Ge,
344        // Ternary select.
345        T::Where => Op::Where,
346        // Dtype-changing: target dtype rides `cast_dtype` (the stable name).
347        T::Cast => Op::Cast(DType::from_str(attrs.cast_dtype.as_deref()?).ok()?),
348        // MatMul: the LOCKED role-vector contraction cell (§5/D5). Empty roles
349        // = the rank-polymorphic recipe form → implicit-accept (unchanged from
350        // today; recipes keep matmul implicit). Explicit roles must match the
351        // canonical cell EXACTLY — same-rank ≥ 2, leading Batch, lhs=[..,FreeM,
352        // ContractedK], rhs=[..,ContractedK,FreeN] — checked by role POSITION,
353        // not extent (so GQA-divisible batch stays all-Batch). Any other config
354        // (transposed / multi-ContractedK / FreeN-before-K / rank mismatch) is a
355        // SURFACED honest miss (`None`, rejected at registration), never a crash.
356        T::MatMul => {
357            if attrs.lhs_roles.is_empty() && attrs.rhs_roles.is_empty() {
358                Op::MatMul
359            } else {
360                let (canon_lhs, canon_rhs) =
361                    matmul_roles(attrs.lhs_roles.len(), attrs.rhs_roles.len());
362                if attrs.lhs_roles.len() == attrs.rhs_roles.len()
363                    && attrs.lhs_roles.len() >= 2
364                    && attrs.lhs_roles == canon_lhs
365                    && attrs.rhs_roles == canon_rhs
366                {
367                    Op::MatMul
368                } else {
369                    return None;
370                }
371            }
372        }
373        T::LogSoftmaxLastDim => Op::LogSoftmaxLastDim,
374        // Shape / layout.
375        T::Transpose => Op::Transpose,
376        T::Permute => Op::Permute(attrs.perm.iter().map(|&x| x as usize).collect()),
377        T::Reshape => Op::Reshape(shape_from_attr(attrs)?),
378        T::BroadcastTo => Op::BroadcastTo(shape_from_attr(attrs)?),
379        T::ReduceSumTo => Op::ReduceSumTo(shape_from_attr(attrs)?),
380        T::ReduceMaxTo => Op::ReduceMaxTo(shape_from_attr(attrs)?),
381        T::Unsqueeze => Op::Unsqueeze {
382            dim: *attrs.dims.first()? as usize,
383        },
384        T::Squeeze => Op::Squeeze {
385            dim: *attrs.dims.first()? as usize,
386        },
387        T::Slice => Op::Slice {
388            dim: attrs.axis? as usize,
389            start: attrs.slice_start? as usize,
390            len: attrs.slice_len? as usize,
391        },
392        T::Concat => Op::Concat {
393            dim: attrs.axis? as usize,
394        },
395        T::Flip => Op::Flip {
396            dim: attrs.axis? as usize,
397        },
398        T::Roll => Op::Roll {
399            dim: attrs.axis? as usize,
400            shift: attrs.roll_shift?,
401        },
402        T::Pad => Op::Pad {
403            padding: attrs
404                .pad_amounts
405                .iter()
406                .map(|&(b, e)| (b as usize, e as usize))
407                .collect(),
408            mode: match attrs.pad_mode? {
409                0 => crate::PadMode::Constant,
410                1 => crate::PadMode::Reflect,
411                2 => crate::PadMode::Replicate,
412                _ => return None,
413            },
414            value: attrs.pad_value.unwrap_or(0.0),
415        },
416        T::Triu => Op::Triu {
417            diagonal: attrs.axis?,
418        },
419        T::Tril => Op::Tril {
420            diagonal: attrs.axis?,
421        },
422        // Reductions (dim rides `axis`; keepdim reductions ride `target_shape`).
423        T::SumDim => Op::SumDim(attrs.axis? as usize),
424        T::MaxDim => Op::MaxDim(attrs.axis? as usize),
425        T::MeanDim => Op::MeanDim(attrs.axis? as usize),
426        T::SumAll => Op::SumAll,
427        T::MaxAll => Op::MaxAll,
428        T::MinAll => Op::MinAll,
429        T::MeanAll => Op::MeanAll,
430        T::CumSum => Op::CumSum {
431            dim: attrs.axis? as usize,
432        },
433        // Value source leaf (len rides `target_shape` as a 1-element shape).
434        T::Iota => Op::Iota {
435            len: *attrs.target_shape.first()? as usize,
436        },
437        // Indexing (dim rides `axis`).
438        T::IndexSelect => Op::IndexSelect {
439            dim: attrs.axis? as usize,
440        },
441        T::Gather => Op::Gather {
442            dim: attrs.axis? as usize,
443        },
444        T::IndexAdd => Op::IndexAdd {
445            dim: attrs.axis? as usize,
446        },
447        T::ScatterAdd => Op::ScatterAdd {
448            dim: attrs.axis? as usize,
449        },
450
451        // --- Op::Scan structural re-emit (Increment C, B1) ---
452        // A scan's body sub-graph rides the node's operands (the trailing
453        // inputs); this arm reconstructs only the `Op::Scan` params. The body
454        // holes re-emit through the `ScanPlaceholder` arm below. `Op::Scan`
455        // stays a base-map terminal (no native kernel, no `LoweringRule`) — the
456        // recipe just re-emits it so a decompose can round-trip through data.
457        T::Scan => Op::Scan {
458            // GAP-303: `?` (an honest miss) rather than `unwrap_or(1)`. A recipe
459            // that does not state its carry count is not a single-carry scan --
460            // it is a recipe this vocabulary cannot reconstruct, and defaulting
461            // would silently rebuild a DIFFERENT graph.
462            n_carries: attrs.scan_n_carries? as usize,
463            n_xs: attrs.scan_n_xs? as usize,
464            bound: attrs.scan_bound? as usize,
465            emit: match attrs.scan_emit? {
466                0 => crate::ScanEmit::All,
467                1 => crate::ScanEmit::Final,
468                _ => return None,
469            },
470            // Phase-1 `ScanPredicate` is a marker (the predicate DAG rides the
471            // trailing `pred_exit` operand); `Some(true)` re-emits the marker.
472            early_exit: if attrs.scan_early_exit.unwrap_or(false) {
473                Some(crate::ScanPredicate)
474            } else {
475                None
476            },
477        },
478        // A body hole of `Op::Scan` (a childless leaf): `role` + `index` ride
479        // the dedicated `scan_role`/`scan_index` carriers.
480        T::ScanPlaceholder => Op::ScanPlaceholder {
481            role: match attrs.scan_role? {
482                fuel_kernel_seam_types::SCAN_ROLE_CARRY => crate::ScanRole::Carry,
483                fuel_kernel_seam_types::SCAN_ROLE_ELEM => crate::ScanRole::Elem,
484                _ => return None,
485            },
486            index: attrs.scan_index? as usize,
487        },
488        // Multi-output slot projection. The slot's shape/dtype are NOT decoded
489        // here (they come from the producer's `output_views` bundle at emit
490        // time, mirroring `Graph::view`); this arm reconstructs only the op.
491        T::View => Op::View {
492            slot: attrs.view_slot?,
493        },
494
495        // Nested fused op carried AS-IS (Increment C, C-T2, mechanism 2a). The
496        // `fused_op` selector names the registry entry (mirroring `cast_dtype`'s
497        // name-string precedent); resolve it to a `FusedOpId` and reconstruct
498        // the param-less `Op::Fused(fid, params)` via the small `fid -> params`
499        // map. An unset selector, an unknown name, or a param-carrying id (not in
500        // the map — its per-instance params can't be recovered from a name alone)
501        // is a surfaced honest miss (`None`), never a crash. The nested node's
502        // shape/dtype are computed at emit time from the entry's shape/dtype
503        // rules (`primitive_shape` honest-misses `Fused`); this arm reconstructs
504        // only the op. Fuel-INTERNAL — a `Fused` node never reaches the §6.19 wire.
505        T::Fused => {
506            let fid =
507                crate::registry::default_registry().id_for_name(attrs.fused_op.as_deref()?)?;
508            Op::Fused(fid, fused_params_for(fid)?)
509        }
510
511        // MaskedFill (Increment C carriers, A2): the fill VALUE rides
512        // `attrs.scalars[0]` (the `op_to_attrs` projection); its dtype rides
513        // `cast_dtype` when present (a concrete round-trip), else a provisional
514        // F32 that `emit` re-resolves to operand[0]'s dtype (the
515        // dtype-polymorphic recipe path — the byte executor derives `fill_bytes`
516        // at the filled tensor's width, so the Scalar dtype must match). An
517        // unset value or a dtype `Scalar` cannot represent is an honest miss.
518        T::MaskedFill => {
519            let v = *attrs.scalars.first()?;
520            let dtype = match attrs.cast_dtype.as_deref() {
521                Some(name) => DType::from_str(name).ok()?,
522                None => DType::F32,
523            };
524            Op::MaskedFill {
525                value: masked_fill_scalar(v, dtype)?,
526            }
527        }
528
529        // Honest misses (rejected at registration): Clamp (no two-scalar
530        // carrier yet), and any tag whose required attrs are unset or that is
531        // added to OpTag later.
532        _ => return None,
533    })
534}
535
536/// The `fid -> params` map for the nested fused ops a recipe carries as-is
537/// (Increment C, C-T2, mechanism 2a): the two **param-less** attention-trio
538/// nested softmaxes — `SoftmaxLastDim` (forward, used by `flash_attn` /
539/// `paged_attn`) and `SoftmaxLastDimBackward` (used by `flash_attn_backward`).
540/// A param-carrying fused op is NOT recoverable from the name-only `fused_op`
541/// selector — its per-instance params (softmax_scale, block_size, …) have no
542/// carrier here — so any other id is a surfaced honest miss (`None`), never a
543/// crash. Extend this map only when a new *param-less* nested fused op needs to
544/// ride a recipe.
545fn fused_params_for(fid: FusedOpId) -> Option<FusedOpParams> {
546    use crate::registry::FusedOps;
547    Some(match fid {
548        FusedOps::SOFTMAX_LAST_DIM => FusedOpParams::SoftmaxLastDim,
549        FusedOps::SOFTMAX_LAST_DIM_BACKWARD => FusedOpParams::SoftmaxLastDimBackward,
550        _ => return None,
551    })
552}
553
554/// Reconstruct a `MaskedFill` fill [`fuel_ir::Scalar`] from its `f64` value +
555/// the FILLED tensor's dtype (the A2 carrier). `None` for a dtype a `Scalar`
556/// cannot represent (the sub-byte dummy quant formats) — an honest miss, never
557/// a panic (so [`fuel_ir::Scalar::from_f64`]'s dummy-dtype panic is unreachable
558/// through this guard). The value rides `attrs.scalars[0]`; the dtype is the
559/// filled tensor's (operand[0]) dtype, resolved at emit time.
560fn masked_fill_scalar(value: f64, dtype: fuel_ir::DType) -> Option<fuel_ir::Scalar> {
561    // Honest miss for any dtype without a scalar rep; the Result collapses
562    // the old hand-written dummy-dtype guard.
563    fuel_ir::Scalar::from_f64(value, dtype).ok()
564}
565
566/// Decode a target [`fuel_ir::Shape`] from `attrs.target_shape` (the shared
567/// LOGICAL-shape carrier for Reshape/BroadcastTo/ReduceSumTo/ReduceMaxTo).
568/// `None` for an unset (empty) target — an honest miss — UNLESS the
569/// [`OpAttrs::rank0_target`] marker is set, which denotes the intentional
570/// rank-0 (`[]`, scalar) shape (C-T1: a reduce-to-scalar loss tail such as
571/// FSCE's `ReduceSumTo([])`). The marker is what distinguishes an authored
572/// rank-0 target from the empty-`target_shape` wildcard/unset state.
573fn shape_from_attr(attrs: &OpAttrs) -> Option<fuel_ir::Shape> {
574    if attrs.target_shape.is_empty() {
575        if attrs.rank0_target {
576            return Some(fuel_ir::Shape::from_dims(&[]));
577        }
578        return None;
579    }
580    let dims: Vec<usize> = attrs.target_shape.iter().map(|&d| d as usize).collect();
581    Some(fuel_ir::Shape::from_dims(&dims))
582}
583
584/// How many scalar values `tag` consumes from `attrs.scalars` when re-emitted.
585/// The slot machinery (extraction, validation dummy-fill, decompose fill) is
586/// keyed on this; extend alongside `tag_to_op` when a new scalar-param op joins
587/// the v1 vocabulary.
588fn scalar_slot_arity(tag: OpTag) -> usize {
589    matches!(tag, OpTag::AddScalar | OpTag::MulScalar) as usize
590}
591
592// ---- shape-relative attr resolution (Increment C slice 1, T2/D2) -----------
593
594/// A shape-relative attr resolution failure — a typed decline, never a panic.
595/// The emit-integration caller (T3) surfaces any of these as a decompose
596/// fixpoint (`return id`, G2); the registration-validation caller rejects the
597/// region. The `field` names the CONCRETE sibling the rel field resolves into
598/// (`"target_shape"`, `"slice_start"`, `"slice_len"`, `"axis"`, `"dims"`).
599#[derive(Clone, Debug, PartialEq, Eq)]
600pub enum RelAttrError {
601    /// A rel field and its concrete sibling are BOTH set — ambiguous authoring
602    /// (rel XOR abs per field), refused rather than given a silent precedence.
603    RelAbsConflict { field: &'static str },
604    /// The underlying shape-expression evaluation declined (bind out of range,
605    /// axis out of range, divide-by-zero, `Param` with no param threading, …).
606    Expr(fuel_kernel_seam_types::shape_expr::ShapeExprError),
607    /// The expression evaluated over a SYMBOLIC bind extent → a surfaced gap
608    /// (§6.20-0004): a rel attr cannot resolve concrete at emit time.
609    SymbolicGap { field: &'static str },
610    /// The expression produced a negative value where a non-negative
611    /// extent/offset is required.
612    Negative { field: &'static str, value: i64 },
613    /// `axis_last` on a tag with no axis carrier (e.g. `Add`) — meaningless,
614    /// refused (build-time validation, never silently ignored).
615    AxisLastUnsupported { tag: OpTag },
616    /// `axis_last` with no child operand — no rank to resolve LAST against.
617    NoChildOperand,
618    /// The region's Bind-space broadcast **frame** is assembled by per-axis max
619    /// across ≥2 binds (`a[N,1] ⊗ b[1,M] → [N,M]`), so NO single operand carries
620    /// it — and `SameAs { operand }`, the §6.20 EXPRESSION kind's only
621    /// whole-shape constructor, therefore cannot express it. Accepting the
622    /// `SameAs` would SILENTLY resolve a `BroadcastTo` target to a PARTIAL frame
623    /// (`[N,1]` or `[1,M]`) and emit the wrong graph, so it is refused instead
624    /// (Baracuda's §6.20 finding). A **Dims-class gap**: `missing_ctor` is the
625    /// reserved wire tag of the constructor that WOULD express it
626    /// ([`fuel_kernel_seam_types::shape_expr::TAG_DIMS`] = `0x0B`, a §6.20-0002
627    /// extension-registry entrant — proposal filed KISS #80). `frame` is the
628    /// computed per-axis-max frame, for telemetry.
629    FrameNotExpressible {
630        field: &'static str,
631        frame: Vec<i64>,
632        missing_ctor: u8,
633    },
634}
635
636/// Whether `attrs` carries any shape-RELATIVE field (D2) — the emit fast-path
637/// guard: rel-free attrs skip resolution entirely (zero behavior change for
638/// existing concrete regions).
639fn has_rel_attrs(attrs: &OpAttrs) -> bool {
640    attrs.target_shape_rel.is_some()
641        || attrs.slice_start_rel.is_some()
642        || attrs.slice_len_rel.is_some()
643        || attrs.axis_last
644        || attrs.scalar_rel.is_some()
645}
646
647/// The ONE rel-XOR-abs mutual-exclusion oracle (shared by
648/// [`resolve_rel_attrs`] and the registration rel-probe — no second copy to
649/// drift). Returns the first conflicted field name in canonical field order
650/// (`target_shape`, `slice_start`, `slice_len`, then the `axis_last` carrier —
651/// `dims` for Squeeze/Unsqueeze, `axis` otherwise), or `None`. Note the
652/// `axis_last` arm reports a carrier conflict even for a tag the resolver
653/// would refuse as [`RelAttrError::AxisLastUnsupported`] — both are typed
654/// authoring declines, and both-set is checked first.
655fn rel_abs_conflict_field(tag: OpTag, attrs: &OpAttrs) -> Option<&'static str> {
656    if attrs.target_shape_rel.is_some() && !attrs.target_shape.is_empty() {
657        return Some("target_shape");
658    }
659    if attrs.slice_start_rel.is_some() && attrs.slice_start.is_some() {
660        return Some("slice_start");
661    }
662    if attrs.slice_len_rel.is_some() && attrs.slice_len.is_some() {
663        return Some("slice_len");
664    }
665    if attrs.scalar_rel.is_some() && !attrs.scalars.is_empty() {
666        return Some("scalars");
667    }
668    if attrs.axis_last {
669        match tag {
670            OpTag::Unsqueeze | OpTag::Squeeze => {
671                if !attrs.dims.is_empty() {
672                    return Some("dims");
673                }
674            }
675            _ => {
676                if attrs.axis.is_some() {
677                    return Some("axis");
678                }
679            }
680        }
681    }
682    None
683}
684
685/// The region's Bind-space broadcast **frame**: the NumPy right-aligned
686/// per-axis max over EVERY bind shape (`[N,1] ⊗ [1,M] → [N,M]`) — the shape an
687/// elementwise consumer of all the binds produces. `None` when the binds carry
688/// no joint elementwise frame at all: no binds, a SYMBOLIC extent (the frame is
689/// itself a gap — the `SameAs` arm already declines those as
690/// [`RelAttrError::SymbolicGap`]), or mutually broadcast-INcompatible binds (a
691/// matmul/gather region, where per-axis max is meaningless). Pure and total —
692/// unlike the graph-builder `compute_broadcast_shape`, incompatibility is
693/// `None`, never a panic.
694fn bind_broadcast_frame(bind_shapes: &[Vec<i64>]) -> Option<Vec<i64>> {
695    use fuel_kernel_seam_types::shape_expr::SYMBOLIC;
696    let rank = bind_shapes.iter().map(Vec::len).max()?;
697    let mut frame = vec![1i64; rank];
698    for s in bind_shapes {
699        if s.iter().any(|&e| e == SYMBOLIC || e < 0) {
700            return None;
701        }
702        let pad = rank - s.len(); // right-aligned: pad the shorter with leading 1s
703        for (i, &e) in s.iter().enumerate() {
704            let f = &mut frame[pad + i];
705            if *f == e || e == 1 {
706                continue;
707            }
708            if *f == 1 {
709                *f = e;
710                continue;
711            }
712            return None; // incompatible at this axis ⇒ no joint frame
713        }
714    }
715    Some(frame)
716}
717
718/// The SameAs **degradation guard** (I1, Baracuda's §6.20 finding). Even an
719/// ELEMENTWISE output shape is not always expressible as `SameAs(operand)`:
720/// when the region's broadcast frame is assembled by per-axis max across TWO
721/// binds (`a[N,1] ⊗ b[1,M] → [N,M]`) no single operand carries the full frame,
722/// so every `SameAs` spelling resolves to a PARTIAL frame. `BroadcastTo` is the
723/// recipe's frame carrier (its target IS the elementwise output shape), so a
724/// `SameAs` target is accepted only when SOME bind does carry the whole frame;
725/// otherwise the frame is surfaced as a typed Dims-class gap rather than
726/// silently resolved to one operand's partial shape.
727///
728/// Deliberately narrow — it fires ONLY when a joint frame exists and NO bind
729/// equals it. Sub-frame broadcasts (bind1 `[T,D]` inside a `[B,T,D]` region)
730/// and frame-less regions (matmul binds) are untouched, and all 5 slice-1
731/// migrated recipes are safe by construction (their `SameAs` target is bind 0,
732/// which carries the frame).
733fn same_as_frame_guard(
734    tag: OpTag,
735    se: &fuel_kernel_seam_types::shape_expr::ShapeExpr,
736    bind_shapes: &[Vec<i64>],
737) -> Result<(), RelAttrError> {
738    use fuel_kernel_seam_types::shape_expr::{ShapeExpr, TAG_DIMS};
739    // Exhaustive on purpose: the Dims/WithDim extension entrants (KISS #80)
740    // express the max-frame DIRECTLY (a whole-shape ctor), so they must NOT be
741    // routed through this partial-`SameAs`-frame guard — they return early.
742    match se {
743        ShapeExpr::SameAs { .. } => {}
744        ShapeExpr::WithDim { .. } | ShapeExpr::Dims(_) => return Ok(()),
745    }
746    if tag != OpTag::BroadcastTo {
747        return Ok(());
748    }
749    let Some(frame) = bind_broadcast_frame(bind_shapes) else {
750        return Ok(()); // no joint elementwise frame at play
751    };
752    if bind_shapes.contains(&frame) {
753        return Ok(()); // some operand carries the whole frame ⇒ expressible
754    }
755    Err(RelAttrError::FrameNotExpressible {
756        field: "target_shape",
757        frame,
758        missing_ctor: TAG_DIMS,
759    })
760}
761
762/// Resolve `attrs`' shape-RELATIVE fields (`target_shape_rel`,
763/// `slice_start_rel`/`slice_len_rel`, `axis_last` — D2) into their concrete
764/// siblings, returning a fully-concrete [`OpAttrs`] ready for the unchanged
765/// `tag_to_op` → `primitive_shape` path. Pure: no graph access.
766///
767/// * `bind_shapes` — the region's **Bind-space** shapes, `bind_shapes[i]` =
768///   `Bind { index: i }`'s shape. This is what `ShapeExpr::SameAs { operand }`
769///   and `Dim::Extent { operand, .. }` index (the recipe-interior reference
770///   convention, same as the merged KISS shape-oracle RFC's contract roles).
771/// * `child_shapes` — THIS op's direct operand shapes (the already-emitted
772///   children), which `axis_last` resolves its rank against — a region
773///   interior node's shape generally matches NO bind.
774///
775/// Evaluation reuses `shape_expr::eval_dim`/`eval_shape`/`resolve_axis` — the
776/// single §6.20 evaluator, no second one. `Dim::Param` declines with a typed
777/// [`fuel_kernel_seam_types::shape_expr::ShapeExprError::ParamOutOfRange`] until param threading lands (C-4);
778/// symbolic bind extents decline as [`RelAttrError::SymbolicGap`]. Rel fields
779/// are CLEARED in the output (rel+abs both set in the RESULT would trip the
780/// mutual-exclusion check on a second resolve).
781pub fn resolve_rel_attrs(
782    tag: OpTag,
783    attrs: &OpAttrs,
784    bind_shapes: &[Vec<i64>],
785    child_shapes: &[Vec<i64>],
786) -> Result<OpAttrs, RelAttrError> {
787    use fuel_kernel_seam_types::shape_expr::{self, Dim, DimValue, LAST, ShapeValue, resolve_axis};
788    // Mutual exclusion FIRST, for every field, before any evaluation — so a
789    // value-dependent decline in an earlier field can't mask a rel+abs
790    // authoring conflict in a later one (the registration probe relies on
791    // this completeness).
792    if let Some(field) = rel_abs_conflict_field(tag, attrs) {
793        return Err(RelAttrError::RelAbsConflict { field });
794    }
795    let mut out = attrs.clone();
796
797    // target_shape_rel → target_shape (SameAs over the Bind space).
798    if let Some(se) = &attrs.target_shape_rel {
799        match shape_expr::eval_shape(se, bind_shapes, &[]).map_err(RelAttrError::Expr)? {
800            ShapeValue::Concrete(s) => {
801                if let Some(&bad) = s.iter().find(|&&e| e < 0) {
802                    return Err(RelAttrError::Negative {
803                        field: "target_shape",
804                        value: bad,
805                    });
806                }
807                // I1: refuse a `SameAs` target whose region frame no operand
808                // carries — a silent PARTIAL frame otherwise (see
809                // [`same_as_frame_guard`]). Runs AFTER evaluation so the
810                // structural declines (`OperandOutOfRange`, `SymbolicGap`)
811                // keep their existing precedence.
812                same_as_frame_guard(tag, se, bind_shapes)?;
813                out.target_shape = s;
814            }
815            ShapeValue::Gap => {
816                return Err(RelAttrError::SymbolicGap {
817                    field: "target_shape",
818                });
819            }
820        }
821        out.target_shape_rel = None;
822    }
823
824    // slice_{start,len}_rel → slice_{start,len} (DimExpr over the Bind space).
825    let eval_dim_field = |d: &Dim, field: &'static str| -> Result<u64, RelAttrError> {
826        match shape_expr::eval_dim(d, bind_shapes, &[]).map_err(RelAttrError::Expr)? {
827            DimValue::Concrete(v) if v < 0 => Err(RelAttrError::Negative { field, value: v }),
828            DimValue::Concrete(v) => Ok(v as u64),
829            DimValue::Gap => Err(RelAttrError::SymbolicGap { field }),
830        }
831    };
832    if let Some(d) = &attrs.slice_start_rel {
833        out.slice_start = Some(eval_dim_field(d, "slice_start")?);
834        out.slice_start_rel = None;
835    }
836    if let Some(d) = &attrs.slice_len_rel {
837        out.slice_len = Some(eval_dim_field(d, "slice_len")?);
838        out.slice_len_rel = None;
839    }
840
841    // scalar_rel → scalars (DimExpr over the Bind space; the reduced_count
842    // concept — `Dim::Extent { operand, axis: LAST }` = n = extent of that
843    // operand's last axis, the norm-backward `MulScalar(n)` divisor). Rides the
844    // SAME §6.20 `eval_dim`/`resolve_axis` evaluator as the slice_* rel fields,
845    // resolved against the Bind space (not this op's children — a scalar rides no
846    // operand's rank). Unlike an extent/offset, a scalar may be negative, so only
847    // a symbolic Gap declines (§6.20-0004); every concrete value flows through.
848    if let Some(d) = &attrs.scalar_rel {
849        match shape_expr::eval_dim(d, bind_shapes, &[]).map_err(RelAttrError::Expr)? {
850            DimValue::Concrete(v) => out.scalars = vec![v as f64],
851            DimValue::Gap => return Err(RelAttrError::SymbolicGap { field: "scalars" }),
852        }
853        out.scalar_rel = None;
854    }
855
856    // axis_last → the per-tag axis carrier, resolved against operand[0]'s rank.
857    if attrs.axis_last {
858        let rank = child_shapes
859            .first()
860            .ok_or(RelAttrError::NoChildOperand)?
861            .len();
862        use OpTag as T;
863        match tag {
864            // `axis`-carrier tags: this op's LAST = rank − 1 via the shared
865            // §6.20 resolver (typed AxisOutOfRange on a rank-0 operand).
866            T::SumDim
867            | T::MaxDim
868            | T::MeanDim
869            | T::CumSum
870            | T::Concat
871            | T::Flip
872            | T::Slice
873            | T::Roll
874            | T::IndexSelect
875            | T::Gather
876            | T::IndexAdd
877            | T::ScatterAdd => {
878                let a = resolve_axis(LAST, rank).map_err(RelAttrError::Expr)?;
879                out.axis = Some(a as i64);
880            }
881            // `dims`-carrier: Unsqueeze APPENDS — dim == rank (`primitive_shape`
882            // permits `dim == rank`; keepdim-restore spelling, D3).
883            T::Unsqueeze => {
884                out.dims = vec![rank as u8];
885            }
886            // `dims`-carrier: Squeeze drops the trailing axis = rank − 1.
887            T::Squeeze => {
888                let a = resolve_axis(LAST, rank).map_err(RelAttrError::Expr)?;
889                out.dims = vec![a as u8];
890            }
891            other => return Err(RelAttrError::AxisLastUnsupported { tag: other }),
892        }
893        out.axis_last = false;
894    }
895
896    Ok(out)
897}
898
899/// Count the region's open scalar **slots** in pattern pre-order — scalar-param
900/// ops whose `attrs.scalars` is empty (a baked value is a pattern constant, not
901/// a slot). This is the length of the `scalars` vec `match_region_extract`
902/// returns for a match, and of the `FusedOpParams::Runtime { scalars }` the
903/// fused node must carry for [`decompose_region`] to fill the re-emit.
904pub fn count_scalar_slots(node: &PatternNode) -> usize {
905    match node {
906        PatternNode::Op {
907            op,
908            operands,
909            attrs,
910        } => {
911            // A `scalar_rel` node is filled from an input SHAPE at emit time, NOT
912            // from the params cursor — so it is never an open slot (mirrors a
913            // baked-value node). Only an empty-`scalars`, no-`scalar_rel`
914            // scalar-param op is a cursor slot.
915            let own = if attrs.scalars.is_empty() && attrs.scalar_rel.is_none() {
916                scalar_slot_arity(*op)
917            } else {
918                0
919            };
920            own + operands.iter().map(count_scalar_slots).sum::<usize>()
921        }
922        _ => 0,
923    }
924}
925
926/// The ONE recipe-validation oracle, shared by [`register_runtime_fused`]
927/// (runtime Tier-2 registration) and the static-registry
928/// [`crate::registry::decompose_via_recipe`] bridge (T5): bind indices form a
929/// contiguous `[0, n)` AND every op re-emits to primitives (totality — incl.
930/// the rel-attr probe). A recipe carrying a semantics-absent op token (no
931/// primitive re-emission — the flip-withdrawal posture: unknown/non-registry
932/// tokens are surfaced honest-miss declines, never accepted, never a crash)
933/// is a typed [`RuntimeFusedError::UnRepresentable`] decline here.
934pub(crate) fn validate_recipe(region: &PatternNode) -> Result<(), RuntimeFusedError> {
935    let binds = region.bind_indices();
936    let n = binds.len() as u8;
937    if binds != (0..n).collect::<Vec<_>>() {
938        return Err(RuntimeFusedError::NonContiguousBinds(binds));
939    }
940    validate_representable(region)
941}
942
943fn validate_representable(region: &PatternNode) -> Result<(), RuntimeFusedError> {
944    let n_binds = region.bind_indices().len();
945    validate_node(region, n_binds)
946}
947
948fn validate_node(node: &PatternNode, n_binds: usize) -> Result<(), RuntimeFusedError> {
949    match node {
950        PatternNode::Op {
951            op,
952            operands,
953            attrs,
954        } => {
955            // A rel-attr op is a SHAPE-POLYMORPHIC template — probe-resolve it
956            // (T3, mirror of the scalar slot dummy-fill below) so the
957            // `tag_to_op` representability check can run on concrete attrs.
958            // Structural authoring errors reject the region with a typed
959            // decline; value-dependent declines at the probe shape register
960            // fine and surface at emit time as a decompose fixpoint.
961            let probed;
962            let attrs = if has_rel_attrs(attrs) {
963                probed = rel_probe(*op, attrs, n_binds)
964                    .map_err(|error| RuntimeFusedError::InvalidRelAttrs { tag: *op, error })?;
965                &probed
966            } else {
967                attrs
968            };
969            // A scalar-param op with empty scalars is a SLOT template —
970            // validate re-emittability with a dummy fill (the live value is
971            // substituted from the fused node's `Runtime { scalars }` at
972            // decompose time).
973            let representable = if attrs.scalars.is_empty() && scalar_slot_arity(*op) > 0 {
974                let mut filled = attrs.clone();
975                filled.scalars = vec![0.0; scalar_slot_arity(*op)];
976                tag_to_op(*op, &filled).is_some()
977            } else {
978                tag_to_op(*op, attrs).is_some()
979            };
980            if !representable {
981                return Err(RuntimeFusedError::UnRepresentable(*op));
982            }
983            for o in operands {
984                validate_node(o, n_binds)?;
985            }
986            Ok(())
987        }
988        PatternNode::Bind { .. } => Ok(()),
989        PatternNode::Any | PatternNode::SeeThrough { .. } => {
990            Err(RuntimeFusedError::NonConcreteRegion)
991        }
992    }
993}
994
995/// The registration-time rel-attr probe: resolve `attrs` against a fixed
996/// `[2, 4]` probe shape (every bind + the child) through the ONE resolver.
997/// * `Ok(resolved)` — fully-concrete attrs for the `tag_to_op` probe.
998/// * `Err` — a STRUCTURAL authoring error that can never resolve at ANY
999///   shape: rel+abs both set, a bind/`Param` reference out of range,
1000///   `axis_last` on an axis-less tag.
1001/// * A VALUE-dependent decline at the probe shape (`Negative`,
1002///   `AxisOutOfRange` against the probe rank, `DivideByZero` through a
1003///   derived extent, …) is NOT an authoring error — the attrs get a dummy
1004///   concrete fill instead (the emit-time resolver is the real gate; its
1005///   decline there is a G2 fixpoint).
1006fn rel_probe(tag: OpTag, attrs: &OpAttrs, n_binds: usize) -> Result<OpAttrs, RelAttrError> {
1007    use fuel_kernel_seam_types::shape_expr::ShapeExprError as E;
1008    let probe: Vec<i64> = vec![2, 4];
1009    let bind_shapes = vec![probe.clone(); n_binds];
1010    match resolve_rel_attrs(tag, attrs, &bind_shapes, std::slice::from_ref(&probe)) {
1011        Ok(resolved) => Ok(resolved),
1012        Err(
1013            e @ (RelAttrError::RelAbsConflict { .. }
1014            | RelAttrError::AxisLastUnsupported { .. }
1015            | RelAttrError::Expr(E::OperandOutOfRange { .. } | E::ParamOutOfRange { .. })),
1016        ) => Err(e),
1017        Err(_) => Ok(dummy_fill_rel(tag, attrs)),
1018    }
1019}
1020
1021/// Clear `attrs`' rel fields and dummy-fill their concrete siblings (only
1022/// where the sibling is unset — a rel+abs conflict never reaches here, the
1023/// probe rejects it first) so `tag_to_op` representability can be checked.
1024fn dummy_fill_rel(tag: OpTag, attrs: &OpAttrs) -> OpAttrs {
1025    let mut out = attrs.clone();
1026    if out.target_shape_rel.take().is_some() && out.target_shape.is_empty() {
1027        out.target_shape = vec![1];
1028    }
1029    if out.slice_start_rel.take().is_some() && out.slice_start.is_none() {
1030        out.slice_start = Some(0);
1031    }
1032    if out.slice_len_rel.take().is_some() && out.slice_len.is_none() {
1033        out.slice_len = Some(1);
1034    }
1035    if out.scalar_rel.take().is_some() && out.scalars.is_empty() {
1036        out.scalars = vec![1.0];
1037    }
1038    if out.axis_last {
1039        out.axis_last = false;
1040        match tag {
1041            OpTag::Unsqueeze | OpTag::Squeeze => {
1042                if out.dims.is_empty() {
1043                    out.dims = vec![0];
1044                }
1045            }
1046            _ => {
1047                if out.axis.is_none() {
1048                    out.axis = Some(0);
1049                }
1050            }
1051        }
1052    }
1053    out
1054}
1055
1056/// Decompose a runtime `Op::Fused(id, Runtime { .. })` node by re-emitting its
1057/// region as primitives, returning the new root (the re-emitted sink). If `id`
1058/// is not a registered runtime op the node is returned unchanged (a fixpoint —
1059/// no recipe, G2). The matched node's inputs are the region's bound external
1060/// inputs in bind-index order.
1061pub fn decompose_region(graph: &mut Graph, node_id: NodeId) -> NodeId {
1062    let (fid, node_scalars) = match &graph.node(node_id).op {
1063        Op::Fused(id, FusedOpParams::Runtime { scalars }) => (*id, scalars.clone()),
1064        Op::Fused(id, _) => (*id, Vec::new()),
1065        _ => return node_id,
1066    };
1067    let region = match runtime_region(fid) {
1068        Some(r) => r,
1069        None => return node_id,
1070    };
1071    // The node's live scalars must fill the region's slots exactly (pattern
1072    // pre-order, the same canon `match_region_extract` produced them in). A
1073    // mismatch is a malformed fused node — surfaced as a no-op fixpoint (the
1074    // lowering driver records no progress), never a crash (G2).
1075    if node_scalars.len() != count_scalar_slots(&region) {
1076        return node_id;
1077    }
1078    let inputs = graph.node(node_id).inputs.clone();
1079    let bind_shapes = bind_operand_shapes(graph, &inputs);
1080    let mut cursor = node_scalars.as_slice();
1081    // A shape-relative attr that fails to resolve at THESE input shapes (a
1082    // symbolic extent, a negative result, …) is a typed decline surfaced as a
1083    // no-op fixpoint (G2) — same posture as the slot-count mismatch above,
1084    // never a panic. Any child nodes emitted before the decline stay in the
1085    // push-only graph as unreferenced dead nodes (inert).
1086    emit(
1087        graph,
1088        &region,
1089        &inputs,
1090        &bind_shapes,
1091        &mut cursor,
1092        &mut Vec::new(),
1093    )
1094    .unwrap_or(node_id)
1095}
1096
1097/// Re-emit a validated region on the given external input nodes (public entry
1098/// for callers holding a raw [`PatternNode`] + input [`NodeId`]s — e.g. the
1099/// reference realization during candidate-kernel verification, which has a raw
1100/// region and freshly-pushed `Op::Const` input nodes rather than a Fused node
1101/// already in the graph). `scalars` fill the region's open scalar slots in
1102/// pre-order (the canonical order `match_region_extract` recorded them in);
1103/// pass `&[]` for a parameterless region. Thin wrapper over the private
1104/// `emit`; the same re-emittability caveat applies (a non-re-emittable
1105/// `OpTag` panics inside `emit` — validated decomposes never carry one).
1106/// Second panic risk: `emit`'s scalar-cursor fill (`scalars.split_at(arity)`)
1107/// panics if `scalars` is shorter than the region's total open-slot count.
1108/// [`decompose_region`] (the node-driven caller) guards this with its own
1109/// length check before ever calling `emit`; `emit_region` deliberately does
1110/// NOT — it's a thin wrapper, so validating the length is the caller's job.
1111/// Callers must pass a `scalars` slice at least as long as the region's
1112/// open-slot count. Third (T3): a shape-RELATIVE attr (D2) that fails to
1113/// resolve at these input shapes panics through the wrapper's `expect` —
1114/// rel-attr callers use [`try_emit_region`], which surfaces it as a typed
1115/// [`RelAttrError`] instead.
1116pub fn emit_region(
1117    graph: &mut Graph,
1118    region: &PatternNode,
1119    inputs: &[NodeId],
1120    scalars: &[f64],
1121) -> NodeId {
1122    try_emit_region(graph, region, inputs, scalars).expect(
1123        "rel-attr resolution failed — emit_region callers pass concrete-attr or \
1124         shape-compatible pre-validated regions; fallible callers use try_emit_region",
1125    )
1126}
1127
1128/// The FALLIBLE re-emit entry (Increment C slice 1, T3): like [`emit_region`]
1129/// but surfacing a shape-relative attr resolution failure (D2) as a typed
1130/// [`RelAttrError`] instead of a panic. This is the resolving entry the
1131/// registry `decompose_via_recipe` bridge calls (any failure ⇒ `return id`,
1132/// the G2 fixpoint). Concrete-attr regions can never hit the `Err` arm — for
1133/// them this is exactly the legacy `emit_region`. The `emit_region` panic
1134/// caveats (non-re-emittable `OpTag`, short `scalars` slice) apply unchanged.
1135pub fn try_emit_region(
1136    graph: &mut Graph,
1137    region: &PatternNode,
1138    inputs: &[NodeId],
1139    scalars: &[f64],
1140) -> Result<NodeId, RelAttrError> {
1141    let bind_shapes = bind_operand_shapes(graph, inputs);
1142    let mut cursor = scalars;
1143    emit(
1144        graph,
1145        region,
1146        inputs,
1147        &bind_shapes,
1148        &mut cursor,
1149        &mut Vec::new(),
1150    )
1151}
1152
1153/// A graph [`fuel_ir::Shape`] as a §6.20 evaluator operand: per-axis extents
1154/// with a bounded-symbolic (`Extent::Range`) axis mapped to the
1155/// [`shape_expr::SYMBOLIC`] sentinel — so a rel attr over a symbolic extent
1156/// declines as [`RelAttrError::SymbolicGap`] (surfaced gap, §6.20-0004)
1157/// instead of silently resolving against the capacity bound.
1158fn shape_expr_operand(shape: &fuel_ir::Shape) -> Vec<i64> {
1159    use fuel_kernel_seam_types::shape_expr::SYMBOLIC;
1160    (0..shape.rank())
1161        .map(|a| {
1162            if shape.extent(a).is_dynamic() {
1163                SYMBOLIC
1164            } else {
1165                shape.dims()[a] as i64
1166            }
1167        })
1168        .collect()
1169}
1170
1171/// The region's **Bind-space** shapes (`bind_shapes[i]` = `inputs[i]`'s shape)
1172/// in §6.20 operand form — what `ShapeExpr::SameAs`/`Dim::Extent` index.
1173fn bind_operand_shapes(graph: &Graph, inputs: &[NodeId]) -> Vec<Vec<i64>> {
1174    inputs
1175        .iter()
1176        .map(|&id| shape_expr_operand(&graph.node(id).shape))
1177        .collect()
1178}
1179
1180/// The recursive re-emit core. `memo` is the per-emit-call identity-share
1181/// table (T5): a REPEATED slot-free subtree — the tree spelling of a DAG
1182/// recipe's shared interior (e.g. softmax's `e = Exp(..)`, consumed by both
1183/// the denominator reduce and the final Div) — emits ONCE, so the emitted
1184/// graph is the DAG, not a duplicated-compute tree. Lookup is by structural
1185/// equality (`PatternNode: PartialEq`; regions are tiny, a linear scan is
1186/// fine) and is sound because, within one call, `inputs`/`bind_shapes` are
1187/// fixed and emission is deterministic — equal subtrees emit equal nodes.
1188/// Subtrees with OPEN scalar slots are NEVER shared: each occurrence takes
1189/// its own value(s) from the pre-order cursor. (The flat-DAG node table with
1190/// real CSE is slice 3; this is only within-call identity-share.)
1191fn emit<'r>(
1192    graph: &mut Graph,
1193    node: &'r PatternNode,
1194    inputs: &[NodeId],
1195    bind_shapes: &[Vec<i64>],
1196    scalars: &mut &[f64],
1197    memo: &mut Vec<(&'r PatternNode, NodeId)>,
1198) -> Result<NodeId, RelAttrError> {
1199    match node {
1200        PatternNode::Bind { index } => Ok(inputs[*index as usize]),
1201        PatternNode::Op {
1202            op,
1203            operands,
1204            attrs,
1205        } => {
1206            // Identity-share: a slot-free subtree already emitted in THIS call
1207            // re-uses its node (see the fn doc). Checked before the cursor
1208            // fill — a slot-free subtree never moves the cursor, so a hit
1209            // cannot misalign later slots.
1210            let sharable = count_scalar_slots(node) == 0;
1211            if sharable && let Some(&(_, id)) = memo.iter().find(|(p, _)| *p == node) {
1212                return Ok(id);
1213            }
1214            // Fill an open scalar slot from the cursor in PRE-order (before
1215            // descending into operands) — the same canonical order
1216            // `match_region_extract` recorded the live values in. (T3 note:
1217            // children are now EMITTED before the attrs are USED, but the
1218            // cursor fill stays right here, before the descent — the cursor
1219            // order is authoring order, not emission order.)
1220            // A `scalar_rel` node is filled from an input shape by the rel-attr
1221            // resolver below (NOT the params cursor) — matching
1222            // `count_scalar_slots`, which does not count it as a slot, so it must
1223            // not consume a cursor value here either.
1224            let arity = scalar_slot_arity(*op);
1225            let filled;
1226            let attrs = if attrs.scalars.is_empty() && arity > 0 && attrs.scalar_rel.is_none() {
1227                let (take, rest) = scalars.split_at(arity);
1228                *scalars = rest;
1229                filled = OpAttrs {
1230                    scalars: take.to_vec(),
1231                    ..attrs.clone()
1232                };
1233                &filled
1234            } else {
1235                attrs
1236            };
1237            // Children FIRST (T3 reorder): their emitted shapes feed the
1238            // rel-attr resolver (`axis_last`'s rank, D4's pad decision).
1239            let mut child_ids = Vec::with_capacity(operands.len());
1240            for o in operands {
1241                child_ids.push(emit(graph, o, inputs, bind_shapes, scalars, memo)?);
1242            }
1243            let mut child_shapes: Vec<fuel_ir::Shape> = child_ids
1244                .iter()
1245                .map(|&c| graph.node(c).shape.clone())
1246                .collect();
1247            let child_dtypes: Vec<fuel_ir::DType> =
1248                child_ids.iter().map(|&c| graph.node(c).dtype).collect();
1249            // Shape-RELATIVE attrs (D2) resolve to fully-concrete siblings
1250            // against the region's Bind space + this op's operand shapes; the
1251            // unchanged tag_to_op → primitive_shape path then runs on the
1252            // result. A failure is a typed decline the caller surfaces
1253            // (`decompose_region` ⇒ fixpoint, `try_emit_region` ⇒ `Err`) —
1254            // never a panic. Nodes already pushed for the children stay in the
1255            // graph as unreferenced (dead) nodes: `Graph` is push-only and
1256            // base-map extraction walks from roots, so they are inert.
1257            let resolved;
1258            let attrs = if has_rel_attrs(attrs) {
1259                let child_ops: Vec<Vec<i64>> =
1260                    child_shapes.iter().map(shape_expr_operand).collect();
1261                resolved = resolve_rel_attrs(*op, attrs, bind_shapes, &child_ops)?;
1262                &resolved
1263            } else {
1264                attrs
1265            };
1266            let mut prim =
1267                tag_to_op(*op, attrs).expect("region validated re-emittable at registration");
1268            // A `MaskedFill` fill Scalar must carry the FILLED tensor's
1269            // (operand[0]) dtype — the byte executor derives `fill_bytes` at
1270            // that width. A recipe authors the value dtype-polymorphically (no
1271            // `cast_dtype`), so re-resolve the Scalar at operand[0]'s emitted
1272            // dtype here, mirroring the imperative `Scalar::one(dtype)`. Concrete
1273            // round-trips already carry the matching dtype (identity); a dtype a
1274            // Scalar cannot represent leaves the provisional value untouched (an
1275            // inert dead node, never a panic — the executor rejects it later).
1276            if let Op::MaskedFill { value } = &prim
1277                && let Some(&dt) = child_dtypes.first()
1278                && value.dtype() != dt
1279                && let Some(fixed) = masked_fill_scalar(value.to_f64(), dt)
1280            {
1281                prim = Op::MaskedFill { value: fixed };
1282            }
1283            // D4: a `BroadcastTo` whose target rank EXCEEDS its operand's rank
1284            // first materializes the legacy `Reshape` pad (1-padded left,
1285            // right-aligned — byte-identical to `registry::rope`'s hand-built
1286            // broadcast prep, since `check_broadcast_compatible` is
1287            // right-aligned). Recipes stay free of rank-dependent nodes while
1288            // the emitted graph matches the legacy imperative builders.
1289            // Applied uniformly (rel-resolved AND absolute targets); an
1290            // equal-rank broadcast is unchanged (no pad).
1291            if let Op::BroadcastTo(target) = &prim
1292                && let Some(cs) = child_shapes.first()
1293                && target.rank() > cs.rank()
1294            {
1295                let mut padded: Vec<usize> = vec![1; target.rank() - cs.rank()];
1296                padded.extend_from_slice(cs.dims());
1297                let pad_shape = fuel_ir::Shape::from_dims(&padded);
1298                let pad = graph.push(Node {
1299                    op: Op::Reshape(pad_shape.clone()),
1300                    inputs: vec![child_ids[0]],
1301                    shape: pad_shape.clone(),
1302                    dtype: child_dtypes[0],
1303                });
1304                child_ids[0] = pad;
1305                child_shapes[0] = pad_shape;
1306            }
1307            // Shape/dtype for the emitted node. Most ops use the single source
1308            // of truth (`primitive_shape`) — a pure function of operand shapes,
1309            // correct for shape-changing/reducing/dtype-changing ops. Two
1310            // structural multi-output terminals are NOT such a function and are
1311            // resolved here with the graph in hand, mirroring `NodeHandle::scan` /
1312            // `Graph::view` (Increment C, B1):
1313            //   * `Op::Scan` — the node's PRIMARY (slot-0) shape is the stacked
1314            //     ys `[bound] ++ body_y`, and its 2-slot `output_views` bundle
1315            //     (slot 0 = stacked ys, slot 1 = final carry) is attached AFTER
1316            //     the push so downstream `Op::View`s can read it.
1317            //   * `Op::View` — the slot's shape/dtype come from the producer's
1318            //     `output_views[slot]` (set when the producing `Scan` emitted).
1319            // Both fall back to operand[0]'s shape/dtype on a malformed authored
1320            // region (never a panic) — the same posture as the primitive_shape
1321            // fallback (`.first()`, never `[0]`, guards a zero-operand leaf).
1322            fn fallback_sd(
1323                cs: &[fuel_ir::Shape],
1324                cd: &[fuel_ir::DType],
1325            ) -> (fuel_ir::Shape, fuel_ir::DType) {
1326                (
1327                    cs.first()
1328                        .cloned()
1329                        .unwrap_or_else(|| fuel_ir::Shape::from_dims(&[])),
1330                    cd.first().copied().unwrap_or(fuel_ir::DType::F32),
1331                )
1332            }
1333            let mut scan_bundle: Option<Vec<fuel_ir::storage::OutputViewSpec>> = None;
1334            let (s, d) = match &prim {
1335                Op::Scan {
1336                    n_carries: _,
1337                    n_xs,
1338                    bound,
1339                    early_exit,
1340                    ..
1341                } => {
1342                    // inputs = [init_carry, xs(n_xs), consts.., body_new_carry,
1343                    // body_y, [pred_exit]] — the Phase-1 lax.scan encoding.
1344                    let n_trailing = if early_exit.is_some() { 3 } else { 2 };
1345                    let len = child_ids.len();
1346                    if len >= 1 + *n_xs + n_trailing {
1347                        let carry_shape = child_shapes[0].clone();
1348                        let carry_dtype = child_dtypes[0];
1349                        let body_y_i = len - n_trailing + 1;
1350                        let y_shape = &child_shapes[body_y_i];
1351                        let y_dtype = child_dtypes[body_y_i];
1352                        let mut ys_dims = Vec::with_capacity(1 + y_shape.rank());
1353                        ys_dims.push(*bound);
1354                        ys_dims.extend_from_slice(y_shape.dims());
1355                        let ys_shape = fuel_ir::Shape::from_dims(&ys_dims);
1356                        scan_bundle = Some(vec![
1357                            fuel_ir::storage::OutputViewSpec::contiguous(y_dtype, ys_shape.clone()),
1358                            fuel_ir::storage::OutputViewSpec::contiguous(carry_dtype, carry_shape),
1359                        ]);
1360                        (ys_shape, y_dtype)
1361                    } else {
1362                        fallback_sd(&child_shapes, &child_dtypes)
1363                    }
1364                }
1365                Op::View { slot } => child_ids
1366                    .first()
1367                    .and_then(|&producer| {
1368                        graph
1369                            .output_views(producer)
1370                            .and_then(|v| v.get(*slot as usize))
1371                            .map(|spec| (spec.shape.clone(), spec.dtype))
1372                    })
1373                    .unwrap_or_else(|| fallback_sd(&child_shapes, &child_dtypes)),
1374                // A nested fused node carried as-is (Increment C, C-T2, mechanism
1375                // 2a). `primitive_shape` honest-misses `Fused` — a fused op is not
1376                // a first-order shape-inferable primitive — so its output frame
1377                // comes from the named registry entry's OWN `shape_rule` /
1378                // `dtype_rule` (e.g. softmax passes both through). Falls back to
1379                // operand[0] on an unregistered id (never a panic), the same
1380                // posture as the primitive_shape fallback.
1381                Op::Fused(fid, params) => match crate::registry::default_registry().entry(*fid) {
1382                    Some(e) => (
1383                        (e.shape_rule)(&child_shapes, params),
1384                        (e.dtype_rule)(&child_dtypes, params),
1385                    ),
1386                    None => fallback_sd(&child_shapes, &child_dtypes),
1387                },
1388                // A childless scan-body hole (`Op::ScanPlaceholder`). Its shape
1389                // is DECLARED on the recipe node (`target_shape` /
1390                // `target_shape_rel`, resolved above) rather than inferred from
1391                // (absent) operands, so the re-emitted body carries the same
1392                // per-step shapes the imperative `selective_scan`/`ssd_chunk_scan`
1393                // decompose stamps on its placeholders. This is load-bearing:
1394                // `unroll_scan` CLONES body interior nodes with their STORED
1395                // shapes, so a rank-0 fallback here would poison the unrolled
1396                // graph (e.g. `du = Mul(d_t, u_t)` would come out rank-0). Dtype =
1397                // the scan's working dtype = bind 0's (uniform-dtype scan inputs).
1398                // Both fall back to the operand/F32 default on a shapeless
1399                // authored placeholder (B1's round-trip recipes), never a panic.
1400                Op::ScanPlaceholder { .. } => (
1401                    shape_from_attr(attrs)
1402                        .unwrap_or_else(|| fallback_sd(&child_shapes, &child_dtypes).0),
1403                    inputs
1404                        .first()
1405                        .map(|&b| graph.node(b).dtype)
1406                        .unwrap_or_else(|| fallback_sd(&child_shapes, &child_dtypes).1),
1407                ),
1408                _ => crate::shape::primitive_shape(&prim, &child_shapes, &child_dtypes)
1409                    .unwrap_or_else(|_| fallback_sd(&child_shapes, &child_dtypes)),
1410            };
1411            let out = graph.push(Node {
1412                op: prim,
1413                inputs: child_ids,
1414                shape: s,
1415                dtype: d,
1416            });
1417            // Attach the scan's 2-slot bundle (mirrors `NodeHandle::scan`). A
1418            // malformed spec (compose/validate failure) leaves the producer
1419            // bundle-less — a later `View` then falls back — never a panic.
1420            if let Some(specs) = scan_bundle
1421                && let Ok((_bytes, views)) = fuel_ir::storage::compose_bundle(&specs)
1422            {
1423                let _ = graph.set_output_views(out, std::sync::Arc::from(views.into_boxed_slice()));
1424            }
1425            if sharable {
1426                memo.push((node, out));
1427            }
1428            Ok(out)
1429        }
1430        PatternNode::Any | PatternNode::SeeThrough { .. } => {
1431            unreachable!("region validated concrete (Op/Bind) at registration")
1432        }
1433    }
1434}
1435
1436/// A [`crate::opt::LoweringRule`]-shaped `decompose` for runtime ops: re-emit
1437/// the region. The scalar `extract:` substitution rides on the NODE (its
1438/// `FusedOpParams::Runtime { scalars }` fills the region's open slots inside
1439/// [`decompose_region`]), so the rule-shaped `params` argument stays unused.
1440pub fn runtime_lowering_decompose(
1441    graph: &mut Graph,
1442    node_id: NodeId,
1443    _params: &FusedOpParams,
1444) -> NodeId {
1445    decompose_region(graph, node_id)
1446}
1447
1448#[cfg(test)]
1449mod tests {
1450    use super::*;
1451    use fuel_ir::{DType, Shape};
1452
1453    fn relu_add_region() -> PatternNode {
1454        PatternNode::Op {
1455            op: OpTag::Relu,
1456            attrs: OpAttrs::default(),
1457            operands: vec![PatternNode::Op {
1458                op: OpTag::Add,
1459                attrs: OpAttrs::default(),
1460                operands: vec![
1461                    PatternNode::Bind { index: 0 },
1462                    PatternNode::Bind { index: 1 },
1463                ],
1464            }],
1465        }
1466    }
1467
1468    /// Structurally DISTINCT from `relu_add_region()` (`Mul` inner op, not
1469    /// `Add`) — used only by
1470    /// `register_allocates_a_runtime_id_and_keeps_the_region`, whose
1471    /// assertion on `runtime_name` needs a region no OTHER test in this
1472    /// module also registers. Since Task 7's dedup (`register_runtime_fused`
1473    /// above) resolves any two structurally-identical regions registered
1474    /// anywhere in the process to the SAME id — and `RUNTIME_FUSED_OPS` /
1475    /// `hash_index` are process-global statics shared by every `#[test]` in
1476    /// this binary, which `cargo test` runs concurrently by default — a
1477    /// `runtime_name` assertion tied to one specific registration call would
1478    /// be racy against any other test using `relu_add_region()` (both
1479    /// `decompose_region_re_emits_relu_add` and
1480    /// `register_runtime_fused_dedups_structurally_identical_regions` do):
1481    /// whichever call reaches the shared hash slot FIRST wins the name, and
1482    /// thread scheduling decides which that is. Those other two tests never
1483    /// assert on `runtime_name`, so they're unaffected by dedup either way;
1484    /// this one needs its own hash to stay deterministic.
1485    fn relu_mul_region() -> PatternNode {
1486        PatternNode::Op {
1487            op: OpTag::Relu,
1488            attrs: OpAttrs::default(),
1489            operands: vec![PatternNode::Op {
1490                op: OpTag::Mul,
1491                attrs: OpAttrs::default(),
1492                operands: vec![
1493                    PatternNode::Bind { index: 0 },
1494                    PatternNode::Bind { index: 1 },
1495                ],
1496            }],
1497        }
1498    }
1499
1500    #[test]
1501    fn register_allocates_a_runtime_id_and_keeps_the_region() {
1502        let id = register_runtime_fused("test::relu_mul", relu_mul_region()).unwrap();
1503        assert!(id.is_runtime(), "allocated id is in the runtime range");
1504        assert_eq!(runtime_region(id), Some(relu_mul_region()));
1505        assert_eq!(runtime_name(id).as_deref(), Some("test::relu_mul"));
1506    }
1507
1508    #[test]
1509    fn register_runtime_fused_dedups_structurally_identical_regions() {
1510        let id1 = register_runtime_fused("dedup::a", relu_add_region()).unwrap();
1511        let id2 = register_runtime_fused("dedup::b", relu_add_region()).unwrap(); // same region, different name
1512        assert_eq!(
1513            id1, id2,
1514            "an identical region must resolve to the same FusedOpId, not a duplicate"
1515        );
1516    }
1517
1518    #[test]
1519    fn register_rejects_non_contiguous_binds() {
1520        // bind indices {0, 2} — missing 1.
1521        let region = PatternNode::Op {
1522            op: OpTag::Add,
1523            attrs: OpAttrs::default(),
1524            operands: vec![
1525                PatternNode::Bind { index: 0 },
1526                PatternNode::Bind { index: 2 },
1527            ],
1528        };
1529        assert_eq!(
1530            register_runtime_fused("bad", region),
1531            Err(RuntimeFusedError::NonContiguousBinds(vec![0, 2]))
1532        );
1533    }
1534
1535    #[test]
1536    fn register_rejects_unrepresentable_region() {
1537        // Convergence A made MatMul/shape/reduction ops representable, and
1538        // Increment C carriers made MaskedFill (A2) + PowI (A3) representable;
1539        // `Clamp` stays an honest miss (no two-scalar carrier yet), so it is the
1540        // current canonical still-unrepresentable tag.
1541        let region = PatternNode::Op {
1542            op: OpTag::Clamp,
1543            attrs: OpAttrs::default(),
1544            operands: vec![PatternNode::Bind { index: 0 }],
1545        };
1546        assert_eq!(
1547            register_runtime_fused("bad", region),
1548            Err(RuntimeFusedError::UnRepresentable(OpTag::Clamp))
1549        );
1550    }
1551
1552    #[test]
1553    fn tag_to_op_reconstructs_shape_changing_ops() {
1554        use fuel_ir::Shape;
1555        // Slice{dim:1,start:2,len:3}
1556        let attrs = OpAttrs {
1557            axis: Some(1),
1558            slice_start: Some(2),
1559            slice_len: Some(3),
1560            ..OpAttrs::default()
1561        };
1562        assert!(matches!(
1563            super::tag_to_op(OpTag::Slice, &attrs),
1564            Some(Op::Slice {
1565                dim: 1,
1566                start: 2,
1567                len: 3
1568            })
1569        ));
1570        // Concat{dim:0}
1571        let attrs = OpAttrs {
1572            axis: Some(0),
1573            ..OpAttrs::default()
1574        };
1575        assert!(matches!(
1576            super::tag_to_op(OpTag::Concat, &attrs),
1577            Some(Op::Concat { dim: 0 })
1578        ));
1579        // Reshape([6])
1580        let attrs = OpAttrs {
1581            target_shape: vec![6],
1582            ..OpAttrs::default()
1583        };
1584        assert_eq!(
1585            super::tag_to_op(OpTag::Reshape, &attrs),
1586            Some(Op::Reshape(Shape::from_dims(&[6])))
1587        );
1588        // BroadcastTo([2,3])
1589        let attrs = OpAttrs {
1590            target_shape: vec![2, 3],
1591            ..OpAttrs::default()
1592        };
1593        assert_eq!(
1594            super::tag_to_op(OpTag::BroadcastTo, &attrs),
1595            Some(Op::BroadcastTo(Shape::from_dims(&[2, 3])))
1596        );
1597        // ReduceMaxTo([2,1])
1598        let attrs = OpAttrs {
1599            target_shape: vec![2, 1],
1600            ..OpAttrs::default()
1601        };
1602        assert_eq!(
1603            super::tag_to_op(OpTag::ReduceMaxTo, &attrs),
1604            Some(Op::ReduceMaxTo(Shape::from_dims(&[2, 1])))
1605        );
1606    }
1607
1608    #[test]
1609    fn shape_target_ops_represent_rank0_via_the_marker() {
1610        // C-T1: an INTENTIONAL rank-0 (`[]`) reduce/reshape/broadcast target.
1611        // A rank-0 shape has empty `target_shape` — the same empty state as an
1612        // unset/wildcard target — so the `rank0_target` marker disambiguates.
1613        use fuel_ir::Shape;
1614        let empty = Shape::from_dims(&[]);
1615        // Marker SET → the concrete rank-0 shape (RED before the shape_from_attr
1616        // rank0 arm: an empty `target_shape` honest-missed to `None`).
1617        let rank0 = OpAttrs {
1618            rank0_target: true,
1619            ..OpAttrs::default()
1620        };
1621        assert_eq!(super::shape_from_attr(&rank0), Some(empty.clone()));
1622        assert_eq!(
1623            super::tag_to_op(OpTag::ReduceSumTo, &rank0),
1624            Some(Op::ReduceSumTo(empty.clone()))
1625        );
1626        assert_eq!(
1627            super::tag_to_op(OpTag::ReduceMaxTo, &rank0),
1628            Some(Op::ReduceMaxTo(empty.clone()))
1629        );
1630        assert_eq!(
1631            super::tag_to_op(OpTag::Reshape, &rank0),
1632            Some(Op::Reshape(empty.clone()))
1633        );
1634        assert_eq!(
1635            super::tag_to_op(OpTag::BroadcastTo, &rank0),
1636            Some(Op::BroadcastTo(empty))
1637        );
1638        // Marker UNSET + empty target_shape stays an honest miss (wildcard).
1639        assert_eq!(super::shape_from_attr(&OpAttrs::default()), None);
1640        assert_eq!(
1641            super::tag_to_op(OpTag::ReduceSumTo, &OpAttrs::default()),
1642            None
1643        );
1644    }
1645
1646    #[test]
1647    fn tag_to_op_reconstructs_reductions_dtype_and_matmul() {
1648        use fuel_ir::DType;
1649        assert!(matches!(
1650            super::tag_to_op(
1651                OpTag::MeanDim,
1652                &OpAttrs {
1653                    axis: Some(1),
1654                    ..OpAttrs::default()
1655                }
1656            ),
1657            Some(Op::MeanDim(1))
1658        ));
1659        assert!(matches!(
1660            super::tag_to_op(OpTag::MatMul, &OpAttrs::default()),
1661            Some(Op::MatMul)
1662        ));
1663        // Cast target dtype via name.
1664        let attrs = OpAttrs {
1665            cast_dtype: Some("f16".into()),
1666            ..OpAttrs::default()
1667        };
1668        assert_eq!(
1669            super::tag_to_op(OpTag::Cast, &attrs),
1670            Some(Op::Cast(DType::F16))
1671        );
1672        // Comparison.
1673        assert!(matches!(
1674            super::tag_to_op(OpTag::Lt, &OpAttrs::default()),
1675            Some(Op::Lt)
1676        ));
1677    }
1678
1679    #[test]
1680    fn tag_to_op_reconstructs_masked_fill() {
1681        // A2 (Increment C carriers): the MaskedFill re-emit carrier. Value on
1682        // `scalars[0]`, dtype on `cast_dtype` (the `op_to_attrs` projection) —
1683        // RED before A2 (MaskedFill was the `_ => return None` honest miss).
1684        use fuel_ir::Scalar;
1685        let attrs = OpAttrs {
1686            scalars: vec![-1.0],
1687            cast_dtype: Some("f16".into()),
1688            ..OpAttrs::default()
1689        };
1690        match super::tag_to_op(OpTag::MaskedFill, &attrs) {
1691            Some(Op::MaskedFill { value }) => {
1692                assert_eq!(value.dtype(), DType::F16, "dtype rides cast_dtype");
1693                assert_eq!(
1694                    value,
1695                    Scalar::from_f64(-1.0, DType::F16).unwrap(),
1696                    "value rides scalars[0]"
1697                );
1698            }
1699            other => panic!("expected MaskedFill, got {other:?}"),
1700        }
1701        // No fill value ⇒ honest miss (None), never a defaulted 0.
1702        assert_eq!(
1703            super::tag_to_op(OpTag::MaskedFill, &OpAttrs::default()),
1704            None
1705        );
1706        // A dtype `Scalar` cannot represent (sub-byte dummy) ⇒ honest miss.
1707        let dummy = OpAttrs {
1708            scalars: vec![1.0],
1709            cast_dtype: Some("f4".into()),
1710            ..OpAttrs::default()
1711        };
1712        assert_eq!(super::tag_to_op(OpTag::MaskedFill, &dummy), None);
1713        // dtype-polymorphic authoring (no cast_dtype) reconstructs a provisional
1714        // F32 that `emit` later re-resolves to operand[0]'s dtype.
1715        let poly = OpAttrs {
1716            scalars: vec![1.0],
1717            ..OpAttrs::default()
1718        };
1719        assert!(matches!(
1720            super::tag_to_op(OpTag::MaskedFill, &poly),
1721            Some(Op::MaskedFill { .. })
1722        ));
1723        // The fill value is a BAKED pattern constant, not a cursor slot.
1724        assert_eq!(super::scalar_slot_arity(OpTag::MaskedFill), 0);
1725    }
1726
1727    #[test]
1728    fn tag_to_op_reconstructs_powi() {
1729        // A3 (Increment C carriers): the PowI i32-exponent re-emit carrier. The
1730        // exponent rides `scalars[0]` as an f64 (an EXACT round-trip for every
1731        // i32 — |n| < 2^53), reconstructed via `as i32`. RED before A3 (PowI was
1732        // in the `_ => return None` honest-miss set).
1733        assert!(matches!(
1734            super::tag_to_op(
1735                OpTag::PowI,
1736                &OpAttrs {
1737                    scalars: vec![3.0],
1738                    ..OpAttrs::default()
1739                }
1740            ),
1741            Some(Op::PowI(3))
1742        ));
1743        // Negative exponent (e.g. PowI(-1) = reciprocal — the exp==0 backward arm).
1744        assert!(matches!(
1745            super::tag_to_op(
1746                OpTag::PowI,
1747                &OpAttrs {
1748                    scalars: vec![-1.0],
1749                    ..OpAttrs::default()
1750                }
1751            ),
1752            Some(Op::PowI(-1))
1753        ));
1754        // No exponent ⇒ honest miss (None), never a defaulted PowI(0).
1755        assert_eq!(super::tag_to_op(OpTag::PowI, &OpAttrs::default()), None);
1756        // The exponent is a BAKED pattern constant (like MaskedFill's fill), not
1757        // a cursor slot — so it never draws from the params projection.
1758        assert_eq!(super::scalar_slot_arity(OpTag::PowI), 0);
1759    }
1760
1761    #[test]
1762    fn masked_fill_region_registers_and_emits_with_operand_dtype() {
1763        // Before A2 this region was `UnRepresentable(MaskedFill)`; now it
1764        // registers, and `emit` resolves the fill Scalar to operand[0]'s dtype
1765        // (dtype-polymorphic — the imperative `Scalar::one(dtype)` behaviour).
1766        // No `clear_runtime_fused_for_tests()` here: this test asserts on the
1767        // EMITTED op (dtype/value), never on the registry state or the minted
1768        // id, so the clear served nothing — and calling it unserialized in this
1769        // parallel #[test] binary raced every other registry-touching test by
1770        // wiping ids mid-run (GAP-219; the fn's own doc requires callers to
1771        // serialize). Registering a unique region without a clean slate is fine.
1772        let region = PatternNode::Op {
1773            op: OpTag::MaskedFill,
1774            attrs: OpAttrs {
1775                scalars: vec![1.0],
1776                ..OpAttrs::default()
1777            },
1778            operands: vec![
1779                PatternNode::Bind { index: 0 },
1780                PatternNode::Bind { index: 1 },
1781            ],
1782        };
1783        register_runtime_fused("mf::poly", region.clone())
1784            .expect("MaskedFill region registers (was UnRepresentable before A2)");
1785        // Emit over an F16 tensor + U8 mask ⇒ MaskedFill with an F16 fill Scalar.
1786        let mut g = Graph::new();
1787        let x = g.push(Node {
1788            op: Op::Const,
1789            inputs: vec![],
1790            shape: Shape::from_dims(&[2, 3]),
1791            dtype: DType::F16,
1792        });
1793        let mask = g.push(Node {
1794            op: Op::Const,
1795            inputs: vec![],
1796            shape: Shape::from_dims(&[2, 3]),
1797            dtype: DType::U8,
1798        });
1799        let root = emit_region(&mut g, &region, &[x, mask], &[]);
1800        match &g.node(root).op {
1801            Op::MaskedFill { value } => {
1802                assert_eq!(
1803                    value.dtype(),
1804                    DType::F16,
1805                    "fill Scalar re-resolved to operand[0] dtype"
1806                );
1807                assert_eq!(
1808                    value.to_f64(),
1809                    1.0,
1810                    "fill VALUE preserved through the re-resolution"
1811                );
1812            }
1813            other => panic!("expected MaskedFill, got {other:?}"),
1814        }
1815        assert_eq!(g.node(root).dtype, DType::F16);
1816    }
1817
1818    #[test]
1819    fn powi_region_registers_and_emits() {
1820        // Before A3 a bare PowI region was `UnRepresentable(PowI)`; now it
1821        // registers, and `emit` reconstructs the i32 exponent from `scalars[0]`.
1822        // No `clear_runtime_fused_for_tests()` here — see the masked_fill test
1823        // above: this test asserts on the emitted op, not registry state, and
1824        // an unserialized clear in a parallel binary raced other tests (GAP-219).
1825        let region = PatternNode::Op {
1826            op: OpTag::PowI,
1827            attrs: OpAttrs {
1828                scalars: vec![3.0],
1829                ..OpAttrs::default()
1830            },
1831            operands: vec![PatternNode::Bind { index: 0 }],
1832        };
1833        register_runtime_fused("powi::cube", region.clone())
1834            .expect("PowI region registers (was UnRepresentable before A3)");
1835        let mut g = Graph::new();
1836        let x = g.push(Node {
1837            op: Op::Const,
1838            inputs: vec![],
1839            shape: Shape::from_dims(&[2, 3]),
1840            dtype: DType::F32,
1841        });
1842        let root = emit_region(&mut g, &region, &[x], &[]);
1843        assert!(
1844            matches!(g.node(root).op, Op::PowI(3)),
1845            "emit reconstructs Op::PowI(3) from scalars[0]",
1846        );
1847        assert_eq!(
1848            g.node(root).shape,
1849            Shape::from_dims(&[2, 3]),
1850            "PowI is shape-preserving"
1851        );
1852        assert_eq!(g.node(root).dtype, DType::F32);
1853    }
1854
1855    #[test]
1856    fn tag_to_op_matmul_resolves_canonical_roles() {
1857        // T9 (D5): explicit CANONICAL role vectors resolve to Op::MatMul. The
1858        // resolver checks role POSITIONS against the locked cell, not extents.
1859        let attrs = OpAttrs {
1860            lhs_roles: vec![1, 3],
1861            rhs_roles: vec![3, 2],
1862            ..OpAttrs::default()
1863        };
1864        assert!(matches!(
1865            super::tag_to_op(OpTag::MatMul, &attrs),
1866            Some(Op::MatMul)
1867        ));
1868        // Rank-4 canonical (leading Batch dims) also resolves — GQA-divisible
1869        // batch extents stay all-Batch (positions, not extents).
1870        let attrs4 = OpAttrs {
1871            lhs_roles: vec![0, 0, 1, 3],
1872            rhs_roles: vec![0, 0, 3, 2],
1873            ..OpAttrs::default()
1874        };
1875        assert!(matches!(
1876            super::tag_to_op(OpTag::MatMul, &attrs4),
1877            Some(Op::MatMul)
1878        ));
1879    }
1880
1881    #[test]
1882    fn tag_to_op_matmul_empty_roles_implicit_accept() {
1883        // Empty roles = the rank-polymorphic recipe form → implicit-accept
1884        // (unchanged from today; recipes keep matmul implicit).
1885        assert!(matches!(
1886            super::tag_to_op(OpTag::MatMul, &OpAttrs::default()),
1887            Some(Op::MatMul)
1888        ));
1889    }
1890
1891    #[test]
1892    fn tag_to_op_matmul_rejects_noncanonical_roles() {
1893        // Non-canonical role configs are a SURFACED honest miss (typed decline at
1894        // registration), never a crash.
1895        // (1) transposed lhs = [ContractedK, FreeM] = [3,1] instead of [1,3].
1896        let transposed = OpAttrs {
1897            lhs_roles: vec![3, 1],
1898            rhs_roles: vec![3, 2],
1899            ..OpAttrs::default()
1900        };
1901        assert_eq!(super::tag_to_op(OpTag::MatMul, &transposed), None);
1902        // (2) multi-ContractedK on lhs = [3,3].
1903        let multi_k = OpAttrs {
1904            lhs_roles: vec![3, 3],
1905            rhs_roles: vec![3, 2],
1906            ..OpAttrs::default()
1907        };
1908        assert_eq!(super::tag_to_op(OpTag::MatMul, &multi_k), None);
1909        // (3) FreeN-before-K on rhs = [FreeN, ContractedK] = [2,3] instead of [3,2].
1910        let freen_before_k = OpAttrs {
1911            lhs_roles: vec![1, 3],
1912            rhs_roles: vec![2, 3],
1913            ..OpAttrs::default()
1914        };
1915        assert_eq!(super::tag_to_op(OpTag::MatMul, &freen_before_k), None);
1916    }
1917
1918    #[test]
1919    fn tag_to_op_reconstructs_max_dim() {
1920        // T4 (Increment C slice 1): OpTag::MaxDim → Op::MaxDim(axis), the
1921        // axis riding `attrs.axis` exactly like SumDim/MeanDim.
1922        assert!(matches!(
1923            super::tag_to_op(
1924                OpTag::MaxDim,
1925                &OpAttrs {
1926                    axis: Some(1),
1927                    ..OpAttrs::default()
1928                }
1929            ),
1930            Some(Op::MaxDim(1))
1931        ));
1932        // An unset axis is an honest miss (typed decline at registration),
1933        // never a defaulted axis.
1934        assert_eq!(super::tag_to_op(OpTag::MaxDim, &OpAttrs::default()), None);
1935        // Not a scalar-param op: zero scalar slots.
1936        assert_eq!(super::scalar_slot_arity(OpTag::MaxDim), 0);
1937    }
1938
1939    #[test]
1940    fn max_dim_axis_last_resolves_to_rank_minus_one() {
1941        // D3 consumer: migrated recipes spell keepdim as MaxDim(axis_last) +
1942        // Unsqueeze(append), so MaxDim must be an `axis`-carrier tag for the
1943        // rel-attr resolver (rank − 1 via the shared §6.20 LAST resolver).
1944        let attrs = OpAttrs {
1945            axis_last: true,
1946            ..OpAttrs::default()
1947        };
1948        let resolved =
1949            super::resolve_rel_attrs(OpTag::MaxDim, &attrs, &[vec![2, 3, 4]], &[vec![2, 3, 4]])
1950                .expect("axis_last must resolve on MaxDim, not AxisLastUnsupported");
1951        assert_eq!(resolved.axis, Some(2), "rank-3 operand → LAST = axis 2");
1952        assert!(
1953            !resolved.axis_last,
1954            "rel carrier must be cleared post-resolve"
1955        );
1956    }
1957
1958    #[test]
1959    fn tag_to_op_still_rejects_basis_gap_and_scan() {
1960        // qmatmul/conv flow through Op::Fused (no OpTag); Scan is higher-order.
1961        assert_eq!(
1962            super::tag_to_op(OpTag::Iota, &OpAttrs::default()),
1963            None,
1964            "Iota needs a len (target_shape) — empty attrs is a miss"
1965        );
1966    }
1967
1968    // ---- C-T2 (Increment C): the nested-fused re-emit carrier (mechanism 2a) --
1969
1970    /// `OpTag::Fused` reconstructs the param-less nested fused ops from the
1971    /// `fused_op` selector (name → fid → params), and honest-misses anything
1972    /// else. Born-red before the C-T2 `tag_to_op` arm: `Fused` falls to the
1973    /// `_ => return None` catch-all, so every `Some(..)` assertion below fails.
1974    #[test]
1975    fn tag_to_op_reconstructs_nested_fused() {
1976        use crate::registry::{FusedOpParams, FusedOps};
1977        let sm = OpAttrs {
1978            fused_op: Some("SoftmaxLastDim".into()),
1979            ..OpAttrs::default()
1980        };
1981        assert_eq!(
1982            super::tag_to_op(OpTag::Fused, &sm),
1983            Some(Op::Fused(
1984                FusedOps::SOFTMAX_LAST_DIM,
1985                FusedOpParams::SoftmaxLastDim
1986            )),
1987            "SoftmaxLastDim selector reconstructs the param-less nested fused op",
1988        );
1989        let smb = OpAttrs {
1990            fused_op: Some("SoftmaxLastDimBackward".into()),
1991            ..OpAttrs::default()
1992        };
1993        assert_eq!(
1994            super::tag_to_op(OpTag::Fused, &smb),
1995            Some(Op::Fused(
1996                FusedOps::SOFTMAX_LAST_DIM_BACKWARD,
1997                FusedOpParams::SoftmaxLastDimBackward,
1998            )),
1999            "SoftmaxLastDimBackward selector reconstructs its param-less nested fused op",
2000        );
2001        // Honest misses (never a crash): unset selector, unknown name, and a
2002        // param-carrying fused op (not round-trippable through the name-only
2003        // selector — its params can't be reconstructed from the map).
2004        assert_eq!(
2005            super::tag_to_op(OpTag::Fused, &OpAttrs::default()),
2006            None,
2007            "an unset fused_op selector is an honest miss",
2008        );
2009        let bogus = OpAttrs {
2010            fused_op: Some("NotARealFusedOp".into()),
2011            ..OpAttrs::default()
2012        };
2013        assert_eq!(
2014            super::tag_to_op(OpTag::Fused, &bogus),
2015            None,
2016            "an unknown name is an honest miss"
2017        );
2018        let paged = OpAttrs {
2019            fused_op: Some("PagedAttn".into()),
2020            ..OpAttrs::default()
2021        };
2022        assert_eq!(
2023            super::tag_to_op(OpTag::Fused, &paged),
2024            None,
2025            "a registered but param-carrying fused op is an honest miss (not in the fid->params map)",
2026        );
2027    }
2028
2029    /// A hand-built recipe carrying a nested `Op::Fused(SOFTMAX_LAST_DIM)` node
2030    /// validates as re-emittable and round-trips through `emit`: the emitted node
2031    /// is the real nested fused op, with shape/dtype from the registry entry's
2032    /// `shape_rule`/`dtype_rule` (softmax passes both through) since
2033    /// `primitive_shape` honest-misses `Fused`. Born-red before the C-T2 arms:
2034    /// `validate_recipe` rejects the region `UnRepresentable`.
2035    #[test]
2036    fn emit_reconstructs_nested_fused_shape_and_dtype() {
2037        use crate::registry::{FusedOpParams, FusedOps};
2038        use fuel_ir::{DType, Shape};
2039        let region = PatternNode::Op {
2040            op: OpTag::Fused,
2041            attrs: OpAttrs {
2042                fused_op: Some("SoftmaxLastDim".into()),
2043                ..OpAttrs::default()
2044            },
2045            operands: vec![PatternNode::Bind { index: 0 }],
2046        };
2047        assert!(
2048            super::validate_recipe(&region).is_ok(),
2049            "the nested-fused recipe validates as re-emittable (C-T2 tag_to_op arm)",
2050        );
2051        let mut g = Graph::new();
2052        let x = g.push(Node {
2053            op: Op::Const,
2054            inputs: vec![],
2055            shape: Shape::from_dims(&[2, 3, 4]),
2056            dtype: DType::F32,
2057        });
2058        let root = emit_region(&mut g, &region, &[x], &[]);
2059        assert_eq!(
2060            g.node(root).op,
2061            Op::Fused(FusedOps::SOFTMAX_LAST_DIM, FusedOpParams::SoftmaxLastDim),
2062            "the nested fused node is reconstructed as-is (2a)",
2063        );
2064        assert_eq!(
2065            g.node(root).shape,
2066            Shape::from_dims(&[2, 3, 4]),
2067            "shape from softmax shape_rule (passthrough), NOT primitive_shape (which misses Fused)",
2068        );
2069        assert_eq!(
2070            g.node(root).dtype,
2071            DType::F32,
2072            "dtype from softmax dtype_rule (passthrough)"
2073        );
2074        assert_eq!(
2075            g.node(root).inputs,
2076            vec![x],
2077            "the fused node's single operand is the bound input"
2078        );
2079    }
2080
2081    #[test]
2082    fn validate_representable_now_accepts_a_slice_region() {
2083        // Region: Concat{0}(Neg(Slice{...}(bind0)), bind0) — the rope rotate-half shape.
2084        let region = PatternNode::Op {
2085            op: OpTag::Concat,
2086            attrs: OpAttrs {
2087                axis: Some(0),
2088                ..OpAttrs::default()
2089            },
2090            operands: vec![
2091                PatternNode::Op {
2092                    op: OpTag::Neg,
2093                    attrs: OpAttrs::default(),
2094                    operands: vec![PatternNode::Op {
2095                        op: OpTag::Slice,
2096                        attrs: OpAttrs {
2097                            axis: Some(0),
2098                            slice_start: Some(0),
2099                            slice_len: Some(1),
2100                            ..OpAttrs::default()
2101                        },
2102                        operands: vec![PatternNode::Bind { index: 0 }],
2103                    }],
2104                },
2105                PatternNode::Bind { index: 0 },
2106            ],
2107        };
2108        assert!(
2109            super::validate_representable(&region).is_ok(),
2110            "slice/concat region must now validate"
2111        );
2112    }
2113
2114    #[test]
2115    fn emit_gets_shape_right_for_a_reduction_region() {
2116        use fuel_ir::{DType, Shape};
2117        // Region: ReduceSumTo([2,1])(bind0). Input [2,5] → output [2,1].
2118        let region = PatternNode::Op {
2119            op: OpTag::ReduceSumTo,
2120            attrs: OpAttrs {
2121                target_shape: vec![2, 1],
2122                ..OpAttrs::default()
2123            },
2124            operands: vec![PatternNode::Bind { index: 0 }],
2125        };
2126        let mut g = Graph::new();
2127        let x = g.push(Node {
2128            op: Op::Const,
2129            inputs: vec![],
2130            shape: Shape::from_dims(&[2, 5]),
2131            dtype: DType::F32,
2132        });
2133        let root = emit_region(&mut g, &region, &[x], &[]);
2134        assert!(matches!(g.node(root).op, Op::ReduceSumTo(_)));
2135        assert_eq!(
2136            g.node(root).shape,
2137            Shape::from_dims(&[2, 1]),
2138            "emit must use the reduced shape, not operand[0]"
2139        );
2140        assert_eq!(g.node(root).dtype, DType::F32);
2141    }
2142
2143    #[test]
2144    fn emit_gets_dtype_right_for_a_cast_region() {
2145        use fuel_ir::{DType, Shape};
2146        // Region: Cast(F16)(bind0). Input F32 → output F16, same shape.
2147        let region = PatternNode::Op {
2148            op: OpTag::Cast,
2149            attrs: OpAttrs {
2150                cast_dtype: Some("f16".into()),
2151                ..OpAttrs::default()
2152            },
2153            operands: vec![PatternNode::Bind { index: 0 }],
2154        };
2155        let mut g = Graph::new();
2156        let x = g.push(Node {
2157            op: Op::Const,
2158            inputs: vec![],
2159            shape: Shape::from_dims(&[3, 3]),
2160            dtype: DType::F32,
2161        });
2162        let root = emit_region(&mut g, &region, &[x], &[]);
2163        assert!(matches!(g.node(root).op, Op::Cast(DType::F16)));
2164        assert_eq!(
2165            g.node(root).dtype,
2166            DType::F16,
2167            "emit must take Cast's target dtype, not operand[0]'s"
2168        );
2169        assert_eq!(g.node(root).shape, Shape::from_dims(&[3, 3]));
2170    }
2171
2172    #[test]
2173    fn emit_zero_operand_representable_region_is_panic_free() {
2174        // M-1 never-panic hardening: a MALFORMED region — a binary op given ZERO
2175        // operands. `validate_representable` accepts it (it checks
2176        // `tag_to_op(op).is_some()`, NOT arity), and `emit_region` is a public
2177        // raw-region entry (candidate-kernel verification) that does not
2178        // re-validate. `primitive_shape(Add, [], [])` errs, so the fallback runs
2179        // with an EMPTY child_shapes — it must NOT index-panic. emit stays total:
2180        // it returns a node (with a degenerate rank-0 shape), never a panic.
2181        let region = PatternNode::Op {
2182            op: OpTag::Add,
2183            attrs: OpAttrs::default(),
2184            operands: vec![],
2185        };
2186        let mut g = Graph::new();
2187        let root = emit_region(&mut g, &region, &[], &[]);
2188        assert!(
2189            matches!(g.node(root).op, Op::Add),
2190            "emit returns a node, not a panic"
2191        );
2192    }
2193
2194    #[test]
2195    fn decompose_region_re_emits_relu_add() {
2196        let id = register_runtime_fused("test::relu_add::decompose", relu_add_region()).unwrap();
2197        let mut g = Graph::new();
2198        let s = Shape::from_dims(&[4]);
2199        let a = g.push(Node {
2200            op: Op::Const,
2201            inputs: vec![],
2202            shape: s.clone(),
2203            dtype: DType::F32,
2204        });
2205        let b = g.push(Node {
2206            op: Op::Const,
2207            inputs: vec![],
2208            shape: s.clone(),
2209            dtype: DType::F32,
2210        });
2211        let fused = g.push(Node {
2212            op: Op::Fused(id, FusedOpParams::Runtime { scalars: vec![] }),
2213            inputs: vec![a, b],
2214            shape: s.clone(),
2215            dtype: DType::F32,
2216        });
2217
2218        let root = decompose_region(&mut g, fused);
2219
2220        // The re-emitted sink is Relu over Add(a, b) — the region, on primitives.
2221        assert!(matches!(g.node(root).op, Op::Relu));
2222        let add_id = g.node(root).inputs[0];
2223        assert!(matches!(g.node(add_id).op, Op::Add));
2224        assert_eq!(g.node(add_id).inputs, vec![a, b]);
2225        // Shapes propagated from the leaves (same-shape elementwise).
2226        assert_eq!(g.node(root).shape, s);
2227        assert_eq!(g.node(add_id).shape, s);
2228    }
2229
2230    // ---- scalar slots (the `extract:` substitution) ---------------------
2231
2232    /// tanh(mul_scalar(a)) with the scalar left OPEN (a slot template).
2233    fn tanh_mul_scalar_slot_region() -> PatternNode {
2234        PatternNode::Op {
2235            op: OpTag::Tanh,
2236            attrs: OpAttrs::default(),
2237            operands: vec![PatternNode::Op {
2238                op: OpTag::MulScalar,
2239                attrs: OpAttrs::default(), // empty scalars = an open slot
2240                operands: vec![PatternNode::Bind { index: 0 }],
2241            }],
2242        }
2243    }
2244
2245    #[test]
2246    fn slot_template_registers_and_counts() {
2247        // Born-red before slot support: validation rejected an AddScalar/
2248        // MulScalar pattern node with no baked value.
2249        let id =
2250            register_runtime_fused("test::tanh_mul_scalar::slot", tanh_mul_scalar_slot_region())
2251                .expect("a slot template is registrable");
2252        let region = runtime_region(id).unwrap();
2253        assert_eq!(count_scalar_slots(&region), 1, "one open slot");
2254    }
2255
2256    #[test]
2257    fn decompose_fills_slots_from_the_node_scalars() {
2258        let id =
2259            register_runtime_fused("test::tanh_mul_scalar::fill", tanh_mul_scalar_slot_region())
2260                .unwrap();
2261        let mut g = Graph::new();
2262        let s = Shape::from_dims(&[4]);
2263        let a = g.push(Node {
2264            op: Op::Const,
2265            inputs: vec![],
2266            shape: s.clone(),
2267            dtype: DType::F32,
2268        });
2269        let fused = g.push(Node {
2270            op: Op::Fused(id, FusedOpParams::Runtime { scalars: vec![2.5] }),
2271            inputs: vec![a],
2272            shape: s.clone(),
2273            dtype: DType::F32,
2274        });
2275
2276        let root = decompose_region(&mut g, fused);
2277
2278        // tanh(mul_scalar(a, 2.5)) — the LIVE value filled the slot.
2279        assert!(matches!(g.node(root).op, Op::Tanh));
2280        let ms = g.node(root).inputs[0];
2281        assert!(
2282            matches!(g.node(ms).op, Op::MulScalar(v) if v == 2.5),
2283            "slot filled with the node's live scalar, got {:?}",
2284            g.node(ms).op,
2285        );
2286        assert_eq!(g.node(ms).inputs, vec![a]);
2287    }
2288
2289    #[test]
2290    fn decompose_slot_count_mismatch_is_a_fixpoint_not_a_crash() {
2291        let id = register_runtime_fused(
2292            "test::tanh_mul_scalar::mismatch",
2293            tanh_mul_scalar_slot_region(),
2294        )
2295        .unwrap();
2296        let mut g = Graph::new();
2297        let s = Shape::from_dims(&[4]);
2298        let a = g.push(Node {
2299            op: Op::Const,
2300            inputs: vec![],
2301            shape: s.clone(),
2302            dtype: DType::F32,
2303        });
2304        // One slot, but the node carries NO scalars — malformed; must be a
2305        // no-op fixpoint (G2), never a panic.
2306        let fused = g.push(Node {
2307            op: Op::Fused(id, FusedOpParams::Runtime { scalars: vec![] }),
2308            inputs: vec![a],
2309            shape: s.clone(),
2310            dtype: DType::F32,
2311        });
2312        assert_eq!(
2313            decompose_region(&mut g, fused),
2314            fused,
2315            "mismatch ⇒ fixpoint"
2316        );
2317    }
2318
2319    // ---- shape-derived scalar slot (A1: the reduced_count live-emission) -----
2320
2321    /// `mul_scalar(a)` whose scalar is a SHAPE-DERIVED value: `scalar_rel =
2322    /// Extent(operand 0, LAST)` — the reduced_count of bind 0's last axis, filled
2323    /// from the input shape at emit time (NOT from the params cursor). This is
2324    /// the narrow carrier a norm-backward's `MulScalar(n = dims[last])` needs.
2325    fn mul_scalar_reduced_count_region() -> PatternNode {
2326        use fuel_kernel_seam_types::shape_expr::{Dim, LAST};
2327        PatternNode::Op {
2328            op: OpTag::MulScalar,
2329            attrs: OpAttrs {
2330                scalar_rel: Some(Dim::Extent {
2331                    operand: 0,
2332                    axis: LAST,
2333                }),
2334                ..OpAttrs::default()
2335            },
2336            operands: vec![PatternNode::Bind { index: 0 }],
2337        }
2338    }
2339
2340    /// A1 born-red: a `scalar_rel` node is filled from an input SHAPE, so it is
2341    /// NOT a params-cursor slot (`count_scalar_slots == 0`) and its emitted value
2342    /// is the reduced_count of the resolving input's last axis. RED before the
2343    /// wiring: `count_scalar_slots` counts the empty-`scalars` MulScalar as one
2344    /// open slot, and `emit` ignores `scalar_rel` (fills from the cursor).
2345    #[test]
2346    fn scalar_rel_is_shape_derived_not_a_cursor_slot() {
2347        let region = mul_scalar_reduced_count_region();
2348        assert_eq!(
2349            count_scalar_slots(&region),
2350            0,
2351            "a scalar_rel node is shape-derived, never a params-cursor slot",
2352        );
2353        // Emit over x[3,5]: n = last extent = 5, with NO cursor scalars.
2354        let mut g = Graph::new();
2355        let x = g.push(Node {
2356            op: Op::Const,
2357            inputs: vec![],
2358            shape: Shape::from_dims(&[3, 5]),
2359            dtype: DType::F32,
2360        });
2361        let root = try_emit_region(&mut g, &region, &[x], &[])
2362            .expect("scalar_rel resolves against the input shape");
2363        assert!(
2364            matches!(g.node(root).op, Op::MulScalar(v) if v == 5.0),
2365            "scalar_rel = Extent(0, LAST) over x[3,5] ⇒ MulScalar(5.0), got {:?}",
2366            g.node(root).op,
2367        );
2368        assert_eq!(g.node(root).inputs, vec![x]);
2369        // Rank-polymorphic: the SAME datum resolves to 7 over x[..,7].
2370        let mut g3 = Graph::new();
2371        let x3 = g3.push(Node {
2372            op: Op::Const,
2373            inputs: vec![],
2374            shape: Shape::from_dims(&[2, 4, 7]),
2375            dtype: DType::F32,
2376        });
2377        let root3 = try_emit_region(&mut g3, &region, &[x3], &[]).unwrap();
2378        assert!(
2379            matches!(g3.node(root3).op, Op::MulScalar(v) if v == 7.0),
2380            "same datum, x[2,4,7] ⇒ MulScalar(7.0), got {:?}",
2381            g3.node(root3).op,
2382        );
2383    }
2384
2385    /// A1: a `scalar_rel` set together with a non-empty `scalars` is a typed
2386    /// rel-XOR-abs authoring conflict at emit (never a silent precedence).
2387    #[test]
2388    fn scalar_rel_and_baked_scalar_together_is_a_typed_conflict() {
2389        use fuel_kernel_seam_types::shape_expr::{Dim, LAST};
2390        let region = PatternNode::Op {
2391            op: OpTag::MulScalar,
2392            attrs: OpAttrs {
2393                scalars: vec![2.0],
2394                scalar_rel: Some(Dim::Extent {
2395                    operand: 0,
2396                    axis: LAST,
2397                }),
2398                ..OpAttrs::default()
2399            },
2400            operands: vec![PatternNode::Bind { index: 0 }],
2401        };
2402        let mut g = Graph::new();
2403        let x = g.push(Node {
2404            op: Op::Const,
2405            inputs: vec![],
2406            shape: Shape::from_dims(&[3, 5]),
2407            dtype: DType::F32,
2408        });
2409        assert_eq!(
2410            try_emit_region(&mut g, &region, &[x], &[]),
2411            Err(RelAttrError::RelAbsConflict { field: "scalars" }),
2412            "scalar_rel XOR scalars — both set is a typed decline",
2413        );
2414    }
2415
2416    // ---- Task 5: byte-for-byte emit == registry::*::decompose parity --------
2417    //
2418    // The A.4 acceptance gate: express each hand-written decompose as a
2419    // PatternNode region, re-emit it via the grown `emit`, and assert the
2420    // result is structurally identical (op + shape + dtype at every node) to
2421    // the decompose-oracle output — the migration oracle. Since T5, `emit`
2422    // identity-shares repeated slot-free subtrees within one call, so a
2423    // shared oracle node compares against an equally-shared emitted node;
2424    // `assert_structural_eq` is recursive + order-sensitive (no commutative
2425    // canonicalization — stricter than `base_map_hash`), catching an
2426    // operand-swap the hash would mask.
2427
2428    fn op_node(op: OpTag, attrs: OpAttrs, operands: Vec<PatternNode>) -> PatternNode {
2429        PatternNode::Op {
2430            op,
2431            attrs,
2432            operands,
2433        }
2434    }
2435    fn bind(i: u8) -> PatternNode {
2436        PatternNode::Bind { index: i }
2437    }
2438
2439    /// Recursively assert two subgraphs are identical: same Op, shape, dtype,
2440    /// arity, and recursively-equal inputs. Shared leaves (same NodeId) match
2441    /// by identity. This is the "byte-for-byte" node-structure check.
2442    fn assert_structural_eq(g: &Graph, a: NodeId, b: NodeId) {
2443        if a == b {
2444            return; // shared leaf (bound external input)
2445        }
2446        let na = g.node(a);
2447        let nb = g.node(b);
2448        assert_eq!(na.op, nb.op, "op mismatch: {:?} vs {:?}", na.op, nb.op);
2449        assert_eq!(
2450            na.shape, nb.shape,
2451            "shape mismatch at {:?} vs {:?}",
2452            na.op, nb.op
2453        );
2454        assert_eq!(na.dtype, nb.dtype, "dtype mismatch at {:?}", na.op);
2455        assert_eq!(
2456            na.inputs.len(),
2457            nb.inputs.len(),
2458            "arity mismatch at {:?}",
2459            na.op
2460        );
2461        for (&ia, &ib) in na.inputs.iter().zip(nb.inputs.iter()) {
2462            assert_structural_eq(g, ia, ib);
2463        }
2464    }
2465
2466    /// FROZEN copy of the pre-migration imperative
2467    /// `registry::softmax_last_dim::decompose` (the legacy 7-node
2468    /// `ReduceMaxTo`/`ReduceSumTo` keepdim spelling), copied VERBATIM from
2469    /// that module @ `af4b7dd4` before T5 replaced the live body with the
2470    /// data recipe. Two consumers: the T5 numeric-parity oracle
2471    /// (`recipe_bridge` below) and `emit_matches_softmax_last_dim_decompose`
2472    /// (whose oracle was repointed here — the live decompose no longer emits
2473    /// this spelling).
2474    fn frozen_legacy_softmax_decompose(
2475        graph: &mut Graph,
2476        id: NodeId,
2477        _params: &FusedOpParams,
2478    ) -> NodeId {
2479        let (x_id, x_shape, dtype) = {
2480            let n = graph.node(id);
2481            (n.inputs[0], n.shape.clone(), n.dtype)
2482        };
2483        let dims = x_shape.dims().to_vec();
2484        let rank = dims.len();
2485        let last = rank - 1;
2486
2487        let mut keepdim_dims = dims.clone();
2488        keepdim_dims[last] = 1;
2489        let keepdim_shape = Shape::from_dims(&keepdim_dims);
2490
2491        let m_id = graph.push(Node {
2492            op: Op::ReduceMaxTo(keepdim_shape.clone()),
2493            inputs: vec![x_id],
2494            shape: keepdim_shape.clone(),
2495            dtype,
2496        });
2497        let mb_id = graph.push(Node {
2498            op: Op::BroadcastTo(x_shape.clone()),
2499            inputs: vec![m_id],
2500            shape: x_shape.clone(),
2501            dtype,
2502        });
2503        let s_id = graph.push(Node {
2504            op: Op::Sub,
2505            inputs: vec![x_id, mb_id],
2506            shape: x_shape.clone(),
2507            dtype,
2508        });
2509        let e_id = graph.push(Node {
2510            op: Op::Exp,
2511            inputs: vec![s_id],
2512            shape: x_shape.clone(),
2513            dtype,
2514        });
2515        let d_id = graph.push(Node {
2516            op: Op::ReduceSumTo(keepdim_shape.clone()),
2517            inputs: vec![e_id],
2518            shape: keepdim_shape,
2519            dtype,
2520        });
2521        let db_id = graph.push(Node {
2522            op: Op::BroadcastTo(x_shape.clone()),
2523            inputs: vec![d_id],
2524            shape: x_shape.clone(),
2525            dtype,
2526        });
2527
2528        graph.push(Node {
2529            op: Op::Div,
2530            inputs: vec![e_id, db_id],
2531            shape: x_shape,
2532            dtype,
2533        })
2534    }
2535
2536    /// FROZEN copy of the pre-migration imperative
2537    /// `registry::rope::decompose` (the legacy 11-node spelling with two
2538    /// leading-1-padded `Reshape` prep nodes), copied VERBATIM from that
2539    /// module @ `af4b7dd4` before T6 replaced the live body with the data
2540    /// recipe. Two consumers: `emit_matches_rope_decompose` (whose oracle was
2541    /// repointed here — the live decompose now emits the recipe, which at
2542    /// EQUAL rank elides the no-op prep `Reshape`, D4) and the T6
2543    /// numeric/structural parity oracle (`rope_recipe` below).
2544    fn frozen_legacy_rope_decompose(
2545        graph: &mut Graph,
2546        id: NodeId,
2547        _params: &FusedOpParams,
2548    ) -> NodeId {
2549        let (x_id, cos_id, sin_id, x_shape, dtype) = {
2550            let n = graph.node(id);
2551            (
2552                n.inputs[0],
2553                n.inputs[1],
2554                n.inputs[2],
2555                n.shape.clone(),
2556                n.dtype,
2557            )
2558        };
2559        let dims = x_shape.dims().to_vec();
2560        let rank = dims.len();
2561        let seq = dims[rank - 2];
2562        let d = dims[rank - 1];
2563        let half = d / 2;
2564        let last = rank - 1;
2565
2566        let mut broadcast_shape_dims: Vec<usize> = vec![1usize; rank];
2567        broadcast_shape_dims[rank - 2] = seq;
2568        broadcast_shape_dims[rank - 1] = d;
2569        let broadcast_shape = Shape::from_dims(&broadcast_shape_dims);
2570
2571        let cos_reshaped_id = graph.push(Node {
2572            op: Op::Reshape(broadcast_shape.clone()),
2573            inputs: vec![cos_id],
2574            shape: broadcast_shape.clone(),
2575            dtype,
2576        });
2577        let sin_reshaped_id = graph.push(Node {
2578            op: Op::Reshape(broadcast_shape.clone()),
2579            inputs: vec![sin_id],
2580            shape: broadcast_shape,
2581            dtype,
2582        });
2583        let cos_bcast_id = graph.push(Node {
2584            op: Op::BroadcastTo(x_shape.clone()),
2585            inputs: vec![cos_reshaped_id],
2586            shape: x_shape.clone(),
2587            dtype,
2588        });
2589        let sin_bcast_id = graph.push(Node {
2590            op: Op::BroadcastTo(x_shape.clone()),
2591            inputs: vec![sin_reshaped_id],
2592            shape: x_shape.clone(),
2593            dtype,
2594        });
2595
2596        let mut half_dims = dims.clone();
2597        half_dims[last] = half;
2598        let half_shape = Shape::from_dims(&half_dims);
2599
2600        let first_half_id = graph.push(Node {
2601            op: Op::Slice {
2602                dim: last,
2603                start: 0,
2604                len: half,
2605            },
2606            inputs: vec![x_id],
2607            shape: half_shape.clone(),
2608            dtype,
2609        });
2610        let second_half_id = graph.push(Node {
2611            op: Op::Slice {
2612                dim: last,
2613                start: half,
2614                len: half,
2615            },
2616            inputs: vec![x_id],
2617            shape: half_shape.clone(),
2618            dtype,
2619        });
2620        let neg_second_id = graph.push(Node {
2621            op: Op::Neg,
2622            inputs: vec![second_half_id],
2623            shape: half_shape,
2624            dtype,
2625        });
2626        let rotated_half_id = graph.push(Node {
2627            op: Op::Concat { dim: last },
2628            inputs: vec![neg_second_id, first_half_id],
2629            shape: x_shape.clone(),
2630            dtype,
2631        });
2632
2633        let left_id = graph.push(Node {
2634            op: Op::Mul,
2635            inputs: vec![x_id, cos_bcast_id],
2636            shape: x_shape.clone(),
2637            dtype,
2638        });
2639        let right_id = graph.push(Node {
2640            op: Op::Mul,
2641            inputs: vec![rotated_half_id, sin_bcast_id],
2642            shape: x_shape.clone(),
2643            dtype,
2644        });
2645
2646        graph.push(Node {
2647            op: Op::Add,
2648            inputs: vec![left_id, right_id],
2649            shape: x_shape,
2650            dtype,
2651        })
2652    }
2653
2654    /// FROZEN copy of the pre-migration imperative
2655    /// `registry::rms_norm_last_dim::decompose` (the legacy 7-node
2656    /// `MeanDim → Reshape(keepdim) → AddScalar(eps)` spelling), copied VERBATIM
2657    /// from that module before T7 replaced the live body with the data recipe.
2658    /// Consumer: the T7 numeric-parity oracle (`norm_recipe` below). The live
2659    /// decompose now emits the D3 shrink-via-swap spelling (`Unsqueeze` append
2660    /// in place of `Reshape(keepdim)`), so the parity test evaluates BOTH
2661    /// through the shared reference interpreter and asserts bit-exact
2662    /// equivalence (the swap is metadata-only).
2663    fn frozen_legacy_rms_norm_decompose(
2664        graph: &mut Graph,
2665        id: NodeId,
2666        params: &FusedOpParams,
2667    ) -> NodeId {
2668        let (x_id, x_shape, dtype) = {
2669            let n = graph.node(id);
2670            (n.inputs[0], n.shape.clone(), n.dtype)
2671        };
2672        let eps = match params {
2673            FusedOpParams::RmsNormLastDim { eps } => *eps,
2674            _ => return id,
2675        };
2676        let dims = x_shape.dims().to_vec();
2677        let rank = dims.len();
2678        let last = rank - 1;
2679
2680        let mut keepdim_dims = dims.clone();
2681        keepdim_dims[last] = 1;
2682        let keepdim_shape = Shape::from_dims(&keepdim_dims);
2683        let mut reduced_dims = dims.clone();
2684        reduced_dims.remove(last);
2685        let reduced_shape = Shape::from_dims(&reduced_dims);
2686
2687        let sq_id = graph.push(Node {
2688            op: Op::Sqr,
2689            inputs: vec![x_id],
2690            shape: x_shape.clone(),
2691            dtype,
2692        });
2693        let mean_id = graph.push(Node {
2694            op: Op::MeanDim(last),
2695            inputs: vec![sq_id],
2696            shape: reduced_shape,
2697            dtype,
2698        });
2699        let mean_kd_id = graph.push(Node {
2700            op: Op::Reshape(keepdim_shape.clone()),
2701            inputs: vec![mean_id],
2702            shape: keepdim_shape.clone(),
2703            dtype,
2704        });
2705        let denom_sq_id = graph.push(Node {
2706            op: Op::AddScalar(eps),
2707            inputs: vec![mean_kd_id],
2708            shape: keepdim_shape.clone(),
2709            dtype,
2710        });
2711        let denom_id = graph.push(Node {
2712            op: Op::Sqrt,
2713            inputs: vec![denom_sq_id],
2714            shape: keepdim_shape,
2715            dtype,
2716        });
2717        let denom_bcast_id = graph.push(Node {
2718            op: Op::BroadcastTo(x_shape.clone()),
2719            inputs: vec![denom_id],
2720            shape: x_shape.clone(),
2721            dtype,
2722        });
2723        graph.push(Node {
2724            op: Op::Div,
2725            inputs: vec![x_id, denom_bcast_id],
2726            shape: x_shape,
2727            dtype,
2728        })
2729    }
2730
2731    /// FROZEN copy of the pre-migration imperative
2732    /// `registry::layer_norm_last_dim::decompose` (the legacy 11-node spelling
2733    /// with two `Reshape(keepdim)` restores and the `centered` subterm shared
2734    /// between `Sqr` and the final `Div`), copied VERBATIM from that module
2735    /// before T7 replaced the live body with the data recipe. Two consumers:
2736    /// `emit_matches_layer_norm_last_dim_decompose` (whose oracle was repointed
2737    /// here — the live decompose now emits the `Unsqueeze` D3 spelling) and the
2738    /// T7 numeric-parity oracle (`norm_recipe` below).
2739    fn frozen_legacy_layer_norm_decompose(
2740        graph: &mut Graph,
2741        id: NodeId,
2742        params: &FusedOpParams,
2743    ) -> NodeId {
2744        let (x_id, x_shape, dtype) = {
2745            let n = graph.node(id);
2746            (n.inputs[0], n.shape.clone(), n.dtype)
2747        };
2748        let eps = match params {
2749            FusedOpParams::LayerNormLastDim { eps } => *eps,
2750            _ => return id,
2751        };
2752        let dims = x_shape.dims().to_vec();
2753        let rank = dims.len();
2754        let last = rank - 1;
2755
2756        let mut keepdim_dims = dims.clone();
2757        keepdim_dims[last] = 1;
2758        let keepdim_shape = Shape::from_dims(&keepdim_dims);
2759        let mut reduced_dims = dims.clone();
2760        reduced_dims.remove(last);
2761        let reduced_shape = Shape::from_dims(&reduced_dims);
2762
2763        let mean_id = graph.push(Node {
2764            op: Op::MeanDim(last),
2765            inputs: vec![x_id],
2766            shape: reduced_shape.clone(),
2767            dtype,
2768        });
2769        let mean_kd_id = graph.push(Node {
2770            op: Op::Reshape(keepdim_shape.clone()),
2771            inputs: vec![mean_id],
2772            shape: keepdim_shape.clone(),
2773            dtype,
2774        });
2775        let mean_bcast_id = graph.push(Node {
2776            op: Op::BroadcastTo(x_shape.clone()),
2777            inputs: vec![mean_kd_id],
2778            shape: x_shape.clone(),
2779            dtype,
2780        });
2781        let centered_id = graph.push(Node {
2782            op: Op::Sub,
2783            inputs: vec![x_id, mean_bcast_id],
2784            shape: x_shape.clone(),
2785            dtype,
2786        });
2787        let centered_sq_id = graph.push(Node {
2788            op: Op::Sqr,
2789            inputs: vec![centered_id],
2790            shape: x_shape.clone(),
2791            dtype,
2792        });
2793        let var_id = graph.push(Node {
2794            op: Op::MeanDim(last),
2795            inputs: vec![centered_sq_id],
2796            shape: reduced_shape,
2797            dtype,
2798        });
2799        let var_kd_id = graph.push(Node {
2800            op: Op::Reshape(keepdim_shape.clone()),
2801            inputs: vec![var_id],
2802            shape: keepdim_shape.clone(),
2803            dtype,
2804        });
2805        let var_eps_id = graph.push(Node {
2806            op: Op::AddScalar(eps),
2807            inputs: vec![var_kd_id],
2808            shape: keepdim_shape.clone(),
2809            dtype,
2810        });
2811        let denom_id = graph.push(Node {
2812            op: Op::Sqrt,
2813            inputs: vec![var_eps_id],
2814            shape: keepdim_shape,
2815            dtype,
2816        });
2817        let denom_bcast_id = graph.push(Node {
2818            op: Op::BroadcastTo(x_shape.clone()),
2819            inputs: vec![denom_id],
2820            shape: x_shape.clone(),
2821            dtype,
2822        });
2823        graph.push(Node {
2824            op: Op::Div,
2825            inputs: vec![centered_id, denom_bcast_id],
2826            shape: x_shape,
2827            dtype,
2828        })
2829    }
2830
2831    /// FROZEN copy of the pre-migration imperative
2832    /// `registry::layer_norm_last_dim_backward::decompose` (the legacy ~20-node
2833    /// `MeanDim`/`Reshape(keepdim)`/`BroadcastTo` recompute of
2834    /// `grad_x = istd · (g − mean(g) − xhat·mean(g·xhat))`), copied VERBATIM
2835    /// from that module @ `b967bdb1` before slice-2 replaced the live body with
2836    /// the data recipe. Sole consumer: the slice-2 numeric-parity oracle
2837    /// (`layer_norm_backward_recipe` below). Reads `inputs[0] = x`, `inputs[1] =
2838    /// g` (the upstream gradient) off the node — the order the autograd
2839    /// `BackwardKind::Fused(LAYER_NORM_LAST_DIM_BACKWARD)` edge emits.
2840    fn frozen_legacy_layer_norm_backward_decompose(
2841        graph: &mut Graph,
2842        id: NodeId,
2843        params: &FusedOpParams,
2844    ) -> NodeId {
2845        let (x_id, g_id, x_shape, dtype) = {
2846            let n = graph.node(id);
2847            (n.inputs[0], n.inputs[1], n.shape.clone(), n.dtype)
2848        };
2849        let eps = match params {
2850            FusedOpParams::LayerNormLastDimBackward { eps } => *eps,
2851            // G2: total + never-panic — impossible params; return self.
2852            _ => return id,
2853        };
2854        let dims = x_shape.dims().to_vec();
2855        let last = dims.len() - 1;
2856        let mut kd = dims.clone();
2857        kd[last] = 1;
2858        let keepdim = Shape::from_dims(&kd);
2859        let mut rd = dims.clone();
2860        rd.remove(last);
2861        let reduced = Shape::from_dims(&rd);
2862
2863        // reduce-mean over the last dim, keepdim, broadcast back to x_shape.
2864        let mean_b = |graph: &mut Graph, src: NodeId| -> NodeId {
2865            let m = graph.push(Node {
2866                op: Op::MeanDim(last),
2867                inputs: vec![src],
2868                shape: reduced.clone(),
2869                dtype,
2870            });
2871            let m_kd = graph.push(Node {
2872                op: Op::Reshape(keepdim.clone()),
2873                inputs: vec![m],
2874                shape: keepdim.clone(),
2875                dtype,
2876            });
2877            graph.push(Node {
2878                op: Op::BroadcastTo(x_shape.clone()),
2879                inputs: vec![m_kd],
2880                shape: x_shape.clone(),
2881                dtype,
2882            })
2883        };
2884
2885        // xhat = (x − mean(x)) · istd ; istd = rsqrt(var + eps).
2886        let mean_x = mean_b(graph, x_id);
2887        let xc = graph.push(Node {
2888            op: Op::Sub,
2889            inputs: vec![x_id, mean_x],
2890            shape: x_shape.clone(),
2891            dtype,
2892        });
2893        let xc_sq = graph.push(Node {
2894            op: Op::Sqr,
2895            inputs: vec![xc],
2896            shape: x_shape.clone(),
2897            dtype,
2898        });
2899        let var = mean_b(graph, xc_sq);
2900        let var_eps = graph.push(Node {
2901            op: Op::AddScalar(eps),
2902            inputs: vec![var],
2903            shape: x_shape.clone(),
2904            dtype,
2905        });
2906        let istd = graph.push(Node {
2907            op: Op::Rsqrt,
2908            inputs: vec![var_eps],
2909            shape: x_shape.clone(),
2910            dtype,
2911        });
2912        let xhat = graph.push(Node {
2913            op: Op::Mul,
2914            inputs: vec![xc, istd],
2915            shape: x_shape.clone(),
2916            dtype,
2917        });
2918
2919        // grad_x = istd · (g − mean(g) − xhat·mean(g·xhat)).
2920        let mean_g = mean_b(graph, g_id);
2921        let g_xhat = graph.push(Node {
2922            op: Op::Mul,
2923            inputs: vec![g_id, xhat],
2924            shape: x_shape.clone(),
2925            dtype,
2926        });
2927        let mean_gxh = mean_b(graph, g_xhat);
2928        let t1 = graph.push(Node {
2929            op: Op::Sub,
2930            inputs: vec![g_id, mean_g],
2931            shape: x_shape.clone(),
2932            dtype,
2933        });
2934        let t2 = graph.push(Node {
2935            op: Op::Mul,
2936            inputs: vec![xhat, mean_gxh],
2937            shape: x_shape.clone(),
2938            dtype,
2939        });
2940        let inner = graph.push(Node {
2941            op: Op::Sub,
2942            inputs: vec![t1, t2],
2943            shape: x_shape.clone(),
2944            dtype,
2945        });
2946        graph.push(Node {
2947            op: Op::Mul,
2948            inputs: vec![istd, inner],
2949            shape: x_shape,
2950            dtype,
2951        })
2952    }
2953
2954    /// FROZEN copy of the pre-migration imperative
2955    /// `registry::rms_norm_last_dim_backward::decompose` (the legacy ~22-node
2956    /// `MeanDim`/`SumDim`/`Reshape(keepdim)`/`AddScalar`/`MulScalar(n)`/`Rsqrt`/
2957    /// `BroadcastTo`/`Mul`/`Sub`/`Div` spelling, with the `n = dims[last]`
2958    /// reduced-count baked as an absolute `MulScalar`), copied VERBATIM from that
2959    /// module @ `9d7a1380` before A1 replaced the live body with the shape-
2960    /// polymorphic data recipe. Sole consumer: the A1 numeric-parity oracle
2961    /// (`rms_norm_backward_recipe` below). Reads `inputs = [x, upstream]` + the
2962    /// output shape off the node — the convention the autograd
2963    /// `BackwardKind::Fused` edge emits.
2964    fn frozen_legacy_rms_norm_backward_decompose(
2965        graph: &mut Graph,
2966        id: NodeId,
2967        params: &FusedOpParams,
2968    ) -> NodeId {
2969        let (x_id, up_id, x_shape, dtype) = {
2970            let n = graph.node(id);
2971            (n.inputs[0], n.inputs[1], n.shape.clone(), n.dtype)
2972        };
2973        let eps = match params {
2974            FusedOpParams::RmsNormLastDimBackward { eps } => *eps,
2975            // G2: total + never-panic — impossible params; return self.
2976            _ => return id,
2977        };
2978        let dims = x_shape.dims().to_vec();
2979        let last = dims.len() - 1;
2980        let n = dims[last] as f64;
2981        let mut kd = dims.clone();
2982        kd[last] = 1;
2983        let keepdim = Shape::from_dims(&kd);
2984        let mut rd = dims.clone();
2985        rd.remove(last);
2986        let reduced = Shape::from_dims(&rd);
2987
2988        // denom = mean(x²) + eps  (keepdim).
2989        let sq = graph.push(Node {
2990            op: Op::Sqr,
2991            inputs: vec![x_id],
2992            shape: x_shape.clone(),
2993            dtype,
2994        });
2995        let mean = graph.push(Node {
2996            op: Op::MeanDim(last),
2997            inputs: vec![sq],
2998            shape: reduced.clone(),
2999            dtype,
3000        });
3001        let mean_kd = graph.push(Node {
3002            op: Op::Reshape(keepdim.clone()),
3003            inputs: vec![mean],
3004            shape: keepdim.clone(),
3005            dtype,
3006        });
3007        let denom_kd = graph.push(Node {
3008            op: Op::AddScalar(eps),
3009            inputs: vec![mean_kd],
3010            shape: keepdim.clone(),
3011            dtype,
3012        });
3013        // r_rms = rsqrt(denom), broadcast.
3014        let rrms_kd = graph.push(Node {
3015            op: Op::Rsqrt,
3016            inputs: vec![denom_kd],
3017            shape: keepdim.clone(),
3018            dtype,
3019        });
3020        let rrms_b = graph.push(Node {
3021            op: Op::BroadcastTo(x_shape.clone()),
3022            inputs: vec![rrms_kd],
3023            shape: x_shape.clone(),
3024            dtype,
3025        });
3026        // s = sum(g·x, last)  (keepdim).
3027        let gx = graph.push(Node {
3028            op: Op::Mul,
3029            inputs: vec![up_id, x_id],
3030            shape: x_shape.clone(),
3031            dtype,
3032        });
3033        let s = graph.push(Node {
3034            op: Op::SumDim(last),
3035            inputs: vec![gx],
3036            shape: reduced,
3037            dtype,
3038        });
3039        let s_kd = graph.push(Node {
3040            op: Op::Reshape(keepdim.clone()),
3041            inputs: vec![s],
3042            shape: keepdim.clone(),
3043            dtype,
3044        });
3045        let s_b = graph.push(Node {
3046            op: Op::BroadcastTo(x_shape.clone()),
3047            inputs: vec![s_kd],
3048            shape: x_shape.clone(),
3049            dtype,
3050        });
3051        // term = x·s / (n·denom).
3052        let ndenom_kd = graph.push(Node {
3053            op: Op::MulScalar(n),
3054            inputs: vec![denom_kd],
3055            shape: keepdim.clone(),
3056            dtype,
3057        });
3058        let ndenom_b = graph.push(Node {
3059            op: Op::BroadcastTo(x_shape.clone()),
3060            inputs: vec![ndenom_kd],
3061            shape: x_shape.clone(),
3062            dtype,
3063        });
3064        let xs = graph.push(Node {
3065            op: Op::Mul,
3066            inputs: vec![x_id, s_b],
3067            shape: x_shape.clone(),
3068            dtype,
3069        });
3070        let term = graph.push(Node {
3071            op: Op::Div,
3072            inputs: vec![xs, ndenom_b],
3073            shape: x_shape.clone(),
3074            dtype,
3075        });
3076        // grad_x = r_rms · (g − term).
3077        let inner = graph.push(Node {
3078            op: Op::Sub,
3079            inputs: vec![up_id, term],
3080            shape: x_shape.clone(),
3081            dtype,
3082        });
3083        graph.push(Node {
3084            op: Op::Mul,
3085            inputs: vec![rrms_b, inner],
3086            shape: x_shape,
3087            dtype,
3088        })
3089    }
3090
3091    /// FROZEN copy of the pre-migration imperative
3092    /// `registry::reduce_max_to_backward::decompose` (the legacy 9-node
3093    /// `ReduceMaxTo`→`BroadcastTo`→`Equal(U8)`→`MaskedFill`→`ReduceSumTo`→`Div`→
3094    /// `BroadcastTo`→`Mul` spelling, sharing the single `mask_f`), copied VERBATIM
3095    /// from that module @ `94c69ec7` before A2 replaced the live body with the
3096    /// data recipe. Sole consumer: the A2 numeric-parity oracle
3097    /// (`reduce_max_to_backward_recipe` below). Reads `inputs[0] = x` and
3098    /// `inputs[1] = up` (the upstream gradient) off the node — the order autograd
3099    /// emits. The `MaskedFill` fill is `Scalar::one(dtype)` at the node's dtype.
3100    fn frozen_legacy_reduce_max_to_backward_decompose(
3101        graph: &mut Graph,
3102        id: NodeId,
3103        _params: &FusedOpParams,
3104    ) -> NodeId {
3105        use fuel_ir::Scalar;
3106        let (x_id, up_id, x_shape, dtype) = {
3107            let n = graph.node(id);
3108            (n.inputs[0], n.inputs[1], n.shape.clone(), n.dtype)
3109        };
3110        let target = graph.node(up_id).shape.clone();
3111
3112        // y = per-window max, broadcast back to x's shape.
3113        let y = graph.push(Node {
3114            op: Op::ReduceMaxTo(target.clone()),
3115            inputs: vec![x_id],
3116            shape: target.clone(),
3117            dtype,
3118        });
3119        let y_b = graph.push(Node {
3120            op: Op::BroadcastTo(x_shape.clone()),
3121            inputs: vec![y],
3122            shape: x_shape.clone(),
3123            dtype,
3124        });
3125        // U8 mask = (x == max), then a float mask = MaskedFill(1.0 into zeros).
3126        let mask_u8 = graph.push(Node {
3127            op: Op::Equal,
3128            inputs: vec![x_id, y_b],
3129            shape: x_shape.clone(),
3130            dtype: DType::U8,
3131        });
3132        let zeros = graph.push(Node {
3133            op: Op::MulScalar(0.0),
3134            inputs: vec![x_id],
3135            shape: x_shape.clone(),
3136            dtype,
3137        });
3138        // A reduce-max backward over a non-real (packed/scale) dtype is not a
3139        // thing this frozen oracle handles; self-return the node unchanged
3140        // (decompose-fixpoint / surfaced-gap convention) rather than panic.
3141        let fill = match Scalar::one(dtype) {
3142            Ok(s) => s,
3143            Err(_) => return id,
3144        };
3145        let mask_f = graph.push(Node {
3146            op: Op::MaskedFill { value: fill },
3147            inputs: vec![zeros, mask_u8],
3148            shape: x_shape.clone(),
3149            dtype,
3150        });
3151        // ties = count per window; share = upstream / ties (fair share for ties).
3152        let ties = graph.push(Node {
3153            op: Op::ReduceSumTo(target.clone()),
3154            inputs: vec![mask_f],
3155            shape: target.clone(),
3156            dtype,
3157        });
3158        let share = graph.push(Node {
3159            op: Op::Div,
3160            inputs: vec![up_id, ties],
3161            shape: target,
3162            dtype,
3163        });
3164        let share_b = graph.push(Node {
3165            op: Op::BroadcastTo(x_shape.clone()),
3166            inputs: vec![share],
3167            shape: x_shape.clone(),
3168            dtype,
3169        });
3170        graph.push(Node {
3171            op: Op::Mul,
3172            inputs: vec![mask_f, share_b],
3173            shape: x_shape,
3174            dtype,
3175        })
3176    }
3177
3178    /// FROZEN copy of the pre-migration imperative
3179    /// `registry::powi_backward::decompose` (the legacy 3-node
3180    /// `PowI(exp-1)`→`MulScalar(exp)`→`Mul` spelling), copied VERBATIM from that
3181    /// module before A3 replaced the live body with the param-derived data
3182    /// recipe. Sole consumer: the A3 numeric-parity oracle (`powi_backward_recipe`
3183    /// below). Reads `inputs[0] = x` and `inputs[1] = up` (the upstream gradient)
3184    /// off the node — the order autograd emits — plus the output shape/dtype;
3185    /// `exp` rides the params.
3186    fn frozen_legacy_powi_backward_decompose(
3187        graph: &mut Graph,
3188        id: NodeId,
3189        params: &FusedOpParams,
3190    ) -> NodeId {
3191        let (x_id, up_id, shape, dtype) = {
3192            let n = graph.node(id);
3193            (n.inputs[0], n.inputs[1], n.shape.clone(), n.dtype)
3194        };
3195        let exp = match params {
3196            FusedOpParams::PowIBackward { exp } => *exp,
3197            // G2: total + never-panic — impossible params; return self.
3198            _ => return id,
3199        };
3200        let pow = graph.push(Node {
3201            op: Op::PowI(exp - 1),
3202            inputs: vec![x_id],
3203            shape: shape.clone(),
3204            dtype,
3205        });
3206        let scaled = graph.push(Node {
3207            op: Op::MulScalar(exp as f64),
3208            inputs: vec![pow],
3209            shape: shape.clone(),
3210            dtype,
3211        });
3212        graph.push(Node {
3213            op: Op::Mul,
3214            inputs: vec![scaled, up_id],
3215            shape,
3216            dtype,
3217        })
3218    }
3219
3220    /// FROZEN copy of the pre-migration imperative
3221    /// `registry::fused_linear::decompose` (the legacy 3-node `MatMul` +
3222    /// `BroadcastTo(rank-1 bias)` + `Add` spelling, broadcasting the rank-1
3223    /// bias DIRECTLY with no leading-1 pad), copied VERBATIM from that module @
3224    /// `b967bdb1` before slice-2 replaced the live body with the WithDim data
3225    /// recipe. Sole consumer: the slice-2 numeric-parity oracle
3226    /// (`fused_linear_recipe` below). Reads `inputs = [a, b, bias]` + the
3227    /// matmul output shape off the node.
3228    fn frozen_legacy_fused_linear_decompose(
3229        graph: &mut Graph,
3230        id: NodeId,
3231        _params: &FusedOpParams,
3232    ) -> NodeId {
3233        let (a_id, b_id, bias_id, out_shape, dtype) = {
3234            let n = graph.node(id);
3235            (
3236                n.inputs[0],
3237                n.inputs[1],
3238                n.inputs[2],
3239                n.shape.clone(),
3240                n.dtype,
3241            )
3242        };
3243        let mm_id = graph.push(Node {
3244            op: Op::MatMul,
3245            inputs: vec![a_id, b_id],
3246            shape: out_shape.clone(),
3247            dtype,
3248        });
3249        let bias_bcst_id = graph.push(Node {
3250            op: Op::BroadcastTo(out_shape.clone()),
3251            inputs: vec![bias_id],
3252            shape: out_shape.clone(),
3253            dtype,
3254        });
3255        graph.push(Node {
3256            op: Op::Add,
3257            inputs: vec![mm_id, bias_bcst_id],
3258            shape: out_shape,
3259            dtype,
3260        })
3261    }
3262
3263    /// FROZEN copy of the pre-migration imperative
3264    /// `registry::softmax_last_dim_backward::decompose` (the legacy 5-node
3265    /// `Mul`/`ReduceSumTo(keepdim)`/`BroadcastTo`/`Sub`/`Mul` spelling), copied
3266    /// VERBATIM from that module @ `aa2eee3c` before T8 replaced the live body
3267    /// with the data recipe. Sole consumer: the T8 numeric-parity oracle
3268    /// (`softmax_backward_recipe` below). Reads `inputs[0] = s` (the forward
3269    /// softmax output) and `inputs[1] = g` (the upstream gradient) off the node
3270    /// — the same convention the autograd `BackwardKind::Fused` edge emits
3271    /// (`lib.rs` softmax-backward arm: `vec![id, up_id]`).
3272    fn frozen_legacy_softmax_backward_decompose(
3273        graph: &mut Graph,
3274        id: NodeId,
3275        _params: &FusedOpParams,
3276    ) -> NodeId {
3277        let (s_id, g_id, x_shape, dtype) = {
3278            let n = graph.node(id);
3279            (n.inputs[0], n.inputs[1], n.shape.clone(), n.dtype)
3280        };
3281        // keepdim shape: last dim → 1.
3282        let mut kd = x_shape.dims().to_vec();
3283        let last = kd.len() - 1;
3284        kd[last] = 1;
3285        let keepdim = Shape::from_dims(&kd);
3286
3287        let gs = graph.push(Node {
3288            op: Op::Mul,
3289            inputs: vec![g_id, s_id],
3290            shape: x_shape.clone(),
3291            dtype,
3292        });
3293        let summed = graph.push(Node {
3294            op: Op::ReduceSumTo(keepdim.clone()),
3295            inputs: vec![gs],
3296            shape: keepdim,
3297            dtype,
3298        });
3299        let summed_b = graph.push(Node {
3300            op: Op::BroadcastTo(x_shape.clone()),
3301            inputs: vec![summed],
3302            shape: x_shape.clone(),
3303            dtype,
3304        });
3305        let sub = graph.push(Node {
3306            op: Op::Sub,
3307            inputs: vec![g_id, summed_b],
3308            shape: x_shape.clone(),
3309            dtype,
3310        });
3311        graph.push(Node {
3312            op: Op::Mul,
3313            inputs: vec![s_id, sub],
3314            shape: x_shape,
3315            dtype,
3316        })
3317    }
3318
3319    #[test]
3320    fn emit_matches_softmax_last_dim_decompose() {
3321        use fuel_ir::{DType, Shape};
3322        let mut g = Graph::new();
3323        let sh = Shape::from_dims(&[2, 4]);
3324        let x = g.push(Node {
3325            op: Op::Const,
3326            inputs: vec![],
3327            shape: sh.clone(),
3328            dtype: DType::F32,
3329        });
3330        // Oracle: the FROZEN legacy builder (reads inputs[0] + shape + dtype
3331        // off the node). T5 repointed this from the live registry decompose —
3332        // which now emits the 9-node recipe spelling — so this test keeps
3333        // pinning the Increment-A guarantee it always pinned: the grown
3334        // `emit` reconstructs the LEGACY imperative structure from the legacy
3335        // region datum.
3336        let fused = g.push(Node {
3337            op: Op::Const,
3338            inputs: vec![x],
3339            shape: sh.clone(),
3340            dtype: DType::F32,
3341        });
3342        let oracle = frozen_legacy_softmax_decompose(&mut g, fused, &FusedOpParams::SoftmaxLastDim);
3343
3344        // keepdim shape [2,1]; full shape [2,4].
3345        let kd = OpAttrs {
3346            target_shape: vec![2, 1],
3347            ..OpAttrs::default()
3348        };
3349        let full = OpAttrs {
3350            target_shape: vec![2, 4],
3351            ..OpAttrs::default()
3352        };
3353        // e = Exp(Sub(x, BroadcastTo(ReduceMaxTo(x)))) — mirrors decompose order
3354        // `Sub{[x, mb]}` exactly; built fresh each call so numerator and the
3355        // denominator's ReduceSumTo input are identical subtrees.
3356        let softmax_e = |kd: &OpAttrs, full: &OpAttrs| {
3357            op_node(
3358                OpTag::Exp,
3359                OpAttrs::default(),
3360                vec![op_node(
3361                    OpTag::Sub,
3362                    OpAttrs::default(),
3363                    vec![
3364                        bind(0),
3365                        op_node(
3366                            OpTag::BroadcastTo,
3367                            full.clone(),
3368                            vec![op_node(OpTag::ReduceMaxTo, kd.clone(), vec![bind(0)])],
3369                        ),
3370                    ],
3371                )],
3372            )
3373        };
3374        // out = Div(e, BroadcastTo(ReduceSumTo(e))) — mirrors `Div{[e, db]}`.
3375        let region = op_node(
3376            OpTag::Div,
3377            OpAttrs::default(),
3378            vec![
3379                softmax_e(&kd, &full),
3380                op_node(
3381                    OpTag::BroadcastTo,
3382                    full.clone(),
3383                    vec![op_node(
3384                        OpTag::ReduceSumTo,
3385                        kd.clone(),
3386                        vec![softmax_e(&kd, &full)],
3387                    )],
3388                ),
3389            ],
3390        );
3391        let emitted = emit_region(&mut g, &region, &[x], &[]);
3392        assert_structural_eq(&g, oracle, emitted);
3393    }
3394
3395    #[test]
3396    fn emit_matches_rope_decompose() {
3397        use fuel_ir::{DType, Shape};
3398        let mut g = Graph::new();
3399        let sh = Shape::from_dims(&[2, 4]); // seq=2, d=4, half=2
3400        let x = g.push(Node {
3401            op: Op::Const,
3402            inputs: vec![],
3403            shape: sh.clone(),
3404            dtype: DType::F32,
3405        });
3406        let cos = g.push(Node {
3407            op: Op::Const,
3408            inputs: vec![],
3409            shape: sh.clone(),
3410            dtype: DType::F32,
3411        });
3412        let sin = g.push(Node {
3413            op: Op::Const,
3414            inputs: vec![],
3415            shape: sh.clone(),
3416            dtype: DType::F32,
3417        });
3418        let fused = g.push(Node {
3419            op: Op::Const,
3420            inputs: vec![x, cos, sin],
3421            shape: sh.clone(),
3422            dtype: DType::F32,
3423        });
3424        // Oracle: the FROZEN legacy builder. T6 repointed this from the live
3425        // registry decompose — which now emits the data recipe (byte-identical
3426        // to legacy where a rank-raise occurs, but at EQUAL rank the recipe
3427        // elides the legacy's no-op prep `Reshape`, D4). This test keeps
3428        // pinning the Increment-A guarantee it always pinned: the grown `emit`
3429        // reconstructs the LEGACY imperative structure from a legacy-spelled
3430        // region datum.
3431        let oracle = frozen_legacy_rope_decompose(&mut g, fused, &FusedOpParams::Rope);
3432
3433        // decompose's broadcast_shape for rank-2 [2,4] is [seq,d] = [2,4]; half slices along last dim.
3434        let full = OpAttrs {
3435            target_shape: vec![2, 4],
3436            ..OpAttrs::default()
3437        };
3438        let sl_first = OpAttrs {
3439            axis: Some(1),
3440            slice_start: Some(0),
3441            slice_len: Some(2),
3442            ..OpAttrs::default()
3443        };
3444        let sl_second = OpAttrs {
3445            axis: Some(1),
3446            slice_start: Some(2),
3447            slice_len: Some(2),
3448            ..OpAttrs::default()
3449        };
3450        let cat = OpAttrs {
3451            axis: Some(1),
3452            ..OpAttrs::default()
3453        };
3454        let bcast_reshape = |full: &OpAttrs, i: u8| {
3455            op_node(
3456                OpTag::BroadcastTo,
3457                full.clone(),
3458                vec![op_node(OpTag::Reshape, full.clone(), vec![bind(i)])],
3459            )
3460        };
3461        // left = Mul(x, cos_bcast); right = Mul(rotated_half, sin_bcast); out = Add(left, right).
3462        // rotated_half = Concat{dim:1}(Neg(second_half), first_half).
3463        let rotated = op_node(
3464            OpTag::Concat,
3465            cat,
3466            vec![
3467                op_node(
3468                    OpTag::Neg,
3469                    OpAttrs::default(),
3470                    vec![op_node(OpTag::Slice, sl_second, vec![bind(0)])],
3471                ),
3472                op_node(OpTag::Slice, sl_first, vec![bind(0)]),
3473            ],
3474        );
3475        let left = op_node(
3476            OpTag::Mul,
3477            OpAttrs::default(),
3478            vec![bind(0), bcast_reshape(&full, 1)],
3479        );
3480        let right = op_node(
3481            OpTag::Mul,
3482            OpAttrs::default(),
3483            vec![rotated, bcast_reshape(&full, 2)],
3484        );
3485        let region = op_node(OpTag::Add, OpAttrs::default(), vec![left, right]);
3486
3487        let emitted = emit_region(&mut g, &region, &[x, cos, sin], &[]);
3488        assert_structural_eq(&g, oracle, emitted);
3489    }
3490
3491    #[test]
3492    fn emit_matches_layer_norm_last_dim_decompose() {
3493        use fuel_ir::{DType, Shape};
3494        let mut g = Graph::new();
3495        let sh = Shape::from_dims(&[2, 4]); // last=1, reduced [2], keepdim [2,1]
3496        let x = g.push(Node {
3497            op: Op::Const,
3498            inputs: vec![],
3499            shape: sh.clone(),
3500            dtype: DType::F32,
3501        });
3502        let fused = g.push(Node {
3503            op: Op::Const,
3504            inputs: vec![x],
3505            shape: sh.clone(),
3506            dtype: DType::F32,
3507        });
3508        // Oracle: the FROZEN legacy builder (the `Reshape(keepdim)` spelling).
3509        // T7 repointed this from the live registry decompose — which now emits
3510        // the D3 `Unsqueeze` swap — so this test keeps pinning the Increment-A
3511        // guarantee it always pinned: the grown `emit` reconstructs the LEGACY
3512        // imperative structure from a legacy-spelled (Reshape) region datum.
3513        let oracle = frozen_legacy_layer_norm_decompose(
3514            &mut g,
3515            fused,
3516            &FusedOpParams::LayerNormLastDim { eps: 1e-5 },
3517        );
3518
3519        let kd = OpAttrs {
3520            target_shape: vec![2, 1],
3521            ..OpAttrs::default()
3522        };
3523        let full = OpAttrs {
3524            target_shape: vec![2, 4],
3525            ..OpAttrs::default()
3526        };
3527        let md = OpAttrs {
3528            axis: Some(1),
3529            ..OpAttrs::default()
3530        };
3531        let eps_attrs = OpAttrs {
3532            scalars: vec![1e-5],
3533            ..OpAttrs::default()
3534        }; // BAKED constant, not a slot
3535        // centered = Sub(x, BroadcastTo(Reshape(MeanDim(x)))) — shared subterm.
3536        let centered = op_node(
3537            OpTag::Sub,
3538            OpAttrs::default(),
3539            vec![
3540                bind(0),
3541                op_node(
3542                    OpTag::BroadcastTo,
3543                    full.clone(),
3544                    vec![op_node(
3545                        OpTag::Reshape,
3546                        kd.clone(),
3547                        vec![op_node(OpTag::MeanDim, md.clone(), vec![bind(0)])],
3548                    )],
3549                ),
3550            ],
3551        );
3552        // denom_bcast = BroadcastTo(Sqrt(AddScalar(eps)(Reshape(MeanDim(Sqr(centered)))))).
3553        let denom_bcast = op_node(
3554            OpTag::BroadcastTo,
3555            full.clone(),
3556            vec![op_node(
3557                OpTag::Sqrt,
3558                OpAttrs::default(),
3559                vec![op_node(
3560                    OpTag::AddScalar,
3561                    eps_attrs,
3562                    vec![op_node(
3563                        OpTag::Reshape,
3564                        kd.clone(),
3565                        vec![op_node(
3566                            OpTag::MeanDim,
3567                            md.clone(),
3568                            vec![op_node(
3569                                OpTag::Sqr,
3570                                OpAttrs::default(),
3571                                vec![centered.clone()],
3572                            )],
3573                        )],
3574                    )],
3575                )],
3576            )],
3577        );
3578        // out = Div(centered, denom_bcast).
3579        let region = op_node(OpTag::Div, OpAttrs::default(), vec![centered, denom_bcast]);
3580
3581        let emitted = emit_region(&mut g, &region, &[x], &[]);
3582        assert_structural_eq(&g, oracle, emitted);
3583    }
3584
3585    // ---- T2 (Increment C slice 1): shape-relative attr resolution ----------
3586    //
3587    // `resolve_rel_attrs` is the PURE resolver behind recipe polymorphism: it
3588    // turns the shape-relative interior fields (`target_shape_rel` /
3589    // `slice_{start,len}_rel` / `axis_last`, D2) into the concrete sibling
3590    // fields against the given bind/child shapes, reusing `shape_expr`'s
3591    // evaluator (no second evaluator). Every failure is a typed
3592    // [`RelAttrError`], never a panic.
3593
3594    mod resolve_rel {
3595        use super::super::{RelAttrError, resolve_rel_attrs};
3596        use fuel_kernel_seam_types::shape_expr::{
3597            Dim, LAST, SYMBOLIC, ShapeExpr, ShapeExprError, TAG_DIMS,
3598        };
3599        use fuel_kernel_seam_types::{OpAttrs, OpTag};
3600
3601        fn half_of_bind0_last() -> Dim {
3602            Dim::Div(
3603                Box::new(Dim::Extent {
3604                    operand: 0,
3605                    axis: LAST,
3606                }),
3607                Box::new(Dim::Const(2)),
3608            )
3609        }
3610
3611        #[test]
3612        fn same_as_bind0_tracks_the_bind_shape() {
3613            // The polymorphism seed: ONE recipe datum, two shapes, two targets.
3614            let attrs = OpAttrs {
3615                target_shape_rel: Some(ShapeExpr::SameAs { operand: 0 }),
3616                ..OpAttrs::default()
3617            };
3618            let r = resolve_rel_attrs(OpTag::BroadcastTo, &attrs, &[vec![2, 3]], &[vec![2, 1]])
3619                .expect("resolves");
3620            assert_eq!(r.target_shape, vec![2, 3]);
3621            assert!(
3622                r.target_shape_rel.is_none(),
3623                "resolved attrs are fully concrete"
3624            );
3625            let r = resolve_rel_attrs(OpTag::BroadcastTo, &attrs, &[vec![4, 5]], &[vec![4, 1]])
3626                .expect("resolves");
3627            assert_eq!(r.target_shape, vec![4, 5]);
3628        }
3629
3630        #[test]
3631        fn slice_bounds_track_the_bind_extent() {
3632            // start = len = Extent(bind0, LAST) / 2 — the rope-half worked example.
3633            let attrs = OpAttrs {
3634                axis: Some(1),
3635                slice_start_rel: Some(half_of_bind0_last()),
3636                slice_len_rel: Some(half_of_bind0_last()),
3637                ..OpAttrs::default()
3638            };
3639            let r = resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, 4]], &[vec![2, 4]])
3640                .expect("resolves at d=4");
3641            assert_eq!(r.slice_start, Some(2));
3642            assert_eq!(r.slice_len, Some(2));
3643            assert!(r.slice_start_rel.is_none() && r.slice_len_rel.is_none());
3644            let r = resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, 8]], &[vec![2, 8]])
3645                .expect("resolves at d=8");
3646            assert_eq!(r.slice_start, Some(4));
3647            assert_eq!(r.slice_len, Some(4));
3648        }
3649
3650        #[test]
3651        fn axis_last_resolves_per_tag() {
3652            let attrs = OpAttrs {
3653                axis_last: true,
3654                ..OpAttrs::default()
3655            };
3656            // Reduce family (axis carrier): LAST = rank − 1.
3657            let r = resolve_rel_attrs(OpTag::SumDim, &attrs, &[], &[vec![2, 4]]).expect("rank 2");
3658            assert_eq!(r.axis, Some(1));
3659            assert!(!r.axis_last, "resolved attrs are fully concrete");
3660            let r =
3661                resolve_rel_attrs(OpTag::SumDim, &attrs, &[], &[vec![2, 3, 4]]).expect("rank 3");
3662            assert_eq!(r.axis, Some(2));
3663            // Concat rides the same axis carrier.
3664            let r =
3665                resolve_rel_attrs(OpTag::Concat, &attrs, &[], &[vec![2, 3, 4]]).expect("concat");
3666            assert_eq!(r.axis, Some(2));
3667            // Unsqueeze (dims carrier): APPEND — dim == rank (`primitive_shape`
3668            // permits `dim == rank`).
3669            let r =
3670                resolve_rel_attrs(OpTag::Unsqueeze, &attrs, &[], &[vec![2, 4]]).expect("unsqueeze");
3671            assert_eq!(r.dims, vec![2]);
3672            assert!(!r.axis_last);
3673            // Squeeze (dims carrier): LAST = rank − 1.
3674            let r =
3675                resolve_rel_attrs(OpTag::Squeeze, &attrs, &[], &[vec![2, 4, 1]]).expect("squeeze");
3676            assert_eq!(r.dims, vec![2]);
3677        }
3678
3679        #[test]
3680        fn bind_out_of_range_is_a_typed_decline() {
3681            let attrs = OpAttrs {
3682                target_shape_rel: Some(ShapeExpr::SameAs { operand: 3 }),
3683                ..OpAttrs::default()
3684            };
3685            assert_eq!(
3686                resolve_rel_attrs(OpTag::BroadcastTo, &attrs, &[vec![2, 3]], &[vec![2, 3]]),
3687                Err(RelAttrError::Expr(ShapeExprError::OperandOutOfRange {
3688                    operand: 3,
3689                    operands: 1
3690                })),
3691            );
3692            let attrs = OpAttrs {
3693                axis: Some(1),
3694                slice_start_rel: Some(Dim::Extent {
3695                    operand: 7,
3696                    axis: LAST,
3697                }),
3698                ..OpAttrs::default()
3699            };
3700            assert_eq!(
3701                resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, 4]], &[vec![2, 4]]),
3702                Err(RelAttrError::Expr(ShapeExprError::OperandOutOfRange {
3703                    operand: 7,
3704                    operands: 1
3705                })),
3706            );
3707        }
3708
3709        #[test]
3710        fn rel_and_abs_both_set_is_a_typed_conflict() {
3711            // target_shape XOR target_shape_rel.
3712            let attrs = OpAttrs {
3713                target_shape: vec![2, 3],
3714                target_shape_rel: Some(ShapeExpr::SameAs { operand: 0 }),
3715                ..OpAttrs::default()
3716            };
3717            assert_eq!(
3718                resolve_rel_attrs(OpTag::BroadcastTo, &attrs, &[vec![2, 3]], &[vec![2, 3]]),
3719                Err(RelAttrError::RelAbsConflict {
3720                    field: "target_shape"
3721                }),
3722            );
3723            // slice_start XOR slice_start_rel.
3724            let attrs = OpAttrs {
3725                axis: Some(1),
3726                slice_start: Some(0),
3727                slice_start_rel: Some(half_of_bind0_last()),
3728                ..OpAttrs::default()
3729            };
3730            assert_eq!(
3731                resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, 4]], &[vec![2, 4]]),
3732                Err(RelAttrError::RelAbsConflict {
3733                    field: "slice_start"
3734                }),
3735            );
3736            // slice_len XOR slice_len_rel.
3737            let attrs = OpAttrs {
3738                axis: Some(1),
3739                slice_len: Some(2),
3740                slice_len_rel: Some(half_of_bind0_last()),
3741                ..OpAttrs::default()
3742            };
3743            assert_eq!(
3744                resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, 4]], &[vec![2, 4]]),
3745                Err(RelAttrError::RelAbsConflict { field: "slice_len" }),
3746            );
3747            // axis XOR axis_last.
3748            let attrs = OpAttrs {
3749                axis: Some(0),
3750                axis_last: true,
3751                ..OpAttrs::default()
3752            };
3753            assert_eq!(
3754                resolve_rel_attrs(OpTag::SumDim, &attrs, &[], &[vec![2, 4]]),
3755                Err(RelAttrError::RelAbsConflict { field: "axis" }),
3756            );
3757            // dims XOR axis_last (Unsqueeze's carrier is `dims`).
3758            let attrs = OpAttrs {
3759                dims: vec![0],
3760                axis_last: true,
3761                ..OpAttrs::default()
3762            };
3763            assert_eq!(
3764                resolve_rel_attrs(OpTag::Unsqueeze, &attrs, &[], &[vec![2, 4]]),
3765                Err(RelAttrError::RelAbsConflict { field: "dims" }),
3766            );
3767        }
3768
3769        #[test]
3770        fn negative_result_is_a_typed_decline() {
3771            // 0 − 2 = −2: a negative slice offset is malformed, not a wrap.
3772            let neg = Dim::Sub(Box::new(Dim::Const(0)), Box::new(Dim::Const(2)));
3773            let attrs = OpAttrs {
3774                axis: Some(1),
3775                slice_start_rel: Some(neg),
3776                ..OpAttrs::default()
3777            };
3778            assert_eq!(
3779                resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, 4]], &[vec![2, 4]]),
3780                Err(RelAttrError::Negative {
3781                    field: "slice_start",
3782                    value: -2
3783                }),
3784            );
3785        }
3786
3787        #[test]
3788        fn symbolic_extent_is_a_surfaced_gap_decline() {
3789            // A symbolic bind extent → the expression evaluates to Gap → typed
3790            // decline (the emit caller surfaces it as a fixpoint, G2).
3791            let attrs = OpAttrs {
3792                axis: Some(1),
3793                slice_len_rel: Some(half_of_bind0_last()),
3794                ..OpAttrs::default()
3795            };
3796            assert_eq!(
3797                resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, SYMBOLIC]], &[vec![2, 4]]),
3798                Err(RelAttrError::SymbolicGap { field: "slice_len" }),
3799            );
3800            let attrs = OpAttrs {
3801                target_shape_rel: Some(ShapeExpr::SameAs { operand: 0 }),
3802                ..OpAttrs::default()
3803            };
3804            assert_eq!(
3805                resolve_rel_attrs(
3806                    OpTag::BroadcastTo,
3807                    &attrs,
3808                    &[vec![2, SYMBOLIC]],
3809                    &[vec![2, 4]]
3810                ),
3811                Err(RelAttrError::SymbolicGap {
3812                    field: "target_shape"
3813                }),
3814            );
3815        }
3816
3817        #[test]
3818        fn two_operand_max_frame_declines_instead_of_a_partial_shape() {
3819            // I1 (Baracuda §6.20 finding): an ELEMENTWISE output frame is not
3820            // always expressible as `SameAs(operand)`. When the frame is
3821            // assembled by per-axis max across TWO binds — `a[2,1] ⊗ b[1,3] →
3822            // [2,3]` — NO single operand carries it, so BOTH spellings resolve
3823            // to a PARTIAL frame ([2,1] / [1,3]) and would silently emit the
3824            // wrong `BroadcastTo` target. Must be a typed decline.
3825            // The decline is typed and names the Dims-class constructor that
3826            // WOULD express it (reserved tag 0x0B, KISS #80) — a surfaced gap,
3827            // never a panic and never a wrong shape.
3828            let binds = vec![vec![2, 1], vec![1, 3]]; // frame = [2,3], carried by neither
3829            for operand in [0u8, 1] {
3830                let attrs = OpAttrs {
3831                    target_shape_rel: Some(ShapeExpr::SameAs { operand }),
3832                    ..OpAttrs::default()
3833                };
3834                let child = binds[operand as usize].clone();
3835                assert_eq!(
3836                    resolve_rel_attrs(OpTag::BroadcastTo, &attrs, &binds, &[child]),
3837                    Err(RelAttrError::FrameNotExpressible {
3838                        field: "target_shape",
3839                        frame: vec![2, 3],
3840                        missing_ctor: TAG_DIMS,
3841                    }),
3842                    "SameAs {{ operand: {operand} }} must not resolve to a PARTIAL frame",
3843                );
3844            }
3845        }
3846
3847        #[test]
3848        fn frame_guard_does_not_fire_when_an_operand_carries_the_frame() {
3849            // The guard is deliberately narrow. It must NOT degrade the cases
3850            // the 5 migrated recipes actually use.
3851            let attrs = OpAttrs {
3852                target_shape_rel: Some(ShapeExpr::SameAs { operand: 0 }),
3853                ..OpAttrs::default()
3854            };
3855            // (a) bind0 IS the frame (softmax/rope/rms-norm/layer-norm shape).
3856            let binds = vec![vec![2, 3, 4], vec![4]];
3857            let r = resolve_rel_attrs(OpTag::BroadcastTo, &attrs, &binds, &[vec![4]])
3858                .expect("bind0 carries the frame");
3859            assert_eq!(r.target_shape, vec![2, 3, 4]);
3860            // (b) a SUB-frame target is legitimate: the frame is bind0's, and
3861            // naming bind1's smaller shape is an ordinary interior broadcast.
3862            let sub = OpAttrs {
3863                target_shape_rel: Some(ShapeExpr::SameAs { operand: 1 }),
3864                ..OpAttrs::default()
3865            };
3866            let r = resolve_rel_attrs(OpTag::BroadcastTo, &sub, &binds, &[vec![4]])
3867                .expect("sub-frame broadcast stays expressible");
3868            assert_eq!(r.target_shape, vec![4]);
3869            // (c) binds with NO joint elementwise frame (a matmul region) —
3870            // per-axis max is meaningless there, so the guard stays out.
3871            let mm = vec![vec![8, 4096], vec![4096, 1024]];
3872            let r = resolve_rel_attrs(OpTag::BroadcastTo, &attrs, &mm, &[vec![8, 1]])
3873                .expect("no joint frame ⇒ no guard");
3874            assert_eq!(r.target_shape, vec![8, 4096]);
3875            // (d) a NON-frame-carrier tag is untouched: only `BroadcastTo`'s
3876            // target IS the elementwise output frame.
3877            let two = vec![vec![2, 1], vec![1, 3]];
3878            let r = resolve_rel_attrs(OpTag::Reshape, &attrs, &two, &[vec![2, 1]])
3879                .expect("Reshape's target is not a frame claim");
3880            assert_eq!(r.target_shape, vec![2, 1]);
3881        }
3882
3883        #[test]
3884        fn axis_last_on_an_axisless_tag_or_without_a_child_declines() {
3885            let attrs = OpAttrs {
3886                axis_last: true,
3887                ..OpAttrs::default()
3888            };
3889            // Add has no axis carrier — axis_last is meaningless, a typed decline
3890            // (build-time validation posture: never silently ignore).
3891            assert_eq!(
3892                resolve_rel_attrs(OpTag::Add, &attrs, &[], &[vec![2, 4], vec![2, 4]]),
3893                Err(RelAttrError::AxisLastUnsupported { tag: OpTag::Add }),
3894            );
3895            // No child operand → no rank to resolve LAST against.
3896            assert_eq!(
3897                resolve_rel_attrs(OpTag::SumDim, &attrs, &[], &[]),
3898                Err(RelAttrError::NoChildOperand),
3899            );
3900            // Rank-0 child: LAST has no axis → the shared resolve_axis decline.
3901            assert_eq!(
3902                resolve_rel_attrs(OpTag::SumDim, &attrs, &[], &[vec![]]),
3903                Err(RelAttrError::Expr(ShapeExprError::AxisOutOfRange {
3904                    axis: LAST,
3905                    rank: 0
3906                })),
3907            );
3908        }
3909
3910        #[test]
3911        fn rel_free_attrs_pass_through_unchanged() {
3912            // The no-rel fast path: absolute attrs resolve to themselves.
3913            let attrs = OpAttrs {
3914                axis: Some(1),
3915                slice_start: Some(2),
3916                slice_len: Some(3),
3917                ..OpAttrs::default()
3918            };
3919            let r = resolve_rel_attrs(OpTag::Slice, &attrs, &[vec![2, 4]], &[vec![2, 4]])
3920                .expect("no-op");
3921            assert_eq!(r, attrs);
3922        }
3923    }
3924
3925    // ---- T3 (Increment C slice 1): resolving emit + D4 pad + rel validation ----
3926    //
3927    // The emit integration behind recipe polymorphism: children are emitted
3928    // FIRST (their shapes feed the rel-attr resolver), `resolve_rel_attrs`
3929    // produces fully-concrete attrs, then the unchanged tag_to_op →
3930    // primitive_shape path runs. A resolved `BroadcastTo` whose target rank
3931    // exceeds its operand's materializes the legacy `Reshape` pad (D4).
3932    // `validate_representable` accepts rel-attr regions via a probe-resolve
3933    // (mirror of the scalar slot dummy-fill) and rejects structural authoring
3934    // errors (rel+abs conflict, bind out of range) with a typed decline.
3935
3936    mod emit_rel {
3937        use super::super::*;
3938        use super::{assert_structural_eq, bind, op_node};
3939        use fuel_ir::{DType, Shape};
3940        use fuel_kernel_seam_types::shape_expr::{Dim, ShapeExpr};
3941
3942        fn cst(g: &mut Graph, dims: &[usize]) -> NodeId {
3943            g.push(Node {
3944                op: Op::Const,
3945                inputs: vec![],
3946                shape: Shape::from_dims(dims),
3947                dtype: DType::F32,
3948            })
3949        }
3950
3951        fn bcast_same_as_0() -> OpAttrs {
3952            OpAttrs {
3953                target_shape_rel: Some(ShapeExpr::SameAs { operand: 0 }),
3954                ..OpAttrs::default()
3955            }
3956        }
3957
3958        #[test]
3959        fn rel_region_emits_polymorphically_across_shapes() {
3960            // The headline polymorphism: ONE region datum —
3961            // Add(bind0, BroadcastTo{SameAs{0}}(bind1)) — emitted at two
3962            // different shapes produces the correct target BOTH times
3963            // (impossible with absolute attrs: a baked target matches exactly
3964            // one shape).
3965            let region = op_node(
3966                OpTag::Add,
3967                OpAttrs::default(),
3968                vec![
3969                    bind(0),
3970                    op_node(OpTag::BroadcastTo, bcast_same_as_0(), vec![bind(1)]),
3971                ],
3972            );
3973            let mut g = Graph::new();
3974            let x1 = cst(&mut g, &[2, 3]);
3975            let t1 = cst(&mut g, &[1, 3]);
3976            let r1 = emit_region(&mut g, &region, &[x1, t1], &[]);
3977            assert!(matches!(g.node(r1).op, Op::Add));
3978            assert_eq!(g.node(r1).shape, Shape::from_dims(&[2, 3]));
3979            let b1 = g.node(r1).inputs[1];
3980            assert_eq!(g.node(b1).op, Op::BroadcastTo(Shape::from_dims(&[2, 3])));
3981            assert_eq!(g.node(b1).shape, Shape::from_dims(&[2, 3]));
3982
3983            // The SAME region datum at different shapes → a different target.
3984            let x2 = cst(&mut g, &[4, 5]);
3985            let t2 = cst(&mut g, &[1, 5]);
3986            let r2 = emit_region(&mut g, &region, &[x2, t2], &[]);
3987            assert_eq!(g.node(r2).shape, Shape::from_dims(&[4, 5]));
3988            let b2 = g.node(r2).inputs[1];
3989            assert_eq!(g.node(b2).op, Op::BroadcastTo(Shape::from_dims(&[4, 5])));
3990        }
3991
3992        #[test]
3993        fn two_operand_max_frame_region_declines_through_emit() {
3994            // I1 end-to-end: the ONE region spelling that WANTS the per-axis-max
3995            // frame — Mul(BroadcastTo(a[2,1]), BroadcastTo(b[1,3])) → [2,3],
3996            // which Fuel's primitive `Mul` requires explicitly (`primitive_shape`
3997            // takes in[0]'s shape, it does not broadcast). `SameAs` cannot name
3998            // [2,3], so the resolving emit surfaces the typed Dims-class gap
3999            // instead of emitting a partial-frame BroadcastTo.
4000            let region = op_node(
4001                OpTag::Mul,
4002                OpAttrs::default(),
4003                vec![
4004                    op_node(OpTag::BroadcastTo, bcast_same_as_0(), vec![bind(0)]),
4005                    op_node(OpTag::BroadcastTo, bcast_same_as_0(), vec![bind(1)]),
4006                ],
4007            );
4008            let mut g = Graph::new();
4009            let a = cst(&mut g, &[2, 1]);
4010            let b = cst(&mut g, &[1, 3]);
4011            assert_eq!(
4012                try_emit_region(&mut g, &region, &[a, b], &[]),
4013                Err(RelAttrError::FrameNotExpressible {
4014                    field: "target_shape",
4015                    frame: vec![2, 3],
4016                    missing_ctor: fuel_kernel_seam_types::shape_expr::TAG_DIMS,
4017                }),
4018            );
4019        }
4020
4021        #[test]
4022        fn broadcast_rank_raise_materializes_the_legacy_reshape_pad() {
4023            // D4: a rank-1 bind1 broadcast to rank-3 bind0's shape — the
4024            // resolver must first push the legacy `Reshape` (1-padded left,
4025            // right-aligned; `registry::rope`'s hand-built broadcast prep).
4026            let region = op_node(
4027                OpTag::Mul,
4028                OpAttrs::default(),
4029                vec![
4030                    bind(0),
4031                    op_node(OpTag::BroadcastTo, bcast_same_as_0(), vec![bind(1)]),
4032                ],
4033            );
4034            let mut g = Graph::new();
4035            let x = cst(&mut g, &[2, 3, 4]);
4036            let t = cst(&mut g, &[4]);
4037            // Hand-built legacy reference:
4038            // Reshape([1,1,4])(t) → BroadcastTo([2,3,4]) → Mul(x, ·).
4039            let pad_shape = Shape::from_dims(&[1, 1, 4]);
4040            let full = Shape::from_dims(&[2, 3, 4]);
4041            let pad = g.push(Node {
4042                op: Op::Reshape(pad_shape.clone()),
4043                inputs: vec![t],
4044                shape: pad_shape,
4045                dtype: DType::F32,
4046            });
4047            let bc = g.push(Node {
4048                op: Op::BroadcastTo(full.clone()),
4049                inputs: vec![pad],
4050                shape: full.clone(),
4051                dtype: DType::F32,
4052            });
4053            let reference = g.push(Node {
4054                op: Op::Mul,
4055                inputs: vec![x, bc],
4056                shape: full,
4057                dtype: DType::F32,
4058            });
4059
4060            let emitted = emit_region(&mut g, &region, &[x, t], &[]);
4061            assert_structural_eq(&g, reference, emitted);
4062        }
4063
4064        #[test]
4065        fn concrete_broadcast_rank_raise_also_pads() {
4066            // D4 applies uniformly: an ABSOLUTE rank-raising BroadcastTo also
4067            // materializes the pad (deterministic emission, matches the graph
4068            // builders' right-aligned rank-raising semantics). Equal-rank
4069            // broadcasts stay pad-free (pinned by the softmax parity oracle).
4070            let region = op_node(
4071                OpTag::BroadcastTo,
4072                OpAttrs {
4073                    target_shape: vec![2, 3, 4],
4074                    ..OpAttrs::default()
4075                },
4076                vec![bind(0)],
4077            );
4078            let mut g = Graph::new();
4079            let t = cst(&mut g, &[3, 4]);
4080            let emitted = emit_region(&mut g, &region, &[t], &[]);
4081            assert_eq!(
4082                g.node(emitted).op,
4083                Op::BroadcastTo(Shape::from_dims(&[2, 3, 4]))
4084            );
4085            let pad = g.node(emitted).inputs[0];
4086            assert_eq!(
4087                g.node(pad).op,
4088                Op::Reshape(Shape::from_dims(&[1, 3, 4])),
4089                "rank-raise inserts the legacy 1-padded-left Reshape",
4090            );
4091            assert_eq!(g.node(pad).inputs, vec![t]);
4092        }
4093
4094        #[test]
4095        fn scalar_cursor_fill_stays_pre_order_after_the_reorder() {
4096            // Risk-2 guard: children are now EMITTED first, but the scalar
4097            // cursor fill stays PRE-order (parent before descent) — the
4098            // canonical authoring order `match_region_extract` records.
4099            let region = op_node(
4100                OpTag::AddScalar,
4101                OpAttrs::default(),
4102                vec![op_node(OpTag::MulScalar, OpAttrs::default(), vec![bind(0)])],
4103            );
4104            let mut g = Graph::new();
4105            let x = cst(&mut g, &[4]);
4106            let root = emit_region(&mut g, &region, &[x], &[10.0, 20.0]);
4107            assert!(
4108                matches!(g.node(root).op, Op::AddScalar(v) if v == 10.0),
4109                "parent takes scalars[0] (pre-order), got {:?}",
4110                g.node(root).op,
4111            );
4112            let child = g.node(root).inputs[0];
4113            assert!(
4114                matches!(g.node(child).op, Op::MulScalar(v) if v == 20.0),
4115                "child takes scalars[1], got {:?}",
4116                g.node(child).op,
4117            );
4118        }
4119
4120        #[test]
4121        fn validate_accepts_a_rel_attr_region() {
4122            // Born-red: today tag_to_op(BroadcastTo, {empty target_shape}) →
4123            // None → UnRepresentable. The rel-probe must accept the template.
4124            let region = op_node(
4125                OpTag::Add,
4126                OpAttrs::default(),
4127                vec![
4128                    bind(0),
4129                    op_node(OpTag::BroadcastTo, bcast_same_as_0(), vec![bind(1)]),
4130                ],
4131            );
4132            register_runtime_fused("t3::rel_bcast", region)
4133                .expect("a rel-attr region is registrable");
4134        }
4135
4136        #[test]
4137        fn validate_rejects_rel_abs_conflict_and_bind_out_of_range() {
4138            // rel+abs both set → a typed authoring reject, never a silent
4139            // precedence.
4140            let conflicted = op_node(
4141                OpTag::BroadcastTo,
4142                OpAttrs {
4143                    target_shape: vec![2, 3],
4144                    target_shape_rel: Some(ShapeExpr::SameAs { operand: 0 }),
4145                    ..OpAttrs::default()
4146                },
4147                vec![bind(0)],
4148            );
4149            assert_eq!(
4150                register_runtime_fused("t3::conflict", conflicted),
4151                Err(RuntimeFusedError::InvalidRelAttrs {
4152                    tag: OpTag::BroadcastTo,
4153                    error: RelAttrError::RelAbsConflict {
4154                        field: "target_shape"
4155                    },
4156                }),
4157            );
4158            // A bind reference outside the region's bind space can never
4159            // resolve at ANY shape → a typed authoring reject.
4160            let oob = op_node(
4161                OpTag::BroadcastTo,
4162                OpAttrs {
4163                    target_shape_rel: Some(ShapeExpr::SameAs { operand: 7 }),
4164                    ..OpAttrs::default()
4165                },
4166                vec![bind(0)],
4167            );
4168            assert_eq!(
4169                register_runtime_fused("t3::oob", oob),
4170                Err(RuntimeFusedError::InvalidRelAttrs {
4171                    tag: OpTag::BroadcastTo,
4172                    error: RelAttrError::Expr(
4173                        fuel_kernel_seam_types::shape_expr::ShapeExprError::OperandOutOfRange {
4174                            operand: 7,
4175                            operands: 1,
4176                        },
4177                    ),
4178                }),
4179            );
4180        }
4181
4182        #[test]
4183        fn decompose_rel_resolution_failure_is_a_fixpoint_not_a_crash() {
4184            // slice_start_rel = 0 − 2 → Negative at emit time. Registration
4185            // TOLERATES it (a value-dependent decline at the probe shape is
4186            // not an authoring error); the decompose-path resolution failure
4187            // surfaces as a no-op fixpoint (G2), never a panic.
4188            let neg = Dim::Sub(Box::new(Dim::Const(0)), Box::new(Dim::Const(2)));
4189            let region = op_node(
4190                OpTag::Slice,
4191                OpAttrs {
4192                    axis: Some(1),
4193                    slice_start_rel: Some(neg),
4194                    slice_len: Some(1),
4195                    ..OpAttrs::default()
4196                },
4197                vec![bind(0)],
4198            );
4199            let id = register_runtime_fused("t3::neg_slice", region)
4200                .expect("a value-dependent decline still registers");
4201            let mut g = Graph::new();
4202            let x = cst(&mut g, &[2, 4]);
4203            let fused = g.push(Node {
4204                op: Op::Fused(id, FusedOpParams::Runtime { scalars: vec![] }),
4205                inputs: vec![x],
4206                shape: Shape::from_dims(&[2, 4]),
4207                dtype: DType::F32,
4208            });
4209            assert_eq!(
4210                decompose_region(&mut g, fused),
4211                fused,
4212                "resolution decline ⇒ fixpoint"
4213            );
4214        }
4215
4216        #[test]
4217        fn try_emit_region_surfaces_typed_resolution_errors() {
4218            // Negative → typed error from the fallible entry, never a panic.
4219            let neg = Dim::Sub(Box::new(Dim::Const(0)), Box::new(Dim::Const(2)));
4220            let region = op_node(
4221                OpTag::Slice,
4222                OpAttrs {
4223                    axis: Some(1),
4224                    slice_start_rel: Some(neg),
4225                    slice_len: Some(1),
4226                    ..OpAttrs::default()
4227                },
4228                vec![bind(0)],
4229            );
4230            let mut g = Graph::new();
4231            let x = cst(&mut g, &[2, 4]);
4232            assert_eq!(
4233                try_emit_region(&mut g, &region, &[x], &[]),
4234                Err(RelAttrError::Negative {
4235                    field: "slice_start",
4236                    value: -2
4237                }),
4238            );
4239            // A graph-side SYMBOLIC bind extent maps to the §6.20 SYMBOLIC
4240            // sentinel → SymbolicGap (the surfaced-gap posture, §6.20-0004).
4241            let region = op_node(OpTag::BroadcastTo, bcast_same_as_0(), vec![bind(0)]);
4242            let dyn_shape = Shape::from_dims(&[2, 8]).with_dynamic_axis(1, 0, fuel_ir::SymId(0));
4243            let d = g.push(Node {
4244                op: Op::Const,
4245                inputs: vec![],
4246                shape: dyn_shape,
4247                dtype: DType::F32,
4248            });
4249            assert_eq!(
4250                try_emit_region(&mut g, &region, &[d], &[]),
4251                Err(RelAttrError::SymbolicGap {
4252                    field: "target_shape"
4253                }),
4254            );
4255        }
4256    }
4257
4258    // ---- T5 (Increment C slice 1): identity-share of repeated subtrees ----
4259    //
4260    // A `PatternNode` recipe is a TREE; a DAG recipe (softmax's shared
4261    // `e = Exp(..)` interior, consumed by both the denominator reduce and the
4262    // final Div) is spelled by REPEATING the subtree. `emit` must emit a
4263    // repeated slot-free subtree ONCE per emit call (identity-share), so the
4264    // emitted graph is the DAG, not a duplicated-compute tree. Subtrees with
4265    // OPEN scalar slots are never shared — each occurrence takes its own
4266    // cursor value. (The flat-DAG node table with real CSE is slice 3.)
4267
4268    #[test]
4269    fn emit_shares_repeated_slot_free_subtrees() {
4270        let region = op_node(
4271            OpTag::Add,
4272            OpAttrs::default(),
4273            vec![
4274                op_node(OpTag::Exp, OpAttrs::default(), vec![bind(0)]),
4275                op_node(OpTag::Exp, OpAttrs::default(), vec![bind(0)]),
4276            ],
4277        );
4278        let mut g = Graph::new();
4279        let x = g.push(Node {
4280            op: Op::Const,
4281            inputs: vec![],
4282            shape: Shape::from_dims(&[4]),
4283            dtype: DType::F32,
4284        });
4285        let root = emit_region(&mut g, &region, &[x], &[]);
4286        let add = g.node(root);
4287        assert!(matches!(add.op, Op::Add));
4288        assert_eq!(
4289            add.inputs[0], add.inputs[1],
4290            "structurally-equal slot-free subtrees must share ONE emitted node",
4291        );
4292    }
4293
4294    #[test]
4295    fn emit_does_not_share_subtrees_with_open_scalar_slots() {
4296        // Two open MulScalar slots take DIFFERENT cursor values (pre-order
4297        // fill) — sharing them would silently drop the second live value.
4298        let region = op_node(
4299            OpTag::Add,
4300            OpAttrs::default(),
4301            vec![
4302                op_node(OpTag::MulScalar, OpAttrs::default(), vec![bind(0)]),
4303                op_node(OpTag::MulScalar, OpAttrs::default(), vec![bind(0)]),
4304            ],
4305        );
4306        let mut g = Graph::new();
4307        let x = g.push(Node {
4308            op: Op::Const,
4309            inputs: vec![],
4310            shape: Shape::from_dims(&[4]),
4311            dtype: DType::F32,
4312        });
4313        let root = emit_region(&mut g, &region, &[x], &[2.0, 3.0]);
4314        let a = g.node(root).inputs[0];
4315        let b = g.node(root).inputs[1];
4316        assert_ne!(a, b, "open-slot subtrees are never shared");
4317        assert!(matches!(g.node(a).op, Op::MulScalar(v) if v == 2.0));
4318        assert!(matches!(g.node(b).op, Op::MulScalar(v) if v == 3.0));
4319    }
4320
4321    #[test]
4322    fn emit_matches_cast_over_add_reference() {
4323        // Exercises the dtype path through assert_structural_eq: a hand-built
4324        // two-node reference `Cast(F16)(Add(a, b))` vs the emitted region.
4325        use fuel_ir::{DType, Shape};
4326        let mut g = Graph::new();
4327        let sh = Shape::from_dims(&[4]);
4328        let a = g.push(Node {
4329            op: Op::Const,
4330            inputs: vec![],
4331            shape: sh.clone(),
4332            dtype: DType::F32,
4333        });
4334        let b = g.push(Node {
4335            op: Op::Const,
4336            inputs: vec![],
4337            shape: sh.clone(),
4338            dtype: DType::F32,
4339        });
4340        // Reference graph.
4341        let add = g.push(Node {
4342            op: Op::Add,
4343            inputs: vec![a, b],
4344            shape: sh.clone(),
4345            dtype: DType::F32,
4346        });
4347        let reference = g.push(Node {
4348            op: Op::Cast(DType::F16),
4349            inputs: vec![add],
4350            shape: sh.clone(),
4351            dtype: DType::F16,
4352        });
4353
4354        let region = op_node(
4355            OpTag::Cast,
4356            OpAttrs {
4357                cast_dtype: Some("f16".into()),
4358                ..OpAttrs::default()
4359            },
4360            vec![op_node(
4361                OpTag::Add,
4362                OpAttrs::default(),
4363                vec![bind(0), bind(1)],
4364            )],
4365        );
4366        let emitted = emit_region(&mut g, &region, &[a, b], &[]);
4367        assert_structural_eq(&g, reference, emitted);
4368    }
4369
4370    // ---- T5 (Increment C slice 1): decompose_via_recipe bridge + the
4371    // softmax_last_dim pilot migration --------------------------------------
4372    //
4373    // The registry bridge (`crate::registry::decompose_via_recipe`, design
4374    // D6) makes a static entry's `decompose` a re-emit of portable
4375    // `PatternNode` DATA: node inputs are the binds, a per-entry projection
4376    // supplies the open-slot scalars, the resolving emit does the rest. ANY
4377    // failure — wrong params payload, a semantics-absent op token (the
4378    // flip-withdrawal posture: unknown/non-registry tokens are surfaced
4379    // honest-miss declines, NEVER accepted, NEVER a crash), a bind/arity or
4380    // slot-count mismatch, a rel-resolution decline at these shapes — returns
4381    // `id` (fixpoint, G2), never panics.
4382
4383    mod recipe_bridge {
4384        use super::super::*;
4385        use super::{bind, frozen_legacy_softmax_decompose, op_node};
4386        use crate::registry::{FusedOps, decompose_via_recipe};
4387        use fuel_ir::{DType, Shape};
4388        use std::collections::HashMap;
4389
4390        /// Tiny f64 reference interpreter over the primitive vocabulary the
4391        /// two softmax spellings use (Const leaves, last-axis reduces, keepdim
4392        /// restores, last-dim broadcast, elementwise). BOTH parity sides run
4393        /// through it, with in-order accumulation per row — so the bit-exact
4394        /// assert isolates recipe STRUCTURE; float noise can't differ between
4395        /// two evaluations of the same interpreter. (Not code evaluation: a
4396        /// closed match over our own `Op` enum — no dynamic execution.)
4397        fn eval(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
4398            let node = g.node(id);
4399            match &node.op {
4400                Op::Const => leaves.get(&id).expect("leaf data provided").clone(),
4401                Op::Exp => eval(g, node.inputs[0], leaves)
4402                    .iter()
4403                    .map(|v| v.exp())
4404                    .collect(),
4405                Op::Sub => {
4406                    let a = eval(g, node.inputs[0], leaves);
4407                    let b = eval(g, node.inputs[1], leaves);
4408                    a.iter().zip(&b).map(|(x, y)| x - y).collect()
4409                }
4410                Op::Div => {
4411                    let a = eval(g, node.inputs[0], leaves);
4412                    let b = eval(g, node.inputs[1], leaves);
4413                    a.iter().zip(&b).map(|(x, y)| x / y).collect()
4414                }
4415                // Last-axis reduces — one arm per spelling pair, identical
4416                // in-order fold.
4417                Op::MaxDim(_) | Op::ReduceMaxTo(_) => {
4418                    let input = eval(g, node.inputs[0], leaves);
4419                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
4420                    input
4421                        .chunks(last)
4422                        .map(|row| row.iter().copied().fold(f64::NEG_INFINITY, f64::max))
4423                        .collect()
4424                }
4425                Op::SumDim(_) | Op::ReduceSumTo(_) => {
4426                    let input = eval(g, node.inputs[0], leaves);
4427                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
4428                    input.chunks(last).map(|row| row.iter().sum()).collect()
4429                }
4430                // Metadata-only keepdim restores.
4431                Op::Unsqueeze { .. } | Op::Reshape(_) => eval(g, node.inputs[0], leaves),
4432                // Broadcast a keepdim/reduced tensor back along the last axis.
4433                Op::BroadcastTo(target) => {
4434                    let input = eval(g, node.inputs[0], leaves);
4435                    let out_n: usize = target.dims().iter().product();
4436                    let last = *target.dims().last().unwrap();
4437                    assert_eq!(
4438                        input.len() * last,
4439                        out_n,
4440                        "broadcast is a last-dim repeat in these graphs",
4441                    );
4442                    input
4443                        .iter()
4444                        .flat_map(|&v| std::iter::repeat_n(v, last))
4445                        .collect()
4446                }
4447                other => panic!("eval: unhandled op {other:?}"),
4448            }
4449        }
4450
4451        fn softmax_fused_node(g: &mut Graph, dims: &[usize]) -> (NodeId, NodeId) {
4452            let sh = Shape::from_dims(dims);
4453            let x = g.push(Node {
4454                op: Op::Const,
4455                inputs: vec![],
4456                shape: sh.clone(),
4457                dtype: DType::F32,
4458            });
4459            let fused = g.push(Node {
4460                op: Op::Fused(FusedOps::SOFTMAX_LAST_DIM, FusedOpParams::SoftmaxLastDim),
4461                inputs: vec![x],
4462                shape: sh,
4463                dtype: DType::F32,
4464            });
4465            (x, fused)
4466        }
4467
4468        /// T5 red (a): ONE recipe datum decomposes at BOTH rank 2 and rank 3
4469        /// (the polymorphism the baked-shape legacy body never had), and its
4470        /// numerics match the FROZEN legacy builder bit-exactly under the
4471        /// shared reference interpreter.
4472        #[test]
4473        fn softmax_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
4474            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
4475                let mut g = Graph::new();
4476                let (x, fused) = softmax_fused_node(&mut g, &dims);
4477                let sh = Shape::from_dims(&dims);
4478                let new_root = crate::registry::softmax_last_dim::decompose(
4479                    &mut g,
4480                    fused,
4481                    &FusedOpParams::SoftmaxLastDim,
4482                );
4483                assert_ne!(new_root, fused, "recipe decompose must fire at {dims:?}");
4484                assert_eq!(g.node(new_root).shape, sh, "softmax is shape-preserving");
4485                assert_eq!(g.node(new_root).dtype, DType::F32);
4486
4487                let legacy_root =
4488                    frozen_legacy_softmax_decompose(&mut g, fused, &FusedOpParams::SoftmaxLastDim);
4489
4490                let n: usize = dims.iter().product();
4491                let data: Vec<f64> = (0..n)
4492                    .map(|i| ((i as f64) * 0.37).sin() * 3.0 - 0.5)
4493                    .collect();
4494                let mut leaves = HashMap::new();
4495                leaves.insert(x, data);
4496                let got = eval(&g, new_root, &leaves);
4497                let want = eval(&g, legacy_root, &leaves);
4498                assert_eq!(got.len(), want.len());
4499                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
4500                    assert_eq!(
4501                        a.to_bits(),
4502                        b.to_bits(),
4503                        "softmax[{i}] at {dims:?}: recipe={a} vs legacy={b}",
4504                    );
4505                }
4506            }
4507        }
4508
4509        /// T5 red (b): the structural golden — the ratified D3 shrink-via-swap
4510        /// spelling, 9 op nodes with the `e = Exp(..)` interior SHARED (node
4511        /// identity) between the denominator reduce and the final Div.
4512        #[test]
4513        fn softmax_recipe_emits_the_nine_node_shared_spelling() {
4514            let mut g = Graph::new();
4515            let (x, fused) = softmax_fused_node(&mut g, &[2, 4]);
4516            let sh = Shape::from_dims(&[2, 4]);
4517            let root = crate::registry::softmax_last_dim::decompose(
4518                &mut g,
4519                fused,
4520                &FusedOpParams::SoftmaxLastDim,
4521            );
4522
4523            // out = Div(e, db)
4524            assert!(matches!(g.node(root).op, Op::Div));
4525            let e = g.node(root).inputs[0];
4526            let db = g.node(root).inputs[1];
4527            assert!(matches!(g.node(e).op, Op::Exp));
4528            assert_eq!(g.node(db).op, Op::BroadcastTo(sh.clone()));
4529            // db = BroadcastTo(Unsqueeze(SumDim(e))) — the SAME e node.
4530            let u2 = g.node(db).inputs[0];
4531            assert!(matches!(g.node(u2).op, Op::Unsqueeze { dim: 1 }));
4532            assert_eq!(g.node(u2).shape, Shape::from_dims(&[2, 1]));
4533            let d = g.node(u2).inputs[0];
4534            assert!(matches!(g.node(d).op, Op::SumDim(1)));
4535            assert_eq!(g.node(d).shape, Shape::from_dims(&[2]));
4536            assert_eq!(
4537                g.node(d).inputs[0],
4538                e,
4539                "the denominator reduces the SHARED Exp node — identity-share, not a duplicate",
4540            );
4541            // e = Exp(Sub(x, mb)); mb = BroadcastTo(Unsqueeze(MaxDim(x))).
4542            let s = g.node(e).inputs[0];
4543            assert!(matches!(g.node(s).op, Op::Sub));
4544            assert_eq!(g.node(s).inputs[0], x);
4545            let mb = g.node(s).inputs[1];
4546            assert_eq!(g.node(mb).op, Op::BroadcastTo(sh.clone()));
4547            let u1 = g.node(mb).inputs[0];
4548            assert!(matches!(g.node(u1).op, Op::Unsqueeze { dim: 1 }));
4549            let m = g.node(u1).inputs[0];
4550            assert!(matches!(g.node(m).op, Op::MaxDim(1)));
4551            assert_eq!(g.node(m).inputs[0], x);
4552            // 9 op nodes + the x leaf = 10 reachable (NO duplicated interior).
4553            assert_eq!(
4554                crate::topo_order_multi(&g, &[root]).len(),
4555                10,
4556                "MaxDim/Unsqueeze/Bcast/Sub/Exp/SumDim/Unsqueeze/Bcast/Div + leaf",
4557            );
4558        }
4559
4560        /// T5 red (c): totality — a wrong params payload is a typed decline
4561        /// surfaced as a fixpoint (G2), never a panic, and declines BEFORE any
4562        /// emission (no partial nodes).
4563        #[test]
4564        fn softmax_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
4565            let mut g = Graph::new();
4566            let (_x, fused) = softmax_fused_node(&mut g, &[2, 4]);
4567            let before = g.len();
4568            let out =
4569                crate::registry::softmax_last_dim::decompose(&mut g, fused, &FusedOpParams::Rope);
4570            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
4571            assert_eq!(g.len(), before, "declined before any emission");
4572        }
4573
4574        /// INJECTED item (2), the flip-withdrawal posture (Baracuda #68 /
4575        /// KISS-Ops closed registry): an op token with no registry semantics —
4576        /// the in-memory analog of the withdrawn reverse-scan "flip" spelling —
4577        /// must surface as a typed honest-miss decline: the node stays fused
4578        /// (fixpoint), NEVER accepted, NEVER a crash. The fabricated recipe
4579        /// stands in for a foreign token by carrying `OpTag::Clamp` — a tag
4580        /// with NO primitive re-emission today (`tag_to_op` → `None`), exactly
4581        /// the semantics-absent posture an unregistered op name resolves to.
4582        /// (This stand-in was `OpTag::PowI` until Increment C carriers A3 gave
4583        /// PowI its i32-exponent carrier; `Clamp` — still awaiting a two-scalar
4584        /// carrier — is the current canonical carrier-less token.) If/when the
4585        /// token registers, it becomes a NAMED-op resolution case — semantics
4586        /// arrive via registration, never via silent acceptance here.
4587        #[test]
4588        fn decompose_via_recipe_declines_an_unknown_token_recipe() {
4589            let fabricated = op_node(
4590                OpTag::Clamp,
4591                OpAttrs {
4592                    scalars: vec![0.0, 1.0],
4593                    ..OpAttrs::default()
4594                },
4595                vec![bind(0)],
4596            );
4597            let mut g = Graph::new();
4598            let (_x, fused) = softmax_fused_node(&mut g, &[2, 4]);
4599            let before = g.len();
4600            let out = decompose_via_recipe(&mut g, fused, &fabricated, Some(Vec::new()));
4601            assert_eq!(out, fused, "semantics-absent token ⇒ honest-miss fixpoint");
4602            assert_eq!(
4603                g.len(),
4604                before,
4605                "declined BEFORE any emission — no partial nodes"
4606            );
4607        }
4608
4609        /// The bridge's bind/input arity guard: a recipe over 2 binds cannot
4610        /// decompose a 1-input node — fixpoint, not a crash (and not a
4611        /// misbound emission).
4612        #[test]
4613        fn decompose_via_recipe_bind_arity_mismatch_is_a_fixpoint() {
4614            let recipe = op_node(OpTag::Add, OpAttrs::default(), vec![bind(0), bind(1)]);
4615            let mut g = Graph::new();
4616            let (_x, fused) = softmax_fused_node(&mut g, &[2, 4]);
4617            let before = g.len();
4618            let out = decompose_via_recipe(&mut g, fused, &recipe, Some(Vec::new()));
4619            assert_eq!(out, fused, "bind/input arity mismatch ⇒ fixpoint");
4620            assert_eq!(g.len(), before, "declined before any emission");
4621        }
4622    }
4623
4624    // rope migration (Increment C slice 1, T6) ------------------------------
4625    //
4626    // Rope's 11-node imperative body becomes a 9-node portable `PatternNode`
4627    // DATA recipe: cos/sin broadcasts carry `SameAs { operand: 0 }`, the two
4628    // half-slices carry `DimExpr` start/len over the Bind space (the
4629    // reference-doc worked example: `start=Const(0), len=Div(E,2)` /
4630    // `start=Div(E,2), len=Sub(E, Div(E,2))`), the last-axis Concat carries
4631    // `axis_last`, and the two leading-1-padded prep `Reshape`s are NOT in the
4632    // datum — the emit resolver MATERIALIZES them (D4) only where the
4633    // broadcast target out-ranks its operand. Consequence: at a rank-RAISING
4634    // broadcast (the real attention consumer: cos/sin `[seq,d]`, x `[..,seq,d]`
4635    // rank ≥ 3) emission is BYTE-IDENTICAL to legacy (11 nodes, both pads); at
4636    // EQUAL rank (x itself rank 2 = `[seq,d]`) the recipe emits the 9-node
4637    // form, eliding legacy's no-op `Reshape([seq,d]→[seq,d])` — numerically
4638    // identical, structurally leaner. D4 is shared with softmax/norms and MUST
4639    // NOT add reshapes at equal rank (that would break the softmax parity
4640    // oracle), so the equal-rank elision is intrinsic, not a defect.
4641    mod rope_recipe {
4642        use super::super::*;
4643        use super::frozen_legacy_rope_decompose;
4644        use crate::registry::{FusedOps, rope};
4645        use fuel_ir::{DType, Shape};
4646        use std::collections::HashMap;
4647
4648        /// Tiny f64 reference interpreter over the rope primitive vocabulary
4649        /// (Const leaves, metadata-only rank-pad Reshape, leading-dim
4650        /// BroadcastTo, last-axis Slice/Concat, Neg, elementwise Mul/Add).
4651        /// BOTH parity sides run through it in identical in-order arithmetic —
4652        /// so a bit-exact assert isolates recipe STRUCTURE. (Not code
4653        /// evaluation: a closed match over our own `Op` enum.)
4654        fn eval_rope(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
4655            let node = g.node(id);
4656            match &node.op {
4657                Op::Const => leaves.get(&id).expect("leaf data provided").clone(),
4658                Op::Neg => eval_rope(g, node.inputs[0], leaves)
4659                    .iter()
4660                    .map(|v| -v)
4661                    .collect(),
4662                Op::Mul => {
4663                    let a = eval_rope(g, node.inputs[0], leaves);
4664                    let b = eval_rope(g, node.inputs[1], leaves);
4665                    a.iter().zip(&b).map(|(x, y)| x * y).collect()
4666                }
4667                Op::Add => {
4668                    let a = eval_rope(g, node.inputs[0], leaves);
4669                    let b = eval_rope(g, node.inputs[1], leaves);
4670                    a.iter().zip(&b).map(|(x, y)| x + y).collect()
4671                }
4672                // Metadata-only leading-1 rank-pad: same row-major order.
4673                Op::Reshape(_) => eval_rope(g, node.inputs[0], leaves),
4674                // Broadcast a `[1,..,1,seq,d]` inner block over leading dims:
4675                // the block (all of `input`, since the leading dims are 1)
4676                // tiles to fill the target — row-major `input[i % block]`.
4677                Op::BroadcastTo(target) => {
4678                    let input = eval_rope(g, node.inputs[0], leaves);
4679                    let out_n: usize = target.dims().iter().product();
4680                    (0..out_n).map(|i| input[i % input.len()]).collect()
4681                }
4682                Op::Slice { dim, start, len } => {
4683                    let input = eval_rope(g, node.inputs[0], leaves);
4684                    let in_dims = g.node(node.inputs[0]).shape.dims().to_vec();
4685                    let last = in_dims.len() - 1;
4686                    assert_eq!(*dim, last, "rope slices along the last axis");
4687                    let row = in_dims[last];
4688                    input
4689                        .chunks(row)
4690                        .flat_map(|r| r[*start..*start + *len].to_vec())
4691                        .collect()
4692                }
4693                Op::Concat { dim } => {
4694                    let a = eval_rope(g, node.inputs[0], leaves);
4695                    let b = eval_rope(g, node.inputs[1], leaves);
4696                    let a_last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
4697                    let b_last = *g.node(node.inputs[1]).shape.dims().last().unwrap();
4698                    let last = g.node(node.inputs[0]).shape.dims().len() - 1;
4699                    assert_eq!(*dim, last, "rope concats along the last axis");
4700                    let mut out = Vec::with_capacity(a.len() + b.len());
4701                    let mut ai = a.chunks(a_last);
4702                    let mut bi = b.chunks(b_last);
4703                    loop {
4704                        match (ai.next(), bi.next()) {
4705                            (Some(ra), Some(rb)) => {
4706                                out.extend_from_slice(ra);
4707                                out.extend_from_slice(rb);
4708                            }
4709                            (None, None) => break,
4710                            _ => panic!("concat row-count mismatch"),
4711                        }
4712                    }
4713                    out
4714                }
4715                other => panic!("eval_rope: unhandled op {other:?}"),
4716            }
4717        }
4718
4719        /// Build a fused Rope node over `x [..,seq,d]`, `cos [seq,d]`,
4720        /// `sin [seq,d]`. Returns `(x, cos, sin, fused)`.
4721        fn rope_fused_node(g: &mut Graph, x_dims: &[usize]) -> (NodeId, NodeId, NodeId, NodeId) {
4722            let rank = x_dims.len();
4723            let table_dims = [x_dims[rank - 2], x_dims[rank - 1]];
4724            let x_sh = Shape::from_dims(x_dims);
4725            let t_sh = Shape::from_dims(&table_dims);
4726            let x = g.push(Node {
4727                op: Op::Const,
4728                inputs: vec![],
4729                shape: x_sh.clone(),
4730                dtype: DType::F32,
4731            });
4732            let cos = g.push(Node {
4733                op: Op::Const,
4734                inputs: vec![],
4735                shape: t_sh.clone(),
4736                dtype: DType::F32,
4737            });
4738            let sin = g.push(Node {
4739                op: Op::Const,
4740                inputs: vec![],
4741                shape: t_sh,
4742                dtype: DType::F32,
4743            });
4744            let fused = g.push(Node {
4745                op: Op::Fused(FusedOps::ROPE, FusedOpParams::Rope),
4746                inputs: vec![x, cos, sin],
4747                shape: x_sh,
4748                dtype: DType::F32,
4749            });
4750            (x, cos, sin, fused)
4751        }
4752
4753        /// T6 red (a): ONE recipe datum decomposes at BOTH rank 2 and rank 4
4754        /// — the shape/rank polymorphism the baked-shape legacy body never had
4755        /// — and its numerics match the FROZEN legacy builder bit-exactly
4756        /// under the shared reference interpreter.
4757        #[test]
4758        fn rope_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
4759            for x_dims in [vec![2usize, 4], vec![1, 2, 3, 8]] {
4760                let mut g = Graph::new();
4761                let (x, cos, sin, fused) = rope_fused_node(&mut g, &x_dims);
4762                let x_sh = Shape::from_dims(&x_dims);
4763                let rank = x_dims.len();
4764                let seq = x_dims[rank - 2];
4765                let d = x_dims[rank - 1];
4766
4767                let new_root = rope::decompose(&mut g, fused, &FusedOpParams::Rope);
4768                assert_ne!(new_root, fused, "recipe decompose must fire at {x_dims:?}");
4769                assert_eq!(g.node(new_root).shape, x_sh, "rope is shape-preserving");
4770                assert_eq!(g.node(new_root).dtype, DType::F32);
4771
4772                let legacy_root = frozen_legacy_rope_decompose(&mut g, fused, &FusedOpParams::Rope);
4773
4774                // Distinct, deterministic leaf data for x / cos / sin.
4775                let x_n: usize = x_dims.iter().product();
4776                let t_n = seq * d;
4777                let x_data: Vec<f64> = (0..x_n)
4778                    .map(|i| ((i as f64) * 0.31).sin() * 2.0 - 0.4)
4779                    .collect();
4780                let cos_data: Vec<f64> =
4781                    (0..t_n).map(|i| ((i as f64) * 0.17 + 0.5).cos()).collect();
4782                let sin_data: Vec<f64> =
4783                    (0..t_n).map(|i| ((i as f64) * 0.23 - 0.2).sin()).collect();
4784                let mut leaves = HashMap::new();
4785                leaves.insert(x, x_data);
4786                leaves.insert(cos, cos_data);
4787                leaves.insert(sin, sin_data);
4788
4789                let got = eval_rope(&g, new_root, &leaves);
4790                let want = eval_rope(&g, legacy_root, &leaves);
4791                assert_eq!(got.len(), want.len(), "element count at {x_dims:?}");
4792                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
4793                    assert_eq!(
4794                        a.to_bits(),
4795                        b.to_bits(),
4796                        "rope[{i}] at {x_dims:?}: recipe={a} vs legacy={b}",
4797                    );
4798                }
4799            }
4800        }
4801
4802        /// T6 red (b): at a rank-RAISING broadcast (rank 4 — the real attention
4803        /// consumer's shape), the recipe emission is BYTE-IDENTICAL to legacy:
4804        /// D4 materializes both leading-1-padded prep `Reshape`s, so the whole
4805        /// 11-node DAG matches node-for-node (op, shape, dtype, wiring). This
4806        /// is the byte-identity guarantee that retires all backend risk for the
4807        /// live rope-decompose path.
4808        #[test]
4809        fn rope_recipe_is_byte_identical_to_legacy_at_rank_raise() {
4810            let mut g = Graph::new();
4811            let (_x, _cos, _sin, fused) = rope_fused_node(&mut g, &[1, 2, 3, 8]);
4812            let recipe_root = rope::decompose(&mut g, fused, &FusedOpParams::Rope);
4813            let legacy_root = frozen_legacy_rope_decompose(&mut g, fused, &FusedOpParams::Rope);
4814            super::assert_structural_eq(&g, recipe_root, legacy_root);
4815            // Both leading-1 prep Reshapes are present (byte-identical, 11 ops).
4816            let reachable = crate::topo_order_multi(&g, &[recipe_root]);
4817            let reshapes = reachable
4818                .iter()
4819                .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
4820                .count();
4821            assert_eq!(
4822                reshapes, 2,
4823                "rank-raise materializes both legacy prep Reshapes (D4)"
4824            );
4825        }
4826
4827        /// T6 red (c): at EQUAL rank (x itself `[seq,d]`) the recipe emits the
4828        /// 9-node form — the resolver adds NO pad `Reshape` (D4 pads only on a
4829        /// rank-raise), eliding legacy's no-op `Reshape([seq,d]→[seq,d])`.
4830        /// Numerically identical (covered by the parity test), structurally
4831        /// leaner: 9 op nodes + 3 leaves, zero `Reshape`.
4832        #[test]
4833        fn rope_recipe_elides_the_noop_prep_reshape_at_equal_rank() {
4834            let mut g = Graph::new();
4835            let (_x, _cos, _sin, fused) = rope_fused_node(&mut g, &[2, 4]);
4836            let root = rope::decompose(&mut g, fused, &FusedOpParams::Rope);
4837            assert_ne!(root, fused, "recipe decompose fires");
4838            let reachable = crate::topo_order_multi(&g, &[root]);
4839            let reshapes = reachable
4840                .iter()
4841                .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
4842                .count();
4843            assert_eq!(
4844                reshapes, 0,
4845                "equal-rank broadcast needs no prep Reshape (D4)"
4846            );
4847            let op_nodes = reachable
4848                .iter()
4849                .filter(|&&n| !matches!(g.node(n).op, Op::Const))
4850                .count();
4851            assert_eq!(
4852                op_nodes, 9,
4853                "the 9-node rope recipe (2×Bcast/2×Slice/Neg/Concat/2×Mul/Add)"
4854            );
4855        }
4856
4857        /// T6 red (d): totality (G2) — a wrong params payload is a typed
4858        /// decline surfaced as a fixpoint, never a panic, and declines BEFORE
4859        /// any emission (no partial nodes). The legacy imperative body ignored
4860        /// `params` entirely and always decomposed; the recipe bridge gates on
4861        /// the projection, so a non-`Rope` payload now correctly no-ops.
4862        #[test]
4863        fn rope_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
4864            let mut g = Graph::new();
4865            let (_x, _cos, _sin, fused) = rope_fused_node(&mut g, &[2, 4]);
4866            let before = g.len();
4867            let out = rope::decompose(&mut g, fused, &FusedOpParams::SoftmaxLastDim);
4868            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
4869            assert_eq!(g.len(), before, "declined before any emission");
4870        }
4871    }
4872
4873    // rms_norm + layer_norm migration (Increment C slice 1, T7) --------------
4874    //
4875    // Both norms' imperative bodies become portable `PatternNode` DATA recipes.
4876    // Two forces at play beyond softmax/rope:
4877    //
4878    // * `eps` is an OPEN scalar slot. The recipe's `AddScalar` carries EMPTY
4879    //   `scalars`, so it is a slot template; the per-entry projection
4880    //   (`RmsNormLastDim { eps } → vec![eps]` / `LayerNormLastDim { eps } →
4881    //   vec![eps]`) supplies the live value, and the resolving emit fills the
4882    //   slot in pre-order. The eps-wiring tests below decompose the SAME op at
4883    //   two eps values and assert the realized outputs DIFFER accordingly — the
4884    //   proof that eps rides the projection→slot path, not a baked constant.
4885    //
4886    // * The keepdim restore is the RATIFIED D3 shrink-via-swap: `Reshape(keepdim)`
4887    //   → `Unsqueeze(axis_last = append)` (a node-TYPE change, metadata-only, so
4888    //   numerically bit-exact). `MeanDim(axis_last)` stays a rank-reducing mean.
4889    //   The parity tests evaluate the new recipe emission and the FROZEN legacy
4890    //   builder through one reference interpreter (which treats `Reshape` and
4891    //   `Unsqueeze` identically) and assert bit-exact equivalence at two ranks.
4892    //
4893    // Neither norm ever trips D4 (the keepdim restore rebuilds rank BEFORE the
4894    // broadcast, so every `BroadcastTo` operand already matches its target's
4895    // rank — no leading-1 pad `Reshape` is materialized).
4896    mod norm_recipe {
4897        use super::super::*;
4898        use super::{frozen_legacy_layer_norm_decompose, frozen_legacy_rms_norm_decompose};
4899        use crate::registry::{FusedOps, layer_norm_last_dim, rms_norm_last_dim};
4900        use fuel_ir::{DType, Shape};
4901        use std::collections::HashMap;
4902
4903        /// Tiny f64 reference interpreter over the norm primitive vocabulary
4904        /// (Const leaves, `Sqr`, last-axis `MeanDim`, metadata-only keepdim
4905        /// restores `Unsqueeze`/`Reshape`, `AddScalar`, `Sqrt`, last-dim
4906        /// `BroadcastTo`, elementwise `Sub`/`Div`). BOTH parity sides run
4907        /// through it with identical in-order arithmetic, so a bit-exact assert
4908        /// isolates recipe STRUCTURE (the `Unsqueeze`-vs-`Reshape` swap can't
4909        /// perturb it). Not code evaluation: a closed match over our own `Op`.
4910        fn eval_norm(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
4911            let node = g.node(id);
4912            match &node.op {
4913                Op::Const => leaves.get(&id).expect("leaf data provided").clone(),
4914                Op::Sqr => eval_norm(g, node.inputs[0], leaves)
4915                    .iter()
4916                    .map(|v| v * v)
4917                    .collect(),
4918                Op::Sqrt => eval_norm(g, node.inputs[0], leaves)
4919                    .iter()
4920                    .map(|v| v.sqrt())
4921                    .collect(),
4922                Op::AddScalar(e) => eval_norm(g, node.inputs[0], leaves)
4923                    .iter()
4924                    .map(|v| v + e)
4925                    .collect(),
4926                Op::Sub => {
4927                    let a = eval_norm(g, node.inputs[0], leaves);
4928                    let b = eval_norm(g, node.inputs[1], leaves);
4929                    a.iter().zip(&b).map(|(x, y)| x - y).collect()
4930                }
4931                Op::Div => {
4932                    let a = eval_norm(g, node.inputs[0], leaves);
4933                    let b = eval_norm(g, node.inputs[1], leaves);
4934                    a.iter().zip(&b).map(|(x, y)| x / y).collect()
4935                }
4936                // Last-axis mean — rank-reducing; identical fold both spellings.
4937                Op::MeanDim(_) => {
4938                    let input = eval_norm(g, node.inputs[0], leaves);
4939                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
4940                    input
4941                        .chunks(last)
4942                        .map(|row| row.iter().sum::<f64>() / last as f64)
4943                        .collect()
4944                }
4945                // Metadata-only keepdim restores (the D3 swap and its legacy
4946                // twin evaluate identically here).
4947                Op::Unsqueeze { .. } | Op::Reshape(_) => eval_norm(g, node.inputs[0], leaves),
4948                // Broadcast a keepdim `[.., 1]` tensor back along the last axis.
4949                Op::BroadcastTo(target) => {
4950                    let input = eval_norm(g, node.inputs[0], leaves);
4951                    let out_n: usize = target.dims().iter().product();
4952                    let last = *target.dims().last().unwrap();
4953                    assert_eq!(
4954                        input.len() * last,
4955                        out_n,
4956                        "broadcast is a last-dim repeat in these graphs",
4957                    );
4958                    input
4959                        .iter()
4960                        .flat_map(|&v| std::iter::repeat_n(v, last))
4961                        .collect()
4962                }
4963                other => panic!("eval_norm: unhandled op {other:?}"),
4964            }
4965        }
4966
4967        /// Build a fused RmsNormLastDim node over `x [dims]`, carrying `eps`.
4968        /// Returns `(x, fused)`.
4969        fn rms_norm_fused_node(g: &mut Graph, dims: &[usize], eps: f64) -> (NodeId, NodeId) {
4970            let sh = Shape::from_dims(dims);
4971            let x = g.push(Node {
4972                op: Op::Const,
4973                inputs: vec![],
4974                shape: sh.clone(),
4975                dtype: DType::F32,
4976            });
4977            let fused = g.push(Node {
4978                op: Op::Fused(
4979                    FusedOps::RMS_NORM_LAST_DIM,
4980                    FusedOpParams::RmsNormLastDim { eps },
4981                ),
4982                inputs: vec![x],
4983                shape: sh,
4984                dtype: DType::F32,
4985            });
4986            (x, fused)
4987        }
4988
4989        /// Build a fused LayerNormLastDim node over `x [dims]`, carrying `eps`.
4990        /// Returns `(x, fused)`.
4991        fn layer_norm_fused_node(g: &mut Graph, dims: &[usize], eps: f64) -> (NodeId, NodeId) {
4992            let sh = Shape::from_dims(dims);
4993            let x = g.push(Node {
4994                op: Op::Const,
4995                inputs: vec![],
4996                shape: sh.clone(),
4997                dtype: DType::F32,
4998            });
4999            let fused = g.push(Node {
5000                op: Op::Fused(
5001                    FusedOps::LAYER_NORM_LAST_DIM,
5002                    FusedOpParams::LayerNormLastDim { eps },
5003                ),
5004                inputs: vec![x],
5005                shape: sh,
5006                dtype: DType::F32,
5007            });
5008            (x, fused)
5009        }
5010
5011        /// T7 red (a, rms): ONE recipe datum decomposes at BOTH rank 2 and rank
5012        /// 3 (the polymorphism the baked-shape legacy body never had), and its
5013        /// numerics match the FROZEN legacy builder bit-exactly.
5014        #[test]
5015        fn rms_norm_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
5016            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
5017                let mut g = Graph::new();
5018                let (x, fused) = rms_norm_fused_node(&mut g, &dims, 1e-5);
5019                let sh = Shape::from_dims(&dims);
5020                let new_root = rms_norm_last_dim::decompose(
5021                    &mut g,
5022                    fused,
5023                    &FusedOpParams::RmsNormLastDim { eps: 1e-5 },
5024                );
5025                assert_ne!(new_root, fused, "recipe decompose must fire at {dims:?}");
5026                assert_eq!(g.node(new_root).shape, sh, "rms_norm is shape-preserving");
5027                assert_eq!(g.node(new_root).dtype, DType::F32);
5028
5029                let legacy_root = frozen_legacy_rms_norm_decompose(
5030                    &mut g,
5031                    fused,
5032                    &FusedOpParams::RmsNormLastDim { eps: 1e-5 },
5033                );
5034
5035                let n: usize = dims.iter().product();
5036                let data: Vec<f64> = (0..n)
5037                    .map(|i| ((i as f64) * 0.37).sin() * 3.0 - 0.5)
5038                    .collect();
5039                let mut leaves = HashMap::new();
5040                leaves.insert(x, data);
5041                let got = eval_norm(&g, new_root, &leaves);
5042                let want = eval_norm(&g, legacy_root, &leaves);
5043                assert_eq!(got.len(), want.len());
5044                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
5045                    assert_eq!(
5046                        a.to_bits(),
5047                        b.to_bits(),
5048                        "rms_norm[{i}] at {dims:?}: recipe={a} vs legacy={b}",
5049                    );
5050                }
5051            }
5052        }
5053
5054        /// T7 red (a, layer): same polymorphism + bit-exact parity for the
5055        /// 11-node layer-norm recipe (with the `centered` subterm identity-
5056        /// shared between `Sqr` and the final `Div`).
5057        #[test]
5058        fn layer_norm_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
5059            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
5060                let mut g = Graph::new();
5061                let (x, fused) = layer_norm_fused_node(&mut g, &dims, 1e-5);
5062                let sh = Shape::from_dims(&dims);
5063                let new_root = layer_norm_last_dim::decompose(
5064                    &mut g,
5065                    fused,
5066                    &FusedOpParams::LayerNormLastDim { eps: 1e-5 },
5067                );
5068                assert_ne!(new_root, fused, "recipe decompose must fire at {dims:?}");
5069                assert_eq!(g.node(new_root).shape, sh, "layer_norm is shape-preserving");
5070                assert_eq!(g.node(new_root).dtype, DType::F32);
5071
5072                let legacy_root = frozen_legacy_layer_norm_decompose(
5073                    &mut g,
5074                    fused,
5075                    &FusedOpParams::LayerNormLastDim { eps: 1e-5 },
5076                );
5077
5078                let n: usize = dims.iter().product();
5079                let data: Vec<f64> = (0..n)
5080                    .map(|i| ((i as f64) * 0.29).cos() * 2.0 + 0.3)
5081                    .collect();
5082                let mut leaves = HashMap::new();
5083                leaves.insert(x, data);
5084                let got = eval_norm(&g, new_root, &leaves);
5085                let want = eval_norm(&g, legacy_root, &leaves);
5086                assert_eq!(got.len(), want.len());
5087                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
5088                    assert_eq!(
5089                        a.to_bits(),
5090                        b.to_bits(),
5091                        "layer_norm[{i}] at {dims:?}: recipe={a} vs legacy={b}",
5092                    );
5093                }
5094            }
5095        }
5096
5097        /// T7 red (structural, rms): the keepdim restore is the D3 shrink-via-
5098        /// swap — `Unsqueeze` append, NOT a baked `Reshape(keepdim)`. This is
5099        /// the crisp discriminator against the pre-migration imperative body.
5100        #[test]
5101        fn rms_norm_recipe_uses_the_unsqueeze_keepdim_swap() {
5102            let mut g = Graph::new();
5103            let (_x, fused) = rms_norm_fused_node(&mut g, &[2, 4], 1e-5);
5104            let root = rms_norm_last_dim::decompose(
5105                &mut g,
5106                fused,
5107                &FusedOpParams::RmsNormLastDim { eps: 1e-5 },
5108            );
5109            assert_ne!(root, fused, "recipe decompose fires");
5110            let reachable = crate::topo_order_multi(&g, &[root]);
5111            let unsqueezes = reachable
5112                .iter()
5113                .filter(|&&n| matches!(g.node(n).op, Op::Unsqueeze { .. }))
5114                .count();
5115            let reshapes = reachable
5116                .iter()
5117                .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
5118                .count();
5119            assert_eq!(
5120                unsqueezes, 1,
5121                "keepdim restored via Unsqueeze append (D3 swap)"
5122            );
5123            assert_eq!(reshapes, 0, "no baked keepdim Reshape after the D3 swap");
5124        }
5125
5126        /// T7 red (structural, layer): both keepdim restores are `Unsqueeze`
5127        /// appends; zero `Reshape` (the equal-rank broadcasts add no D4 pad).
5128        #[test]
5129        fn layer_norm_recipe_uses_the_unsqueeze_keepdim_swap() {
5130            let mut g = Graph::new();
5131            let (_x, fused) = layer_norm_fused_node(&mut g, &[2, 4], 1e-5);
5132            let root = layer_norm_last_dim::decompose(
5133                &mut g,
5134                fused,
5135                &FusedOpParams::LayerNormLastDim { eps: 1e-5 },
5136            );
5137            assert_ne!(root, fused, "recipe decompose fires");
5138            let reachable = crate::topo_order_multi(&g, &[root]);
5139            let unsqueezes = reachable
5140                .iter()
5141                .filter(|&&n| matches!(g.node(n).op, Op::Unsqueeze { .. }))
5142                .count();
5143            let reshapes = reachable
5144                .iter()
5145                .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
5146                .count();
5147            assert_eq!(
5148                unsqueezes, 2,
5149                "both keepdim restores via Unsqueeze append (D3 swap)"
5150            );
5151            assert_eq!(
5152                reshapes, 0,
5153                "no baked keepdim Reshape / no D4 pad after the swap"
5154            );
5155            // The `centered` Sub is SHARED (Sqr input == final Div numerator):
5156            // 11 op nodes + 1 leaf = 12 reachable, not the 12-op unshared tree.
5157            let op_nodes = reachable
5158                .iter()
5159                .filter(|&&n| !matches!(g.node(n).op, Op::Const))
5160                .count();
5161            assert_eq!(op_nodes, 11, "11 op nodes with `centered` identity-shared");
5162        }
5163
5164        /// T7 red (eps-wiring, rms): the eps rides the projection→open-slot
5165        /// path. Decomposing the SAME op at two eps values yields DIFFERENT
5166        /// realized outputs — impossible if eps were dropped or baked to a
5167        /// single constant. Small `x` (so `mean(x²) ≈ eps`) makes the eps
5168        /// choice materially move every element.
5169        #[test]
5170        fn rms_norm_recipe_eps_flows_through_the_open_slot() {
5171            let dims = [2usize, 4];
5172            let n: usize = dims.iter().product();
5173            let data: Vec<f64> = (0..n)
5174                .map(|i| 0.001 * (((i as f64) * 0.37).sin() + 1.2))
5175                .collect();
5176
5177            let realize = |eps: f64| -> Vec<f64> {
5178                let mut g = Graph::new();
5179                let (x, fused) = rms_norm_fused_node(&mut g, &dims, eps);
5180                let root = rms_norm_last_dim::decompose(
5181                    &mut g,
5182                    fused,
5183                    &FusedOpParams::RmsNormLastDim { eps },
5184                );
5185                let mut leaves = HashMap::new();
5186                leaves.insert(x, data.clone());
5187                eval_norm(&g, root, &leaves)
5188            };
5189            let a = realize(1e-5);
5190            let b = realize(1e-6);
5191            assert_eq!(a.len(), b.len());
5192            assert_ne!(
5193                a, b,
5194                "different eps must change the output — proves projection→slot, not a baked constant",
5195            );
5196        }
5197
5198        /// T7 red (eps-wiring, layer): same proof for layer-norm's open slot.
5199        #[test]
5200        fn layer_norm_recipe_eps_flows_through_the_open_slot() {
5201            let dims = [2usize, 4];
5202            let n: usize = dims.iter().product();
5203            // Near-constant rows so the variance ≈ eps and the eps choice moves
5204            // the output materially.
5205            let data: Vec<f64> = (0..n)
5206                .map(|i| 1.0 + 0.001 * ((i as f64) * 0.37).sin())
5207                .collect();
5208
5209            let realize = |eps: f64| -> Vec<f64> {
5210                let mut g = Graph::new();
5211                let (x, fused) = layer_norm_fused_node(&mut g, &dims, eps);
5212                let root = layer_norm_last_dim::decompose(
5213                    &mut g,
5214                    fused,
5215                    &FusedOpParams::LayerNormLastDim { eps },
5216                );
5217                let mut leaves = HashMap::new();
5218                leaves.insert(x, data.clone());
5219                eval_norm(&g, root, &leaves)
5220            };
5221            let a = realize(1e-5);
5222            let b = realize(1e-6);
5223            assert_eq!(a.len(), b.len());
5224            assert_ne!(
5225                a, b,
5226                "different eps must change the output — proves projection→slot, not a baked constant",
5227            );
5228        }
5229
5230        /// T7 red (totality, rms): a wrong params payload is a typed decline
5231        /// surfaced as a fixpoint (G2), never a panic, declining BEFORE any
5232        /// emission (no partial nodes).
5233        #[test]
5234        fn rms_norm_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
5235            let mut g = Graph::new();
5236            let (_x, fused) = rms_norm_fused_node(&mut g, &[2, 4], 1e-5);
5237            let before = g.len();
5238            let out = rms_norm_last_dim::decompose(&mut g, fused, &FusedOpParams::SoftmaxLastDim);
5239            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
5240            assert_eq!(g.len(), before, "declined before any emission");
5241        }
5242
5243        /// T7 red (totality, layer): same fixpoint posture for layer-norm.
5244        #[test]
5245        fn layer_norm_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
5246            let mut g = Graph::new();
5247            let (_x, fused) = layer_norm_fused_node(&mut g, &[2, 4], 1e-5);
5248            let before = g.len();
5249            let out = layer_norm_last_dim::decompose(&mut g, fused, &FusedOpParams::SoftmaxLastDim);
5250            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
5251            assert_eq!(g.len(), before, "declined before any emission");
5252        }
5253    }
5254
5255    // softmax_last_dim_backward migration (Increment C slice 1, T8) ----------
5256    //
5257    // The 5-node imperative backward body `s · (g − sum(g·s, last, keepdim))`
5258    // becomes a portable `PatternNode` DATA recipe. Bind space: `0 = s` (the
5259    // forward softmax output), `1 = g` (the upstream gradient) — the order the
5260    // autograd `BackwardKind::Fused(SOFTMAX_LAST_DIM_BACKWARD)` edge emits. The
5261    // keepdim restore is the ratified D3 shrink-via-swap
5262    // (`ReduceSumTo(keepdim)` → `SumDim(axis_last)` + `Unsqueeze(axis_last =
5263    // append)`, node-TYPE change, numerically bit-exact) and the broadcast
5264    // targets `SameAs { operand: 0 }` over the Bind space (D2). D4 never fires
5265    // (the `Unsqueeze` rebuilds rank BEFORE the broadcast, so the broadcast
5266    // operand already matches its target's rank — no leading-1 pad `Reshape`).
5267    // This activates the registry's backward-helper edge END-TO-END on a data
5268    // recipe for the first time.
5269    mod softmax_backward_recipe {
5270        use super::super::*;
5271        use super::frozen_legacy_softmax_backward_decompose;
5272        use crate::registry::{FusedOps, softmax_last_dim_backward};
5273        use fuel_ir::{DType, Shape};
5274        use std::collections::HashMap;
5275
5276        /// Tiny f64 reference interpreter over the softmax-backward primitive
5277        /// vocabulary (leaf-lookup FIRST, then `Mul`/`Sub`, last-axis
5278        /// `SumDim`/`ReduceSumTo`, metadata-only keepdim restore
5279        /// `Unsqueeze`/`Reshape`, last-dim `BroadcastTo`). Leaf-first lets ANY
5280        /// node stand in as a bound input — a `Const`, or the autograd path's
5281        /// forward-softmax (`Op::Fused`) and upstream nodes. BOTH parity sides
5282        /// run through it with identical in-order arithmetic, so a bit-exact
5283        /// assert isolates recipe STRUCTURE (the `SumDim`+`Unsqueeze`-vs-
5284        /// `ReduceSumTo` swap can't perturb it). Not code evaluation: a closed
5285        /// match over our own `Op`.
5286        fn eval_bwd(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
5287            if let Some(v) = leaves.get(&id) {
5288                return v.clone();
5289            }
5290            let node = g.node(id);
5291            match &node.op {
5292                Op::Mul => {
5293                    let a = eval_bwd(g, node.inputs[0], leaves);
5294                    let b = eval_bwd(g, node.inputs[1], leaves);
5295                    a.iter().zip(&b).map(|(x, y)| x * y).collect()
5296                }
5297                Op::Sub => {
5298                    let a = eval_bwd(g, node.inputs[0], leaves);
5299                    let b = eval_bwd(g, node.inputs[1], leaves);
5300                    a.iter().zip(&b).map(|(x, y)| x - y).collect()
5301                }
5302                // Last-axis sum — one arm per spelling pair, identical fold.
5303                Op::SumDim(_) | Op::ReduceSumTo(_) => {
5304                    let input = eval_bwd(g, node.inputs[0], leaves);
5305                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
5306                    input.chunks(last).map(|row| row.iter().sum()).collect()
5307                }
5308                // Metadata-only keepdim restores (the D3 swap and its legacy
5309                // twin evaluate identically here).
5310                Op::Unsqueeze { .. } | Op::Reshape(_) => eval_bwd(g, node.inputs[0], leaves),
5311                // Broadcast a keepdim/reduced tensor back along the last axis.
5312                Op::BroadcastTo(target) => {
5313                    let input = eval_bwd(g, node.inputs[0], leaves);
5314                    let out_n: usize = target.dims().iter().product();
5315                    let last = *target.dims().last().unwrap();
5316                    assert_eq!(
5317                        input.len() * last,
5318                        out_n,
5319                        "broadcast is a last-dim repeat in these graphs",
5320                    );
5321                    input
5322                        .iter()
5323                        .flat_map(|&v| std::iter::repeat_n(v, last))
5324                        .collect()
5325                }
5326                other => panic!("eval_bwd: unhandled op {other:?}"),
5327            }
5328        }
5329
5330        /// Build a fused SoftmaxLastDimBackward node over `s [dims]` (input 0,
5331        /// the forward output) and `g [dims]` (input 1, the upstream gradient).
5332        /// Returns `(s, g, fused)`.
5333        fn softmax_backward_fused_node(g: &mut Graph, dims: &[usize]) -> (NodeId, NodeId, NodeId) {
5334            let sh = Shape::from_dims(dims);
5335            let s = g.push(Node {
5336                op: Op::Const,
5337                inputs: vec![],
5338                shape: sh.clone(),
5339                dtype: DType::F32,
5340            });
5341            let up = g.push(Node {
5342                op: Op::Const,
5343                inputs: vec![],
5344                shape: sh.clone(),
5345                dtype: DType::F32,
5346            });
5347            let fused = g.push(Node {
5348                op: Op::Fused(
5349                    FusedOps::SOFTMAX_LAST_DIM_BACKWARD,
5350                    FusedOpParams::SoftmaxLastDimBackward,
5351                ),
5352                inputs: vec![s, up],
5353                shape: sh,
5354                dtype: DType::F32,
5355            });
5356            (s, up, fused)
5357        }
5358
5359        /// T8 red (a): ONE recipe datum decomposes at BOTH rank 2 and rank 3
5360        /// (the polymorphism the baked-shape legacy body never had), and its
5361        /// numerics match the FROZEN legacy builder bit-exactly under the
5362        /// shared reference interpreter.
5363        #[test]
5364        fn softmax_backward_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
5365            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
5366                let mut g = Graph::new();
5367                let (s, up, fused) = softmax_backward_fused_node(&mut g, &dims);
5368                let sh = Shape::from_dims(&dims);
5369                let new_root = softmax_last_dim_backward::decompose(
5370                    &mut g,
5371                    fused,
5372                    &FusedOpParams::SoftmaxLastDimBackward,
5373                );
5374                assert_ne!(new_root, fused, "recipe decompose must fire at {dims:?}");
5375                assert_eq!(
5376                    g.node(new_root).shape,
5377                    sh,
5378                    "softmax backward is shape-preserving"
5379                );
5380                assert_eq!(g.node(new_root).dtype, DType::F32);
5381
5382                let legacy_root = frozen_legacy_softmax_backward_decompose(
5383                    &mut g,
5384                    fused,
5385                    &FusedOpParams::SoftmaxLastDimBackward,
5386                );
5387
5388                let n: usize = dims.iter().product();
5389                let s_data: Vec<f64> = (0..n)
5390                    .map(|i| ((i as f64) * 0.29).sin() * 0.5 + 0.5)
5391                    .collect();
5392                let g_data: Vec<f64> = (0..n)
5393                    .map(|i| ((i as f64) * 0.53).cos() * 2.0 - 0.3)
5394                    .collect();
5395                let mut leaves = HashMap::new();
5396                leaves.insert(s, s_data);
5397                leaves.insert(up, g_data);
5398                let got = eval_bwd(&g, new_root, &leaves);
5399                let want = eval_bwd(&g, legacy_root, &leaves);
5400                assert_eq!(got.len(), want.len());
5401                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
5402                    assert_eq!(
5403                        a.to_bits(),
5404                        b.to_bits(),
5405                        "softmax_backward[{i}] at {dims:?}: recipe={a} vs legacy={b}",
5406                    );
5407                }
5408            }
5409        }
5410
5411        /// T8 red (structural): the keepdim restore is the D3 shrink-via-swap —
5412        /// `SumDim(last)` + `Unsqueeze` append, NOT the baked
5413        /// `ReduceSumTo(keepdim)`. The crisp discriminator against the
5414        /// pre-migration imperative body; the backward root is the outer `Mul`.
5415        #[test]
5416        fn softmax_backward_recipe_uses_the_sumdim_unsqueeze_swap() {
5417            let mut g = Graph::new();
5418            let (_s, _up, fused) = softmax_backward_fused_node(&mut g, &[2, 4]);
5419            let root = softmax_last_dim_backward::decompose(
5420                &mut g,
5421                fused,
5422                &FusedOpParams::SoftmaxLastDimBackward,
5423            );
5424            assert_ne!(root, fused, "recipe decompose fires");
5425            assert!(
5426                matches!(g.node(root).op, Op::Mul),
5427                "backward root is the outer Mul"
5428            );
5429            let reachable = crate::topo_order_multi(&g, &[root]);
5430            let sumdims = reachable
5431                .iter()
5432                .filter(|&&n| matches!(g.node(n).op, Op::SumDim(_)))
5433                .count();
5434            let unsqueezes = reachable
5435                .iter()
5436                .filter(|&&n| matches!(g.node(n).op, Op::Unsqueeze { .. }))
5437                .count();
5438            let reduce_sum_tos = reachable
5439                .iter()
5440                .filter(|&&n| matches!(g.node(n).op, Op::ReduceSumTo(_)))
5441                .count();
5442            assert_eq!(sumdims, 1, "the reduce is SumDim(last) — the D3 swap");
5443            assert_eq!(unsqueezes, 1, "keepdim restored via Unsqueeze append");
5444            assert_eq!(
5445                reduce_sum_tos, 0,
5446                "no baked keepdim ReduceSumTo after the swap"
5447            );
5448        }
5449
5450        /// T8 red (totality): a wrong params payload is a typed decline
5451        /// surfaced as a fixpoint (G2), never a panic, declining BEFORE any
5452        /// emission. (The pre-migration imperative body IGNORED params and
5453        /// always decomposed; the recipe bridge's `scalars(params)` projection
5454        /// is what makes a wrong payload a fixpoint.)
5455        #[test]
5456        fn softmax_backward_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
5457            let mut g = Graph::new();
5458            let (_s, _up, fused) = softmax_backward_fused_node(&mut g, &[2, 4]);
5459            let before = g.len();
5460            let out = softmax_last_dim_backward::decompose(&mut g, fused, &FusedOpParams::Rope);
5461            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
5462            assert_eq!(g.len(), before, "declined before any emission");
5463        }
5464
5465        /// T8 red (autograd path): the `BackwardKind::Fused(SOFTMAX_LAST_DIM_BACKWARD)`
5466        /// edge exercised END-TO-END. Build a softmax forward, backprop; the
5467        /// input gradient node is `Op::Fused(SOFTMAX_LAST_DIM_BACKWARD)` over
5468        /// `[y, upstream]`; decomposing it fires the MIGRATED recipe (the D3
5469        /// SumDim spelling) and matches the frozen legacy numerically — the
5470        /// "realize" leg, via the reference interpreter feeding synthetic leaf
5471        /// data on the two bound inputs (`y = s`, `upstream = g`).
5472        #[test]
5473        fn softmax_backward_reaches_the_recipe_through_autograd() {
5474            let dev: std::sync::Arc<dyn fuel_backend_contract::DynBackendDevice> =
5475                std::sync::Arc::new(fuel_cpu_backend::dyn_impl::CpuBackendDevice);
5476            let x = crate::NodeHandle::from_f32(
5477                vec![0.1f32, -0.2, 0.3, 0.4, -0.5, 0.6],
5478                Shape::from_dims(&[2, 3]),
5479                &dev,
5480            )
5481            .unwrap();
5482            let y = x.softmax_last_dim();
5483            let y_id = y.id();
5484            let grads = y.backward();
5485            let g_x = grads.get(&x).expect("softmax has an input gradient");
5486            let handle = g_x.graph();
5487            let bwd_id = g_x.id();
5488
5489            // The input-gradient node IS the registry backward fused op over
5490            // `[y, upstream]` (x feeds only the softmax, so no accumulation Add).
5491            let up_id = {
5492                let gr = handle.read().unwrap();
5493                let node = gr.node(bwd_id).clone();
5494                match node.op {
5495                    Op::Fused(fid, params) => {
5496                        assert_eq!(
5497                            fid,
5498                            FusedOps::SOFTMAX_LAST_DIM_BACKWARD,
5499                            "autograd emits the registry backward fused op",
5500                        );
5501                        assert!(matches!(params, FusedOpParams::SoftmaxLastDimBackward));
5502                    }
5503                    other => panic!("expected the backward fused op, got {other:?}"),
5504                }
5505                assert_eq!(
5506                    node.inputs[0], y_id,
5507                    "backward input 0 = the forward softmax output"
5508                );
5509                node.inputs[1]
5510            };
5511
5512            // Decompose the SAME autograd backward node both ways (push-only
5513            // graph — the fused node survives), then compare numerically.
5514            let (new_root, legacy_root, sh) = {
5515                let mut gr = handle.write().unwrap();
5516                let sh = gr.node(bwd_id).shape.clone();
5517                let new_root = softmax_last_dim_backward::decompose(
5518                    &mut gr,
5519                    bwd_id,
5520                    &FusedOpParams::SoftmaxLastDimBackward,
5521                );
5522                let legacy_root = frozen_legacy_softmax_backward_decompose(
5523                    &mut gr,
5524                    bwd_id,
5525                    &FusedOpParams::SoftmaxLastDimBackward,
5526                );
5527                (new_root, legacy_root, sh)
5528            };
5529
5530            let gr = handle.read().unwrap();
5531            assert_ne!(
5532                new_root, bwd_id,
5533                "the autograd backward node decomposes via the recipe"
5534            );
5535            assert_eq!(gr.node(new_root).shape, sh, "shape-preserving");
5536            let reachable = crate::topo_order_multi(&gr, &[new_root]);
5537            assert!(
5538                reachable
5539                    .iter()
5540                    .any(|&n| matches!(gr.node(n).op, Op::SumDim(_))),
5541                "the autograd path reaches the D3 SumDim spelling",
5542            );
5543
5544            // Numeric parity (leaf-first interpreter over `[y = s, up = g]`).
5545            let n: usize = sh.dims().iter().product();
5546            let s_data: Vec<f64> = (0..n)
5547                .map(|i| ((i as f64) * 0.31).sin() * 0.5 + 0.5)
5548                .collect();
5549            let g_data: Vec<f64> = (0..n).map(|i| ((i as f64) * 0.47).cos() - 0.1).collect();
5550            let mut leaves = HashMap::new();
5551            leaves.insert(y_id, s_data);
5552            leaves.insert(up_id, g_data);
5553            let got = eval_bwd(&gr, new_root, &leaves);
5554            let want = eval_bwd(&gr, legacy_root, &leaves);
5555            assert_eq!(got.len(), want.len());
5556            for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
5557                assert_eq!(
5558                    a.to_bits(),
5559                    b.to_bits(),
5560                    "autograd softmax_backward[{i}]: recipe={a} vs legacy={b}",
5561                );
5562            }
5563        }
5564    }
5565
5566    // layer_norm_last_dim_backward migration (Increment C slice 2, S2-1) ------
5567    //
5568    // The ~20-node imperative backward body
5569    // `grad_x = istd · (g − mean(g) − xhat·mean(g·xhat))` (mean over the last
5570    // dim, `xhat = (x − mean(x))·istd`, `istd = rsqrt(var + eps)`) becomes a
5571    // portable `PatternNode` DATA recipe. Bind space: `0 = x`, `1 = g` (the
5572    // upstream gradient) — the order the autograd
5573    // `BackwardKind::Fused(LAYER_NORM_LAST_DIM_BACKWARD)` edge emits. The four
5574    // keepdim restores are the ratified D3 shrink-via-swap (`Reshape(keepdim)`
5575    // → `MeanDim(axis_last)` shrink + `Unsqueeze(axis_last = append)`,
5576    // node-TYPE change, numerically bit-exact); each broadcast targets
5577    // `SameAs { operand: 0 }` (x's full shape) over the Bind space (D2). One
5578    // `eps` open scalar slot. D4 never fires at equal rank (the `Unsqueeze`
5579    // rebuilds rank before every broadcast). RISK-A: `xhat`/`istd` carry the
5580    // eps slot, so emit's slot-free identity-share does NOT dedup them — see
5581    // `..._open_slot_xhat_is_recomputed_...` below.
5582    mod layer_norm_backward_recipe {
5583        use super::super::*;
5584        use super::frozen_legacy_layer_norm_backward_decompose;
5585        use crate::registry::{FusedOps, layer_norm_last_dim_backward};
5586        use fuel_ir::{DType, Shape};
5587        use std::collections::HashMap;
5588
5589        /// Tiny f64 reference interpreter over the layer-norm-backward
5590        /// primitive vocabulary (leaf-lookup FIRST, then elementwise
5591        /// `Mul`/`Sub`, unary `Sqr`/`Rsqrt`, `AddScalar`, last-axis `MeanDim`,
5592        /// metadata-only keepdim restore `Unsqueeze`/`Reshape`, last-dim
5593        /// `BroadcastTo`). BOTH parity sides run through it with identical
5594        /// in-order arithmetic, so a bit-exact assert isolates recipe STRUCTURE
5595        /// — neither the `Reshape`→`Unsqueeze` D3 swap NOR the open-slot
5596        /// `xhat`/`istd` recompute can perturb it (recompute is deterministic).
5597        fn eval_lnb(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
5598            if let Some(v) = leaves.get(&id) {
5599                return v.clone();
5600            }
5601            let node = g.node(id);
5602            match &node.op {
5603                Op::Mul => {
5604                    let a = eval_lnb(g, node.inputs[0], leaves);
5605                    let b = eval_lnb(g, node.inputs[1], leaves);
5606                    a.iter().zip(&b).map(|(x, y)| x * y).collect()
5607                }
5608                Op::Sub => {
5609                    let a = eval_lnb(g, node.inputs[0], leaves);
5610                    let b = eval_lnb(g, node.inputs[1], leaves);
5611                    a.iter().zip(&b).map(|(x, y)| x - y).collect()
5612                }
5613                Op::Sqr => eval_lnb(g, node.inputs[0], leaves)
5614                    .iter()
5615                    .map(|v| v * v)
5616                    .collect(),
5617                Op::Rsqrt => eval_lnb(g, node.inputs[0], leaves)
5618                    .iter()
5619                    .map(|v| 1.0 / v.sqrt())
5620                    .collect(),
5621                Op::AddScalar(e) => eval_lnb(g, node.inputs[0], leaves)
5622                    .iter()
5623                    .map(|v| v + e)
5624                    .collect(),
5625                // Last-axis mean — rank-reducing; identical fold both spellings.
5626                Op::MeanDim(_) => {
5627                    let input = eval_lnb(g, node.inputs[0], leaves);
5628                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
5629                    input
5630                        .chunks(last)
5631                        .map(|row| row.iter().sum::<f64>() / last as f64)
5632                        .collect()
5633                }
5634                // Metadata-only keepdim restores (the D3 swap and its legacy
5635                // twin evaluate identically here).
5636                Op::Unsqueeze { .. } | Op::Reshape(_) => eval_lnb(g, node.inputs[0], leaves),
5637                // Broadcast a keepdim `[.., 1]` tensor back along the last axis.
5638                Op::BroadcastTo(target) => {
5639                    let input = eval_lnb(g, node.inputs[0], leaves);
5640                    let out_n: usize = target.dims().iter().product();
5641                    let last = *target.dims().last().unwrap();
5642                    assert_eq!(
5643                        input.len() * last,
5644                        out_n,
5645                        "broadcast is a last-dim repeat in these graphs",
5646                    );
5647                    input
5648                        .iter()
5649                        .flat_map(|&v| std::iter::repeat_n(v, last))
5650                        .collect()
5651                }
5652                other => panic!("eval_lnb: unhandled op {other:?}"),
5653            }
5654        }
5655
5656        /// Build a fused LayerNormLastDimBackward node over `x [dims]` (input 0)
5657        /// and `g [dims]` (input 1, the upstream gradient), carrying `eps`.
5658        /// Returns `(x, g, fused)`.
5659        fn ln_backward_fused_node(
5660            g: &mut Graph,
5661            dims: &[usize],
5662            eps: f64,
5663        ) -> (NodeId, NodeId, NodeId) {
5664            let sh = Shape::from_dims(dims);
5665            let x = g.push(Node {
5666                op: Op::Const,
5667                inputs: vec![],
5668                shape: sh.clone(),
5669                dtype: DType::F32,
5670            });
5671            let up = g.push(Node {
5672                op: Op::Const,
5673                inputs: vec![],
5674                shape: sh.clone(),
5675                dtype: DType::F32,
5676            });
5677            let fused = g.push(Node {
5678                op: Op::Fused(
5679                    FusedOps::LAYER_NORM_LAST_DIM_BACKWARD,
5680                    FusedOpParams::LayerNormLastDimBackward { eps },
5681                ),
5682                inputs: vec![x, up],
5683                shape: sh,
5684                dtype: DType::F32,
5685            });
5686            (x, up, fused)
5687        }
5688
5689        /// S2-1 (a): ONE recipe datum decomposes at BOTH rank 2 and rank 3 (the
5690        /// polymorphism the baked-shape legacy body never had), and its
5691        /// numerics match the FROZEN legacy builder bit-exactly under the
5692        /// shared reference interpreter.
5693        #[test]
5694        fn layer_norm_backward_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
5695            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
5696                let mut g = Graph::new();
5697                let (x, up, fused) = ln_backward_fused_node(&mut g, &dims, 1e-5);
5698                let sh = Shape::from_dims(&dims);
5699                let new_root = layer_norm_last_dim_backward::decompose(
5700                    &mut g,
5701                    fused,
5702                    &FusedOpParams::LayerNormLastDimBackward { eps: 1e-5 },
5703                );
5704                assert_ne!(new_root, fused, "recipe decompose must fire at {dims:?}");
5705                assert_eq!(
5706                    g.node(new_root).shape,
5707                    sh,
5708                    "layer_norm backward is shape-preserving"
5709                );
5710                assert_eq!(g.node(new_root).dtype, DType::F32);
5711
5712                let legacy_root = frozen_legacy_layer_norm_backward_decompose(
5713                    &mut g,
5714                    fused,
5715                    &FusedOpParams::LayerNormLastDimBackward { eps: 1e-5 },
5716                );
5717
5718                let n: usize = dims.iter().product();
5719                let x_data: Vec<f64> = (0..n)
5720                    .map(|i| ((i as f64) * 0.29).sin() * 2.0 + 0.3)
5721                    .collect();
5722                let g_data: Vec<f64> = (0..n)
5723                    .map(|i| ((i as f64) * 0.53).cos() * 1.5 - 0.2)
5724                    .collect();
5725                let mut leaves = HashMap::new();
5726                leaves.insert(x, x_data);
5727                leaves.insert(up, g_data);
5728                let got = eval_lnb(&g, new_root, &leaves);
5729                let want = eval_lnb(&g, legacy_root, &leaves);
5730                assert_eq!(got.len(), want.len());
5731                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
5732                    assert_eq!(
5733                        a.to_bits(),
5734                        b.to_bits(),
5735                        "layer_norm_backward[{i}] at {dims:?}: recipe={a} vs legacy={b}",
5736                    );
5737                }
5738            }
5739        }
5740
5741        /// S2-1 (structural, born-red): every keepdim restore is the D3
5742        /// shrink-via-swap `Unsqueeze` append, NOT the baked `Reshape(keepdim)`
5743        /// — the crisp discriminator against the pre-migration imperative body
5744        /// (which emits `Reshape`, so this test is RED until migration). Zero
5745        /// `Reshape` also confirms no D4 pad fires (equal rank throughout). The
5746        /// backward root is the outer `Mul(istd, inner)`.
5747        #[test]
5748        fn layer_norm_backward_recipe_uses_the_unsqueeze_keepdim_swap() {
5749            let mut g = Graph::new();
5750            let (_x, _up, fused) = ln_backward_fused_node(&mut g, &[2, 4], 1e-5);
5751            let root = layer_norm_last_dim_backward::decompose(
5752                &mut g,
5753                fused,
5754                &FusedOpParams::LayerNormLastDimBackward { eps: 1e-5 },
5755            );
5756            assert_ne!(root, fused, "recipe decompose fires");
5757            assert!(
5758                matches!(g.node(root).op, Op::Mul),
5759                "backward root is the outer Mul(istd, inner)"
5760            );
5761            let reachable = crate::topo_order_multi(&g, &[root]);
5762            let unsqueezes = reachable
5763                .iter()
5764                .filter(|&&n| matches!(g.node(n).op, Op::Unsqueeze { .. }))
5765                .count();
5766            let reshapes = reachable
5767                .iter()
5768                .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
5769                .count();
5770            assert!(
5771                unsqueezes >= 1,
5772                "keepdim restored via Unsqueeze append (D3 swap)"
5773            );
5774            assert_eq!(
5775                reshapes, 0,
5776                "no baked keepdim Reshape / no D4 pad after the D3 swap"
5777            );
5778        }
5779
5780        /// S2-1 (RISK-A): `xhat = Mul(xc, istd)` and `istd = Rsqrt(AddScalar[
5781        /// eps](...))` carry the eps OPEN slot, so emit's slot-free
5782        /// identity-share does NOT dedup them — the recipe emits them once per
5783        /// occurrence, yielding a STRICTLY HEAVIER base map than the legacy DAG
5784        /// (which shares them). This is the ACCEPTED redundancy documented on
5785        /// `recipe()` (numerically identical — deterministic recompute — and no
5786        /// open-slot-sharing emit extension is built in this slice). The
5787        /// downstream full-optimizer CSE re-collapses the duplicates, which this
5788        /// pins as the mitigation.
5789        #[test]
5790        fn layer_norm_backward_recipe_open_slot_xhat_is_recomputed_then_cse_recollapses() {
5791            let count_ops = |g: &Graph, root: NodeId| -> usize {
5792                crate::topo_order_multi(g, &[root])
5793                    .iter()
5794                    .filter(|&&n| !matches!(g.node(n).op, Op::Const))
5795                    .count()
5796            };
5797
5798            // Recipe emission (base map — no fused ops remain to lower).
5799            let mut gr = Graph::new();
5800            let (_x, _up, fused) = ln_backward_fused_node(&mut gr, &[2, 4], 1e-5);
5801            let recipe_root = layer_norm_last_dim_backward::decompose(
5802                &mut gr,
5803                fused,
5804                &FusedOpParams::LayerNormLastDimBackward { eps: 1e-5 },
5805            );
5806            let recipe_n = count_ops(&gr, recipe_root);
5807
5808            // Legacy DAG (shares xhat/istd via let-bindings).
5809            let mut gl = Graph::new();
5810            let (_x2, _up2, fused2) = ln_backward_fused_node(&mut gl, &[2, 4], 1e-5);
5811            let legacy_root = frozen_legacy_layer_norm_backward_decompose(
5812                &mut gl,
5813                fused2,
5814                &FusedOpParams::LayerNormLastDimBackward { eps: 1e-5 },
5815            );
5816            let legacy_n = count_ops(&gl, legacy_root);
5817
5818            assert!(
5819                recipe_n > legacy_n,
5820                "RISK-A: emit's slot-free share does NOT dedup the eps-slot-carrying \
5821                 xhat/istd — recipe base map {recipe_n} > legacy {legacy_n}",
5822            );
5823
5824            // Downstream full-optimizer CSE re-collapses the redundant recompute.
5825            let shared: crate::SharedGraph = std::sync::Arc::new(std::sync::RwLock::new(gr));
5826            let cse_roots = crate::opt::optimize(&shared, &[recipe_root]);
5827            let gr = shared.read().unwrap();
5828            let cse_n = count_ops(&gr, cse_roots[0]);
5829            assert!(
5830                cse_n < recipe_n,
5831                "downstream CSE re-collapses the redundant recompute: {cse_n} < {recipe_n}",
5832            );
5833            assert_eq!(
5834                cse_n, legacy_n,
5835                "CSE collapses the recipe base map back to the legacy DAG node count",
5836            );
5837        }
5838
5839        /// S2-1 (totality): a wrong params payload is a typed decline surfaced
5840        /// as a fixpoint (G2), never a panic, declining BEFORE any emission.
5841        #[test]
5842        fn layer_norm_backward_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
5843            let mut g = Graph::new();
5844            let (_x, _up, fused) = ln_backward_fused_node(&mut g, &[2, 4], 1e-5);
5845            let before = g.len();
5846            let out = layer_norm_last_dim_backward::decompose(&mut g, fused, &FusedOpParams::Rope);
5847            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
5848            assert_eq!(g.len(), before, "declined before any emission");
5849        }
5850    }
5851
5852    // rms_norm_last_dim_backward migration (Increment C carriers, A1) ---------
5853    //
5854    // The ~22-node imperative closed-form
5855    // `grad_x = r_rms · (g − x·s / (n·(mean_sq + eps)))` becomes a portable
5856    // `PatternNode` DATA recipe — the FIRST recipe to drive a SHAPE-DERIVED
5857    // scalar through live emit: `n = dims[last]` (the reduced_count) is a
5858    // `MulScalar(scalar_rel = Extent(0, LAST))`, resolved from x's shape at emit
5859    // (not baked, not a params slot). Sibling of the layer-norm-backward recipe:
5860    // `MeanDim(axis_last)` + `Unsqueeze(append)` keepdim (D3 swap) + broadcast to
5861    // `SameAs 0`; the `eps` `AddScalar` is an OPEN slot filled by the projection.
5862    // Binds: `0 = x`, `1 = upstream`. RISK-A: `denom_kd` (which carries the eps
5863    // slot) is consumed by BOTH the `Rsqrt` and the `MulScalar(n)`, so emit's
5864    // slot-free identity-share does NOT dedup it — the recipe emits it twice
5865    // (numerically identical deterministic recompute; the legacy DAG shared it),
5866    // and downstream CSE re-collapses. The parity oracle runs both sides through
5867    // a toy f64 interpreter (Reshape/Unsqueeze = metadata passthrough) so neither
5868    // the D3 swap nor the recompute can perturb the bit-exact assert.
5869    mod rms_norm_backward_recipe {
5870        use super::super::*;
5871        use super::frozen_legacy_rms_norm_backward_decompose;
5872        use crate::registry::{FusedOps, rms_norm_last_dim_backward};
5873        use fuel_ir::{DType, Shape};
5874        use std::collections::HashMap;
5875
5876        /// Tiny f64 reference interpreter over the rms-norm-backward primitive
5877        /// vocabulary (leaf-lookup FIRST, then elementwise `Mul`/`Sub`/`Div`,
5878        /// unary `Sqr`/`Rsqrt`, `AddScalar`/`MulScalar`, last-axis
5879        /// `MeanDim`/`SumDim`, metadata-only keepdim restore
5880        /// `Unsqueeze`/`Reshape`, last-dim `BroadcastTo`). BOTH parity sides run
5881        /// through it with identical in-order arithmetic, so a bit-exact assert
5882        /// isolates recipe STRUCTURE — neither the `Reshape`→`Unsqueeze` D3 swap
5883        /// NOR the open-slot `denom_kd` recompute can perturb it.
5884        fn eval_rnb(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
5885            if let Some(v) = leaves.get(&id) {
5886                return v.clone();
5887            }
5888            let node = g.node(id);
5889            match &node.op {
5890                Op::Mul => {
5891                    let a = eval_rnb(g, node.inputs[0], leaves);
5892                    let b = eval_rnb(g, node.inputs[1], leaves);
5893                    a.iter().zip(&b).map(|(x, y)| x * y).collect()
5894                }
5895                Op::Sub => {
5896                    let a = eval_rnb(g, node.inputs[0], leaves);
5897                    let b = eval_rnb(g, node.inputs[1], leaves);
5898                    a.iter().zip(&b).map(|(x, y)| x - y).collect()
5899                }
5900                Op::Div => {
5901                    let a = eval_rnb(g, node.inputs[0], leaves);
5902                    let b = eval_rnb(g, node.inputs[1], leaves);
5903                    a.iter().zip(&b).map(|(x, y)| x / y).collect()
5904                }
5905                Op::Sqr => eval_rnb(g, node.inputs[0], leaves)
5906                    .iter()
5907                    .map(|v| v * v)
5908                    .collect(),
5909                Op::Rsqrt => eval_rnb(g, node.inputs[0], leaves)
5910                    .iter()
5911                    .map(|v| 1.0 / v.sqrt())
5912                    .collect(),
5913                Op::AddScalar(e) => eval_rnb(g, node.inputs[0], leaves)
5914                    .iter()
5915                    .map(|v| v + e)
5916                    .collect(),
5917                Op::MulScalar(m) => eval_rnb(g, node.inputs[0], leaves)
5918                    .iter()
5919                    .map(|v| v * m)
5920                    .collect(),
5921                // Last-axis reductions — rank-reducing; identical fold both
5922                // spellings.
5923                Op::MeanDim(_) => {
5924                    let input = eval_rnb(g, node.inputs[0], leaves);
5925                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
5926                    input
5927                        .chunks(last)
5928                        .map(|row| row.iter().sum::<f64>() / last as f64)
5929                        .collect()
5930                }
5931                Op::SumDim(_) => {
5932                    let input = eval_rnb(g, node.inputs[0], leaves);
5933                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
5934                    input
5935                        .chunks(last)
5936                        .map(|row| row.iter().sum::<f64>())
5937                        .collect()
5938                }
5939                // Metadata-only keepdim restores (the D3 swap and its legacy twin
5940                // evaluate identically here).
5941                Op::Unsqueeze { .. } | Op::Reshape(_) => eval_rnb(g, node.inputs[0], leaves),
5942                // Broadcast a keepdim `[.., 1]` tensor back along the last axis.
5943                Op::BroadcastTo(target) => {
5944                    let input = eval_rnb(g, node.inputs[0], leaves);
5945                    let out_n: usize = target.dims().iter().product();
5946                    let last = *target.dims().last().unwrap();
5947                    assert_eq!(
5948                        input.len() * last,
5949                        out_n,
5950                        "broadcast is a last-dim repeat in these graphs",
5951                    );
5952                    input
5953                        .iter()
5954                        .flat_map(|&v| std::iter::repeat_n(v, last))
5955                        .collect()
5956                }
5957                other => panic!("eval_rnb: unhandled op {other:?}"),
5958            }
5959        }
5960
5961        /// Build a fused RmsNormLastDimBackward node over `x [dims]` (input 0)
5962        /// and `g [dims]` (input 1, the upstream gradient), carrying `eps`.
5963        /// Returns `(x, g, fused)`.
5964        fn rms_backward_fused_node(
5965            g: &mut Graph,
5966            dims: &[usize],
5967            eps: f64,
5968        ) -> (NodeId, NodeId, NodeId) {
5969            let sh = Shape::from_dims(dims);
5970            let x = g.push(Node {
5971                op: Op::Const,
5972                inputs: vec![],
5973                shape: sh.clone(),
5974                dtype: DType::F32,
5975            });
5976            let up = g.push(Node {
5977                op: Op::Const,
5978                inputs: vec![],
5979                shape: sh.clone(),
5980                dtype: DType::F32,
5981            });
5982            let fused = g.push(Node {
5983                op: Op::Fused(
5984                    FusedOps::RMS_NORM_LAST_DIM_BACKWARD,
5985                    FusedOpParams::RmsNormLastDimBackward { eps },
5986                ),
5987                inputs: vec![x, up],
5988                shape: sh,
5989                dtype: DType::F32,
5990            });
5991            (x, up, fused)
5992        }
5993
5994        /// A1 (a): ONE recipe datum decomposes at BOTH rank 2 and rank 3 (the
5995        /// polymorphism the baked-shape legacy body never had — including the
5996        /// shape-derived `MulScalar(n)`), and its numerics match the FROZEN
5997        /// legacy builder bit-exactly under the shared reference interpreter.
5998        #[test]
5999        fn rms_norm_backward_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
6000            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
6001                let mut g = Graph::new();
6002                let (x, up, fused) = rms_backward_fused_node(&mut g, &dims, 1e-5);
6003                let sh = Shape::from_dims(&dims);
6004                let new_root = rms_norm_last_dim_backward::decompose(
6005                    &mut g,
6006                    fused,
6007                    &FusedOpParams::RmsNormLastDimBackward { eps: 1e-5 },
6008                );
6009                assert_ne!(new_root, fused, "recipe decompose must fire at {dims:?}");
6010                assert_eq!(
6011                    g.node(new_root).shape,
6012                    sh,
6013                    "rms_norm backward is shape-preserving"
6014                );
6015                assert_eq!(g.node(new_root).dtype, DType::F32);
6016
6017                let legacy_root = frozen_legacy_rms_norm_backward_decompose(
6018                    &mut g,
6019                    fused,
6020                    &FusedOpParams::RmsNormLastDimBackward { eps: 1e-5 },
6021                );
6022
6023                let n: usize = dims.iter().product();
6024                let x_data: Vec<f64> = (0..n)
6025                    .map(|i| ((i as f64) * 0.41).sin() * 2.0 + 0.7)
6026                    .collect();
6027                let g_data: Vec<f64> = (0..n)
6028                    .map(|i| ((i as f64) * 0.61).cos() * 1.3 - 0.15)
6029                    .collect();
6030                let mut leaves = HashMap::new();
6031                leaves.insert(x, x_data);
6032                leaves.insert(up, g_data);
6033                let got = eval_rnb(&g, new_root, &leaves);
6034                let want = eval_rnb(&g, legacy_root, &leaves);
6035                assert_eq!(got.len(), want.len());
6036                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
6037                    assert_eq!(
6038                        a.to_bits(),
6039                        b.to_bits(),
6040                        "rms_norm_backward[{i}] at {dims:?}: recipe={a} vs legacy={b}",
6041                    );
6042                }
6043            }
6044        }
6045
6046        /// A1 (structural, born-red): every keepdim restore is the D3
6047        /// shrink-via-swap `Unsqueeze` append, NOT the baked `Reshape(keepdim)`
6048        /// (the crisp discriminator against the pre-migration imperative body,
6049        /// which emits `Reshape` — so this is RED until migration), and the
6050        /// reduced-count divisor is a live `MulScalar(n = dims[last])` resolved
6051        /// from x's shape by the `scalar_rel` carrier (evidence the shape-derived
6052        /// scalar reached emit). The backward root is the outer `Mul(rrms, inner)`.
6053        #[test]
6054        fn rms_norm_backward_recipe_uses_the_unsqueeze_keepdim_swap_and_shape_scalar() {
6055            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
6056                let mut g = Graph::new();
6057                let (_x, _up, fused) = rms_backward_fused_node(&mut g, &dims, 1e-5);
6058                let root = rms_norm_last_dim_backward::decompose(
6059                    &mut g,
6060                    fused,
6061                    &FusedOpParams::RmsNormLastDimBackward { eps: 1e-5 },
6062                );
6063                assert_ne!(root, fused, "recipe decompose fires at {dims:?}");
6064                assert!(
6065                    matches!(g.node(root).op, Op::Mul),
6066                    "backward root is the outer Mul(rrms, inner)"
6067                );
6068                let reachable = crate::topo_order_multi(&g, &[root]);
6069                let unsqueezes = reachable
6070                    .iter()
6071                    .filter(|&&n| matches!(g.node(n).op, Op::Unsqueeze { .. }))
6072                    .count();
6073                let reshapes = reachable
6074                    .iter()
6075                    .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
6076                    .count();
6077                assert!(
6078                    unsqueezes >= 1,
6079                    "keepdim restored via Unsqueeze append (D3 swap) at {dims:?}"
6080                );
6081                assert_eq!(
6082                    reshapes, 0,
6083                    "no baked keepdim Reshape / no D4 pad after the D3 swap at {dims:?}"
6084                );
6085                // The reduced-count divisor resolved to n = dims[last] from x's
6086                // shape (the scalar_rel carrier), at whatever rank.
6087                let n = *dims.last().unwrap() as f64;
6088                let mul_scalars: Vec<f64> = reachable
6089                    .iter()
6090                    .filter_map(|&nid| match g.node(nid).op {
6091                        Op::MulScalar(v) => Some(v),
6092                        _ => None,
6093                    })
6094                    .collect();
6095                assert!(
6096                    mul_scalars.contains(&n),
6097                    "a MulScalar(n={n}) resolved from x's last-axis extent at {dims:?}, got {mul_scalars:?}",
6098                );
6099            }
6100        }
6101    }
6102
6103    // fused_linear migration (Increment C slice 2, S2-2) ----------------------
6104    //
6105    // The 3-node imperative `Add(MatMul(a, b), BroadcastTo(rank-1 bias))`
6106    // becomes a portable `PatternNode` DATA recipe, and the FIRST recipe to
6107    // drive `WithDim` through live emit: the bias broadcast targets
6108    // `WithDim { operand: 0, axis: LAST, dim: Extent { operand: 1, axis: LAST } }`
6109    // — a's shape with its LAST axis (K) replaced by b's LAST extent (N) = the
6110    // matmul output `[..batch, M, N]`. This is Fuel-INTERNAL shape resolution
6111    // (not §6.19 wire emission), so it is NOT gated on KISS #86. Binds:
6112    // `0 = a`, `1 = b`, `2 = bias`. The rank-1 bias rank-raises to the matmul
6113    // output, so emit materializes a leading-1 pad `Reshape` (D4) the imperative
6114    // body never had — the numeric-parity oracle below runs both sides through a
6115    // toy interpreter (Reshape = metadata passthrough) to isolate it. The
6116    // `canonical_pattern` see-through of that pad (RISK-C) + the lower→fuse
6117    // round-trip live in `registry/fused_linear.rs`'s own tests.
6118    mod fused_linear_recipe {
6119        use super::super::*;
6120        use super::frozen_legacy_fused_linear_decompose;
6121        use crate::registry::{FusedOps, fused_linear};
6122        use fuel_ir::{DType, Shape};
6123        use std::collections::HashMap;
6124
6125        /// Right-aligned NumPy broadcast of `input` (shape `in_shape`) to
6126        /// `target` — a size-1 or padded leading dim contributes stride 0.
6127        /// General on purpose: fused_linear's bias broadcast tiles along the
6128        /// LEADING `M`/batch dims (`[1,N] → [M,N]`), unlike the norm recipes'
6129        /// last-dim keepdim repeat.
6130        fn broadcast(input: &[f64], in_shape: &[usize], target: &[usize]) -> Vec<f64> {
6131            let rank = target.len();
6132            let pad = rank - in_shape.len();
6133            let mut real_strides = vec![0isize; in_shape.len()];
6134            let mut s = 1isize;
6135            for i in (0..in_shape.len()).rev() {
6136                real_strides[i] = s;
6137                s *= in_shape[i] as isize;
6138            }
6139            let mut in_strides = vec![0isize; rank];
6140            for (i, stride) in in_strides.iter_mut().enumerate() {
6141                if i >= pad {
6142                    let id = i - pad;
6143                    *stride = if in_shape[id] == 1 {
6144                        0
6145                    } else {
6146                        real_strides[id]
6147                    };
6148                }
6149            }
6150            let out_n: usize = target.iter().product();
6151            let mut out = Vec::with_capacity(out_n);
6152            let mut idx = vec![0usize; rank];
6153            for _ in 0..out_n {
6154                let fi: isize = (0..rank).map(|i| idx[i] as isize * in_strides[i]).sum();
6155                out.push(input[fi as usize]);
6156                for i in (0..rank).rev() {
6157                    idx[i] += 1;
6158                    if idx[i] < target[i] {
6159                        break;
6160                    }
6161                    idx[i] = 0;
6162                }
6163            }
6164            out
6165        }
6166
6167        /// Deterministic batched f64 matmul `a[..batch, M, K] · b[..batch, K, N]`
6168        /// (same-rank ≥ 2, aligned batch). BOTH parity sides share the SAME
6169        /// `MatMul(a, b)` binds, so identical accumulation order ⇒ identical
6170        /// bits.
6171        fn matmul(a: &[f64], ash: &[usize], b: &[f64], bsh: &[usize]) -> Vec<f64> {
6172            let r = ash.len();
6173            let (m, k, n) = (ash[r - 2], ash[r - 1], bsh[r - 1]);
6174            let batch: usize = ash[..r - 2].iter().product();
6175            let (a_bs, b_bs, o_bs) = (m * k, k * n, m * n);
6176            let mut out = vec![0.0f64; batch * o_bs];
6177            for bi in 0..batch {
6178                for i in 0..m {
6179                    for j in 0..n {
6180                        let mut acc = 0.0f64;
6181                        for kk in 0..k {
6182                            acc += a[bi * a_bs + i * k + kk] * b[bi * b_bs + kk * n + j];
6183                        }
6184                        out[bi * o_bs + i * n + j] = acc;
6185                    }
6186                }
6187            }
6188            out
6189        }
6190
6191        /// Tiny f64 reference interpreter over the fused-linear primitive
6192        /// vocabulary (leaf-lookup FIRST, then `MatMul`, metadata-only
6193        /// `Reshape` pad, right-aligned `BroadcastTo`, elementwise `Add`). BOTH
6194        /// parity sides run through it with identical arithmetic, so a bit-exact
6195        /// assert isolates recipe STRUCTURE — the D4 pad `Reshape` (recipe only)
6196        /// is a passthrough here.
6197        fn eval_fl(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
6198            if let Some(v) = leaves.get(&id) {
6199                return v.clone();
6200            }
6201            let node = g.node(id);
6202            match &node.op {
6203                Op::MatMul => {
6204                    let a = eval_fl(g, node.inputs[0], leaves);
6205                    let b = eval_fl(g, node.inputs[1], leaves);
6206                    let ash: Vec<usize> = g.node(node.inputs[0]).shape.dims().to_vec();
6207                    let bsh: Vec<usize> = g.node(node.inputs[1]).shape.dims().to_vec();
6208                    matmul(&a, &ash, &b, &bsh)
6209                }
6210                // Metadata-only leading-1 pad (recipe's D4) — values unchanged.
6211                Op::Reshape(_) => eval_fl(g, node.inputs[0], leaves),
6212                Op::BroadcastTo(target) => {
6213                    let input = eval_fl(g, node.inputs[0], leaves);
6214                    let in_shape: Vec<usize> = g.node(node.inputs[0]).shape.dims().to_vec();
6215                    broadcast(&input, &in_shape, target.dims())
6216                }
6217                Op::Add => {
6218                    let a = eval_fl(g, node.inputs[0], leaves);
6219                    let b = eval_fl(g, node.inputs[1], leaves);
6220                    a.iter().zip(&b).map(|(x, y)| x + y).collect()
6221                }
6222                other => panic!("eval_fl: unhandled op {other:?}"),
6223            }
6224        }
6225
6226        /// Build a fused FusedLinear node over `a [a_dims]`, `b [b_dims]`, and a
6227        /// rank-1 `bias [N]` (N = b's last dim). Returns `(a, b, bias, fused)`.
6228        fn fused_linear_fused_node(
6229            g: &mut Graph,
6230            a_dims: &[usize],
6231            b_dims: &[usize],
6232        ) -> (NodeId, NodeId, NodeId, NodeId) {
6233            let ar = a_dims.len();
6234            let n = b_dims[b_dims.len() - 1];
6235            let mut out_dims = a_dims[..ar - 2].to_vec();
6236            out_dims.push(a_dims[ar - 2]);
6237            out_dims.push(n);
6238            let a = g.push(Node {
6239                op: Op::Const,
6240                inputs: vec![],
6241                shape: Shape::from_dims(a_dims),
6242                dtype: DType::F32,
6243            });
6244            let b = g.push(Node {
6245                op: Op::Const,
6246                inputs: vec![],
6247                shape: Shape::from_dims(b_dims),
6248                dtype: DType::F32,
6249            });
6250            let bias = g.push(Node {
6251                op: Op::Const,
6252                inputs: vec![],
6253                shape: Shape::from_dims(&[n]),
6254                dtype: DType::F32,
6255            });
6256            let fused = g.push(Node {
6257                op: Op::Fused(FusedOps::FUSED_LINEAR, FusedOpParams::FusedLinear),
6258                inputs: vec![a, b, bias],
6259                shape: Shape::from_dims(&out_dims),
6260                dtype: DType::F32,
6261            });
6262            (a, b, bias, fused)
6263        }
6264
6265        /// S2-2 (a): ONE recipe datum decomposes at BOTH rank-2 and rank-3
6266        /// (batched) activations — the WithDim-derived broadcast target tracks
6267        /// the matmul output shape — and its numerics match the FROZEN legacy
6268        /// builder bit-exactly under the shared reference interpreter.
6269        #[test]
6270        fn fused_linear_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
6271            for (a_dims, b_dims) in [
6272                (vec![2usize, 3], vec![3usize, 4]),
6273                (vec![2, 2, 3], vec![2, 3, 4]),
6274            ] {
6275                let mut g = Graph::new();
6276                let (a, b, bias, fused) = fused_linear_fused_node(&mut g, &a_dims, &b_dims);
6277                let out_sh = g.node(fused).shape.clone();
6278                let new_root = fused_linear::decompose(&mut g, fused, &FusedOpParams::FusedLinear);
6279                assert_ne!(new_root, fused, "recipe decompose fires at a={a_dims:?}");
6280                assert_eq!(
6281                    g.node(new_root).shape,
6282                    out_sh,
6283                    "output = matmul output shape"
6284                );
6285                assert_eq!(g.node(new_root).dtype, DType::F32);
6286
6287                let legacy_root = frozen_legacy_fused_linear_decompose(
6288                    &mut g,
6289                    fused,
6290                    &FusedOpParams::FusedLinear,
6291                );
6292
6293                let an: usize = a_dims.iter().product();
6294                let bn: usize = b_dims.iter().product();
6295                let bias_n = b_dims[b_dims.len() - 1];
6296                let a_data: Vec<f64> = (0..an)
6297                    .map(|i| ((i as f64) * 0.31).sin() * 1.5 - 0.2)
6298                    .collect();
6299                let b_data: Vec<f64> = (0..bn)
6300                    .map(|i| ((i as f64) * 0.47).cos() * 0.8 + 0.1)
6301                    .collect();
6302                let bias_data: Vec<f64> = (0..bias_n)
6303                    .map(|i| ((i as f64) * 0.7).sin() * 2.0)
6304                    .collect();
6305                let mut leaves = HashMap::new();
6306                leaves.insert(a, a_data);
6307                leaves.insert(b, b_data);
6308                leaves.insert(bias, bias_data);
6309                let got = eval_fl(&g, new_root, &leaves);
6310                let want = eval_fl(&g, legacy_root, &leaves);
6311                assert_eq!(got.len(), want.len());
6312                for (i, (x, y)) in got.iter().zip(want.iter()).enumerate() {
6313                    assert_eq!(
6314                        x.to_bits(),
6315                        y.to_bits(),
6316                        "fused_linear[{i}] a={a_dims:?}: recipe={x} vs legacy={y}",
6317                    );
6318                }
6319            }
6320        }
6321
6322        /// S2-2 (structural, born-red): the WithDim-derived broadcast target
6323        /// rank-raises the rank-1 bias to the matmul output `[M,N]`, so emit
6324        /// materializes exactly one leading-1 pad `Reshape` (the D4 path, driven
6325        /// LIVE by WithDim). The imperative body broadcast the rank-1 bias
6326        /// directly (zero Reshape) — so this is RED until migration. The bias
6327        /// broadcast target equals the matmul output shape.
6328        #[test]
6329        fn fused_linear_recipe_drives_withdim_broadcast_with_a_d4_pad() {
6330            let mut g = Graph::new();
6331            let (_a, _b, _bias, fused) = fused_linear_fused_node(&mut g, &[2, 3], &[3, 4]);
6332            let root = fused_linear::decompose(&mut g, fused, &FusedOpParams::FusedLinear);
6333            assert_ne!(root, fused, "recipe decompose fires");
6334            assert!(
6335                matches!(g.node(root).op, Op::Add),
6336                "root is Add(mm, bias_bcst)"
6337            );
6338            let reachable = crate::topo_order_multi(&g, &[root]);
6339            let reshapes = reachable
6340                .iter()
6341                .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
6342                .count();
6343            assert_eq!(
6344                reshapes, 1,
6345                "WithDim rank-raise materializes exactly one D4 pad Reshape on the bias",
6346            );
6347            let bcst = reachable
6348                .iter()
6349                .find(|&&n| matches!(g.node(n).op, Op::BroadcastTo(_)))
6350                .expect("bias broadcast present");
6351            assert_eq!(
6352                g.node(*bcst).shape.dims(),
6353                &[2, 4],
6354                "bias broadcast target = the matmul output shape (WithDim resolved)",
6355            );
6356        }
6357
6358        /// S2-2 (totality): a wrong params payload is a typed decline surfaced
6359        /// as a fixpoint (G2), never a panic, declining BEFORE any emission.
6360        #[test]
6361        fn fused_linear_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
6362            let mut g = Graph::new();
6363            let (_a, _b, _bias, fused) = fused_linear_fused_node(&mut g, &[2, 3], &[3, 4]);
6364            let before = g.len();
6365            let out = fused_linear::decompose(&mut g, fused, &FusedOpParams::Rope);
6366            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
6367            assert_eq!(g.len(), before, "declined before any emission");
6368        }
6369    }
6370
6371    /// Increment C, B1 — the `Op::Scan` structural re-emit machinery. These
6372    /// tests prove a hand-built `Op::Scan` recipe (an `OpTag::Scan` node whose
6373    /// body sub-graph — `ScanPlaceholder` holes + body ops — rides its trailing
6374    /// operands, per the Phase-1 lax.scan encoding) round-trips through
6375    /// `tag_to_op`/`emit` to a graph whose base map is bit-identical to the
6376    /// same scan built imperatively (the way `selective_scan::decompose` emits
6377    /// it). `Op::Scan` stays a base-map terminal — the recipe just RE-EMITS it.
6378    ///
6379    /// Structure-preserving: the recipe mirrors the imperative
6380    /// `unroll_scan`-shaped scan node exactly (same op types, same params, same
6381    /// body structure, same const-sharing via a shared `Bind`), so the
6382    /// `base_map_hash` equality is a structural-identity proof. Neither
6383    /// `Op::Scan` nor `Op::ScanPlaceholder` folds its own node shape into
6384    /// `base_map_hash` (op_key tags 210/211 fold only their params + the body
6385    /// via child recursion), so emit's fallback interior shapes are inert for
6386    /// the identity — the base map matches by construction.
6387    mod scan_recipe_roundtrip {
6388        use super::super::*;
6389        use super::{bind, op_node};
6390        use crate::opt::base_map_hash;
6391        use crate::{ScanEmit, ScanPredicate, ScanRole};
6392        use fuel_ir::storage::{OutputViewSpec, compose_bundle};
6393        use fuel_ir::{DType, Shape};
6394        use fuel_kernel_seam_types::{SCAN_ROLE_CARRY, SCAN_ROLE_ELEM};
6395        use std::sync::Arc;
6396
6397        fn placeholder(role: u8, index: u32) -> PatternNode {
6398            PatternNode::Op {
6399                op: OpTag::ScanPlaceholder,
6400                attrs: OpAttrs {
6401                    scan_role: Some(role),
6402                    scan_index: Some(index),
6403                    ..OpAttrs::default()
6404                },
6405                operands: vec![],
6406            }
6407        }
6408
6409        /// The recipe: `scan(n_xs=1, bound=3, emit=All)` over
6410        /// `[init(B0), xs0(B1), const(B2), body_new_carry, body_y]` where
6411        /// `body_new_carry = Add(carry_hole, elem_hole)` and
6412        /// `body_y = Mul(body_new_carry, const)`. The `Add(carry,elem)` sub-tree
6413        /// appears in BOTH body exits — emit's slot-free identity-share collapses
6414        /// them to one node, matching the imperative `sum` referenced twice.
6415        fn scan_recipe() -> PatternNode {
6416            let sum = || {
6417                op_node(
6418                    OpTag::Add,
6419                    OpAttrs::default(),
6420                    vec![
6421                        placeholder(SCAN_ROLE_CARRY, 0),
6422                        placeholder(SCAN_ROLE_ELEM, 0),
6423                    ],
6424                )
6425            };
6426            PatternNode::Op {
6427                op: OpTag::Scan,
6428                attrs: OpAttrs {
6429                    scan_n_carries: Some(1),
6430                    scan_n_xs: Some(1),
6431                    scan_bound: Some(3),
6432                    scan_emit: Some(0), // All
6433                    scan_early_exit: None,
6434                    ..OpAttrs::default()
6435                },
6436                operands: vec![
6437                    bind(0),                                                       // init_carry
6438                    bind(1),                                                       // xs0
6439                    bind(2),                                                       // const
6440                    sum(),                                                         // body_new_carry
6441                    op_node(OpTag::Mul, OpAttrs::default(), vec![sum(), bind(2)]), // body_y
6442                ],
6443            }
6444        }
6445
6446        /// Build the reference scan imperatively (the shape/layout
6447        /// `selective_scan::decompose` + `unroll_scan` assume) and return the
6448        /// scan NodeId. Leaves are unpopulated `Op::Const`s — same shape/dtype
6449        /// as the emitted recipe's, so they hash identically.
6450        fn reference_scan(g: &mut Graph) -> NodeId {
6451            let cs = Shape::from_dims(&[1]);
6452            let xs = Shape::from_dims(&[3, 1]);
6453            let init = g.push(Node {
6454                op: Op::Const,
6455                inputs: vec![],
6456                shape: cs.clone(),
6457                dtype: DType::F32,
6458            });
6459            let xs0 = g.push(Node {
6460                op: Op::Const,
6461                inputs: vec![],
6462                shape: xs,
6463                dtype: DType::F32,
6464            });
6465            let konst = g.push(Node {
6466                op: Op::Const,
6467                inputs: vec![],
6468                shape: cs.clone(),
6469                dtype: DType::F32,
6470            });
6471            let carry = g.push(Node {
6472                op: Op::ScanPlaceholder {
6473                    role: ScanRole::Carry,
6474                    index: 0,
6475                },
6476                inputs: vec![],
6477                shape: cs.clone(),
6478                dtype: DType::F32,
6479            });
6480            let elem = g.push(Node {
6481                op: Op::ScanPlaceholder {
6482                    role: ScanRole::Elem,
6483                    index: 0,
6484                },
6485                inputs: vec![],
6486                shape: cs.clone(),
6487                dtype: DType::F32,
6488            });
6489            let sum = g.push(Node {
6490                op: Op::Add,
6491                inputs: vec![carry, elem],
6492                shape: cs.clone(),
6493                dtype: DType::F32,
6494            });
6495            let y = g.push(Node {
6496                op: Op::Mul,
6497                inputs: vec![sum, konst],
6498                shape: cs.clone(),
6499                dtype: DType::F32,
6500            });
6501            g.push(Node {
6502                op: Op::Scan {
6503                    n_carries: 1,
6504                    n_xs: 1,
6505                    bound: 3,
6506                    emit: ScanEmit::All,
6507                    early_exit: None,
6508                },
6509                inputs: vec![init, xs0, konst, sum, y],
6510                shape: Shape::from_dims(&[3, 1]),
6511                dtype: DType::F32,
6512            })
6513        }
6514
6515        /// `tag_to_op` reconstructs the `Op::Scan` params + `ScanPlaceholder`
6516        /// role/index verbatim from the carriers.
6517        #[test]
6518        fn tag_to_op_reconstructs_scan_and_placeholder() {
6519            let scan_attrs = OpAttrs {
6520                scan_n_carries: Some(1),
6521                scan_n_xs: Some(1),
6522                scan_bound: Some(3),
6523                scan_emit: Some(0),
6524                scan_early_exit: None,
6525                ..OpAttrs::default()
6526            };
6527            assert!(matches!(
6528                tag_to_op(OpTag::Scan, &scan_attrs),
6529                Some(Op::Scan {
6530                    n_carries: 1,
6531                    n_xs: 1,
6532                    bound: 3,
6533                    emit: ScanEmit::All,
6534                    early_exit: None
6535                }),
6536            ));
6537            let fin = OpAttrs {
6538                scan_emit: Some(1),
6539                scan_early_exit: Some(true),
6540                ..scan_attrs.clone()
6541            };
6542            assert!(matches!(
6543                tag_to_op(OpTag::Scan, &fin),
6544                Some(Op::Scan {
6545                    emit: ScanEmit::Final,
6546                    early_exit: Some(ScanPredicate),
6547                    ..
6548                }),
6549            ));
6550            // A scan node with no bound is an honest miss (unset required attr).
6551            assert!(tag_to_op(OpTag::Scan, &OpAttrs::default()).is_none());
6552            let carry = OpAttrs {
6553                scan_role: Some(SCAN_ROLE_CARRY),
6554                scan_index: Some(0),
6555                ..OpAttrs::default()
6556            };
6557            assert!(matches!(
6558                tag_to_op(OpTag::ScanPlaceholder, &carry),
6559                Some(Op::ScanPlaceholder {
6560                    role: ScanRole::Carry,
6561                    index: 0
6562                }),
6563            ));
6564            let elem = OpAttrs {
6565                scan_role: Some(SCAN_ROLE_ELEM),
6566                scan_index: Some(2),
6567                ..OpAttrs::default()
6568            };
6569            assert!(matches!(
6570                tag_to_op(OpTag::ScanPlaceholder, &elem),
6571                Some(Op::ScanPlaceholder {
6572                    role: ScanRole::Elem,
6573                    index: 2
6574                }),
6575            ));
6576            assert!(tag_to_op(OpTag::ScanPlaceholder, &OpAttrs::default()).is_none());
6577        }
6578
6579        /// The recipe is a valid, representable region: contiguous binds `[0,1,2]`
6580        /// and every op (incl. `Scan`/`ScanPlaceholder`) re-emits to a primitive.
6581        #[test]
6582        fn scan_recipe_validates_as_representable() {
6583            assert_eq!(scan_recipe().bind_indices(), vec![0, 1, 2]);
6584            assert!(
6585                validate_recipe(&scan_recipe()).is_ok(),
6586                "scan recipe is a total, representable region"
6587            );
6588        }
6589
6590        /// THE round-trip: emit the recipe onto fresh input leaves and assert its
6591        /// base map is bit-identical to the imperatively-built scan.
6592        #[test]
6593        fn scan_recipe_reemits_to_the_same_base_map_as_the_imperative_scan() {
6594            // Reference.
6595            let mut gr = Graph::new();
6596            let ref_scan = reference_scan(&mut gr);
6597            let want = base_map_hash(&gr, ref_scan);
6598
6599            // Emitted from the recipe onto identical leaves.
6600            let mut ge = Graph::new();
6601            let cs = Shape::from_dims(&[1]);
6602            let init = ge.push(Node {
6603                op: Op::Const,
6604                inputs: vec![],
6605                shape: cs.clone(),
6606                dtype: DType::F32,
6607            });
6608            let xs0 = ge.push(Node {
6609                op: Op::Const,
6610                inputs: vec![],
6611                shape: Shape::from_dims(&[3, 1]),
6612                dtype: DType::F32,
6613            });
6614            let konst = ge.push(Node {
6615                op: Op::Const,
6616                inputs: vec![],
6617                shape: cs,
6618                dtype: DType::F32,
6619            });
6620            let root = emit_region(&mut ge, &scan_recipe(), &[init, xs0, konst], &[]);
6621            assert!(
6622                matches!(
6623                    ge.node(root).op,
6624                    Op::Scan {
6625                        n_carries: 1,
6626                        n_xs: 1,
6627                        bound: 3,
6628                        emit: ScanEmit::All,
6629                        early_exit: None
6630                    }
6631                ),
6632                "recipe re-emits an Op::Scan terminal"
6633            );
6634            let got = base_map_hash(&ge, root);
6635
6636            assert_eq!(
6637                got, want,
6638                "scan recipe re-emit base map == imperative scan base map"
6639            );
6640        }
6641
6642        // ---- The View / output_views bundle half (B1) ----------------------
6643        //
6644        // A scan recipe whose root is an `Op::View{slot}` must re-emit the scan,
6645        // attach the 2-slot `output_views` bundle (slot 0 = stacked ys, slot 1 =
6646        // final carry — the SelectiveScan bundle contract), and project the
6647        // slot — round-tripping to the same base map AND the correct slot shape
6648        // as the imperative scan+view. Real shapes throughout (init/const via
6649        // Binds carry real shapes; `body_y = Mul(const, sum)` takes the const's
6650        // shape) so the emitted bundle is byte-faithful.
6651
6652        /// `view(slot)(scan(n_xs=1, bound=3, emit=All))` over binds `[init[2],
6653        /// xs0[3,2], const[2]]`. `body_y = Mul(const, sum)` is const-first so its
6654        /// re-emitted shape is `[2]` (the const's), making slot 0 = `[3,2]` and
6655        /// slot 1 = `[2]`.
6656        fn view_scan_recipe(slot: u32) -> PatternNode {
6657            let sum = || {
6658                op_node(
6659                    OpTag::Add,
6660                    OpAttrs::default(),
6661                    vec![
6662                        placeholder(SCAN_ROLE_CARRY, 0),
6663                        placeholder(SCAN_ROLE_ELEM, 0),
6664                    ],
6665                )
6666            };
6667            let scan = PatternNode::Op {
6668                op: OpTag::Scan,
6669                attrs: OpAttrs {
6670                    scan_n_carries: Some(1),
6671                    scan_n_xs: Some(1),
6672                    scan_bound: Some(3),
6673                    scan_emit: Some(0),
6674                    scan_early_exit: None,
6675                    ..OpAttrs::default()
6676                },
6677                operands: vec![
6678                    bind(0),                                                       // init_carry [2]
6679                    bind(1),                                                       // xs0 [3,2]
6680                    bind(2),                                                       // const [2]
6681                    sum(),                                                         // body_new_carry
6682                    op_node(OpTag::Mul, OpAttrs::default(), vec![bind(2), sum()]), // body_y = Mul(const, sum)
6683                ],
6684            };
6685            op_node(
6686                OpTag::View,
6687                OpAttrs {
6688                    view_slot: Some(slot),
6689                    ..OpAttrs::default()
6690                },
6691                vec![scan],
6692            )
6693        }
6694
6695        /// The imperative reference: the scan + its composed 2-slot bundle + the
6696        /// slot projection (mirrors `NodeHandle::scan` + `Graph::view`).
6697        fn reference_view(g: &mut Graph, slot: u32) -> NodeId {
6698            let two = Shape::from_dims(&[2]);
6699            let init = g.push(Node {
6700                op: Op::Const,
6701                inputs: vec![],
6702                shape: two.clone(),
6703                dtype: DType::F32,
6704            });
6705            let xs0 = g.push(Node {
6706                op: Op::Const,
6707                inputs: vec![],
6708                shape: Shape::from_dims(&[3, 2]),
6709                dtype: DType::F32,
6710            });
6711            let konst = g.push(Node {
6712                op: Op::Const,
6713                inputs: vec![],
6714                shape: two.clone(),
6715                dtype: DType::F32,
6716            });
6717            let carry = g.push(Node {
6718                op: Op::ScanPlaceholder {
6719                    role: ScanRole::Carry,
6720                    index: 0,
6721                },
6722                inputs: vec![],
6723                shape: two.clone(),
6724                dtype: DType::F32,
6725            });
6726            let elem = g.push(Node {
6727                op: Op::ScanPlaceholder {
6728                    role: ScanRole::Elem,
6729                    index: 0,
6730                },
6731                inputs: vec![],
6732                shape: two.clone(),
6733                dtype: DType::F32,
6734            });
6735            let sum = g.push(Node {
6736                op: Op::Add,
6737                inputs: vec![carry, elem],
6738                shape: two.clone(),
6739                dtype: DType::F32,
6740            });
6741            let body_y = g.push(Node {
6742                op: Op::Mul,
6743                inputs: vec![konst, sum],
6744                shape: two.clone(),
6745                dtype: DType::F32,
6746            });
6747            let ys = Shape::from_dims(&[3, 2]);
6748            let scan = g.push(Node {
6749                op: Op::Scan {
6750                    n_carries: 1,
6751                    n_xs: 1,
6752                    bound: 3,
6753                    emit: ScanEmit::All,
6754                    early_exit: None,
6755                },
6756                inputs: vec![init, xs0, konst, sum, body_y],
6757                shape: ys.clone(),
6758                dtype: DType::F32,
6759            });
6760            let specs = vec![
6761                OutputViewSpec::contiguous(DType::F32, ys),
6762                OutputViewSpec::contiguous(DType::F32, two),
6763            ];
6764            let (_bytes, views) = compose_bundle(&specs).expect("compose_bundle");
6765            g.set_output_views(scan, Arc::from(views.into_boxed_slice()))
6766                .expect("set_output_views");
6767            let (sh, dt) = {
6768                let v = g.output_views(scan).expect("bundle set");
6769                let s = &v[slot as usize];
6770                (s.shape.clone(), s.dtype)
6771            };
6772            g.push(Node {
6773                op: Op::View { slot },
6774                inputs: vec![scan],
6775                shape: sh,
6776                dtype: dt,
6777            })
6778        }
6779
6780        fn roundtrip_view(slot: u32, want_shape: &[usize]) {
6781            // Reference.
6782            let mut gr = Graph::new();
6783            let ref_view = reference_view(&mut gr, slot);
6784            let want = base_map_hash(&gr, ref_view);
6785
6786            // Emitted from the recipe onto identical leaves.
6787            let mut ge = Graph::new();
6788            let two = Shape::from_dims(&[2]);
6789            let init = ge.push(Node {
6790                op: Op::Const,
6791                inputs: vec![],
6792                shape: two.clone(),
6793                dtype: DType::F32,
6794            });
6795            let xs0 = ge.push(Node {
6796                op: Op::Const,
6797                inputs: vec![],
6798                shape: Shape::from_dims(&[3, 2]),
6799                dtype: DType::F32,
6800            });
6801            let konst = ge.push(Node {
6802                op: Op::Const,
6803                inputs: vec![],
6804                shape: two,
6805                dtype: DType::F32,
6806            });
6807            let root = emit_region(&mut ge, &view_scan_recipe(slot), &[init, xs0, konst], &[]);
6808
6809            // The View projects the requested slot with the bundle-derived shape.
6810            assert!(
6811                matches!(ge.node(root).op, Op::View { slot: s } if s == slot),
6812                "recipe root re-emits Op::View{{slot={slot}}}"
6813            );
6814            assert_eq!(
6815                ge.node(root).shape.dims(),
6816                want_shape,
6817                "view slot {slot} shape comes from the re-attached output_views bundle"
6818            );
6819            // The producing scan carries the faithful 2-slot bundle.
6820            let producer = ge.node(root).inputs[0];
6821            assert!(
6822                matches!(ge.node(producer).op, Op::Scan { .. }),
6823                "producer is the scan"
6824            );
6825            assert_eq!(
6826                ge.output_views(producer).map(|v| v.len()),
6827                Some(2),
6828                "emit re-attaches the 2-slot bundle so slot>=1 views resolve"
6829            );
6830
6831            let got = base_map_hash(&ge, root);
6832            assert_eq!(
6833                got, want,
6834                "view+scan recipe re-emit base map == imperative scan+view base map"
6835            );
6836        }
6837
6838        /// Slot 0 (stacked ys): its shape equals the scan node's primary shape,
6839        /// so even the child-fallback would find it — but it must come through
6840        /// the bundle for the layout/offset to be right.
6841        #[test]
6842        fn scan_view_slot0_reemits_to_the_same_base_map() {
6843            roundtrip_view(0, &[3, 2]);
6844        }
6845
6846        /// Slot 1 (final carry / last_state) is the load-bearing case: its shape
6847        /// (`[2]`) lives ONLY in the bundle and differs from the scan's primary
6848        /// (`[3,2]`). `op_key(View) = None` folds the node shape into the hash,
6849        /// so a wrong slot-1 shape would break the round-trip — this proves the
6850        /// bundle is re-attached and read.
6851        #[test]
6852        fn scan_view_slot1_reemits_to_the_same_base_map() {
6853            roundtrip_view(1, &[2]);
6854        }
6855
6856        /// A view recipe validates as a representable region (contiguous binds,
6857        /// every op — incl. `View`/`Scan`/`ScanPlaceholder` — re-emittable).
6858        #[test]
6859        fn view_scan_recipe_validates_as_representable() {
6860            assert_eq!(view_scan_recipe(1).bind_indices(), vec![0, 1, 2]);
6861            assert!(validate_recipe(&view_scan_recipe(1)).is_ok());
6862            // tag_to_op reconstructs the View op from `view_slot`.
6863            assert!(matches!(
6864                tag_to_op(
6865                    OpTag::View,
6866                    &OpAttrs {
6867                        view_slot: Some(1),
6868                        ..OpAttrs::default()
6869                    }
6870                ),
6871                Some(Op::View { slot: 1 }),
6872            ));
6873            assert!(tag_to_op(OpTag::View, &OpAttrs::default()).is_none());
6874        }
6875
6876        /// B2 emit carrier (born-red without it): a `ScanPlaceholder` that
6877        /// declares its per-step shape via `target_shape_rel` emits with THAT
6878        /// shape, not the rank-0 `primitive_shape` fallback. This is
6879        /// load-bearing for the SSM scan recipes — `unroll_scan` clones body
6880        /// interior nodes with their STORED shapes, so a rank-0 placeholder
6881        /// would poison the unrolled body (e.g. `du = Mul(d_t, u_t)` rank-0).
6882        /// Dtype comes from bind 0 (the uniform-dtype scan inputs).
6883        #[test]
6884        fn scan_placeholder_recipe_emits_its_declared_shape() {
6885            use fuel_kernel_seam_types::shape_expr::{Dim, ShapeExpr};
6886            let dims2 = || {
6887                ShapeExpr::Dims(vec![
6888                    Dim::Extent {
6889                        operand: 0,
6890                        axis: 0,
6891                    },
6892                    Dim::Extent {
6893                        operand: 0,
6894                        axis: 1,
6895                    },
6896                ])
6897            };
6898            let ph = |index: u32| PatternNode::Op {
6899                op: OpTag::ScanPlaceholder,
6900                attrs: OpAttrs {
6901                    scan_role: Some(SCAN_ROLE_ELEM),
6902                    scan_index: Some(index),
6903                    target_shape_rel: Some(dims2()),
6904                    ..OpAttrs::default()
6905                },
6906                operands: vec![],
6907            };
6908            // Mul takes operand[0]'s shape, so its shape mirrors the placeholder.
6909            // A `Bind { 0 }` operand supplies the frame the placeholder's Dims
6910            // reference (operand 0) — so the recipe carries a valid bind.
6911            let recipe = op_node(OpTag::Mul, OpAttrs::default(), vec![ph(0), bind(0)]);
6912            assert!(validate_recipe(&recipe).is_ok());
6913            let mut g = Graph::new();
6914            let bind0 = g.push(Node {
6915                op: Op::Const,
6916                inputs: vec![],
6917                shape: Shape::from_dims(&[2, 3]),
6918                dtype: DType::F32,
6919            });
6920            let root = emit_region(&mut g, &recipe, &[bind0], &[]);
6921            let ph_id = g.node(root).inputs[0];
6922            assert!(matches!(
6923                g.node(ph_id).op,
6924                Op::ScanPlaceholder {
6925                    role: ScanRole::Elem,
6926                    index: 0
6927                }
6928            ));
6929            assert_eq!(
6930                g.node(ph_id).shape.dims(),
6931                &[2, 3],
6932                "placeholder emits its declared target_shape_rel shape, not rank-0",
6933            );
6934            assert_eq!(
6935                g.node(ph_id).dtype,
6936                DType::F32,
6937                "placeholder dtype = bind 0's"
6938            );
6939            assert_eq!(
6940                g.node(root).shape.dims(),
6941                &[2, 3],
6942                "Mul(placeholder,..) inherits the declared shape"
6943            );
6944        }
6945    }
6946
6947    // reduce_max_to_backward migration (Increment C carriers, A2) -------------
6948    //
6949    // The 9-node imperative fair-share max subgradient (ReduceMaxTo→BroadcastTo→
6950    // Equal(U8)→MaskedFill→ReduceSumTo→Div→BroadcastTo→Mul, sharing the single
6951    // `mask_f`) becomes a portable `PatternNode` DATA recipe, and the FIRST recipe
6952    // to carry a `MaskedFill` — driven LIVE by the A2 fill-Scalar re-emit carrier
6953    // (the fill dtype re-resolves to x's dtype, matching `Scalar::one(dtype)`).
6954    // A DIRECT structural mirror (no D3 keepdim swap, no D4 pad), so the parity
6955    // oracle runs both sides through a toy f64 interpreter and asserts BIT-EXACT
6956    // structure — the discriminating power is confirmed by the sabotage-calibrated
6957    // negative in `reduce_max_to_backward_recipe_shares_mask_and_has_no_reshape`
6958    // (identity-share ⇒ ONE MaskedFill node across its two uses). Binds:
6959    // `0 = x`, `1 = up`. The reduce/count targets are `SameAs 1` (upstream's
6960    // shape); the broadcasts are `SameAs 0` (x's shape).
6961    mod reduce_max_to_backward_recipe {
6962        use super::super::*;
6963        use super::frozen_legacy_reduce_max_to_backward_decompose;
6964        use crate::registry::{FusedOps, reduce_max_to_backward};
6965        use fuel_ir::{DType, Shape};
6966        use std::collections::HashMap;
6967
6968        /// Right-aligned NumPy broadcast of `input` (shape `in_shape`) to
6969        /// `target` — a size-1 or padded leading dim contributes stride 0. General
6970        /// on purpose (both `bcast_x` sites tile a `[..,1]` keepdim back to x).
6971        fn broadcast(input: &[f64], in_shape: &[usize], target: &[usize]) -> Vec<f64> {
6972            let rank = target.len();
6973            let pad = rank - in_shape.len();
6974            let mut real_strides = vec![0isize; in_shape.len()];
6975            let mut s = 1isize;
6976            for i in (0..in_shape.len()).rev() {
6977                real_strides[i] = s;
6978                s *= in_shape[i] as isize;
6979            }
6980            let mut in_strides = vec![0isize; rank];
6981            for (i, stride) in in_strides.iter_mut().enumerate() {
6982                if i >= pad {
6983                    let id = i - pad;
6984                    *stride = if in_shape[id] == 1 {
6985                        0
6986                    } else {
6987                        real_strides[id]
6988                    };
6989                }
6990            }
6991            let out_n: usize = target.iter().product();
6992            let mut out = Vec::with_capacity(out_n);
6993            let mut idx = vec![0usize; rank];
6994            for _ in 0..out_n {
6995                let fi: isize = (0..rank).map(|i| idx[i] as isize * in_strides[i]).sum();
6996                out.push(input[fi as usize]);
6997                for i in (0..rank).rev() {
6998                    idx[i] += 1;
6999                    if idx[i] < target[i] {
7000                        break;
7001                    }
7002                    idx[i] = 0;
7003                }
7004            }
7005            out
7006        }
7007
7008        /// Tiny f64 reference interpreter over the reduce-max-backward primitive
7009        /// vocabulary (leaf-lookup FIRST, then elementwise `Mul`/`Div`,
7010        /// `MulScalar`, `Equal` (1.0/0.0), the `MaskedFill` fill from the op's
7011        /// `Scalar`, last-axis `ReduceMaxTo`/`ReduceSumTo`, right-aligned
7012        /// `BroadcastTo`). BOTH parity sides run through it with identical in-order
7013        /// arithmetic, so a bit-exact assert isolates recipe STRUCTURE. Test dims
7014        /// reduce the LAST axis (`up = [.., 1]`), so the reduces are a last-axis
7015        /// chunk fold.
7016        fn eval_rmb(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
7017            if let Some(v) = leaves.get(&id) {
7018                return v.clone();
7019            }
7020            let node = g.node(id);
7021            match &node.op {
7022                Op::Mul => {
7023                    let a = eval_rmb(g, node.inputs[0], leaves);
7024                    let b = eval_rmb(g, node.inputs[1], leaves);
7025                    a.iter().zip(&b).map(|(x, y)| x * y).collect()
7026                }
7027                Op::Div => {
7028                    let a = eval_rmb(g, node.inputs[0], leaves);
7029                    let b = eval_rmb(g, node.inputs[1], leaves);
7030                    a.iter().zip(&b).map(|(x, y)| x / y).collect()
7031                }
7032                Op::MulScalar(m) => eval_rmb(g, node.inputs[0], leaves)
7033                    .iter()
7034                    .map(|v| v * m)
7035                    .collect(),
7036                Op::Equal => {
7037                    let a = eval_rmb(g, node.inputs[0], leaves);
7038                    let b = eval_rmb(g, node.inputs[1], leaves);
7039                    a.iter()
7040                        .zip(&b)
7041                        .map(|(x, y)| if x == y { 1.0 } else { 0.0 })
7042                        .collect()
7043                }
7044                Op::MaskedFill { value } => {
7045                    let input = eval_rmb(g, node.inputs[0], leaves);
7046                    let mask = eval_rmb(g, node.inputs[1], leaves);
7047                    let fill = value.to_f64();
7048                    input
7049                        .iter()
7050                        .zip(&mask)
7051                        .map(|(x, m)| if *m != 0.0 { fill } else { *x })
7052                        .collect()
7053                }
7054                // Last-axis reduce-to-shape (up = [.., 1]); identical fold both
7055                // parity sides.
7056                Op::ReduceMaxTo(_) => {
7057                    let input = eval_rmb(g, node.inputs[0], leaves);
7058                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
7059                    input
7060                        .chunks(last)
7061                        .map(|row| row.iter().cloned().fold(f64::NEG_INFINITY, f64::max))
7062                        .collect()
7063                }
7064                Op::ReduceSumTo(_) => {
7065                    let input = eval_rmb(g, node.inputs[0], leaves);
7066                    let last = *g.node(node.inputs[0]).shape.dims().last().unwrap();
7067                    input
7068                        .chunks(last)
7069                        .map(|row| row.iter().sum::<f64>())
7070                        .collect()
7071                }
7072                Op::BroadcastTo(target) => {
7073                    let input = eval_rmb(g, node.inputs[0], leaves);
7074                    let in_shape: Vec<usize> = g.node(node.inputs[0]).shape.dims().to_vec();
7075                    broadcast(&input, &in_shape, target.dims())
7076                }
7077                other => panic!("eval_rmb: unhandled op {other:?}"),
7078            }
7079        }
7080
7081        /// Build a fused ReduceMaxToBackward node over `x [x_dims]` (input 0) and
7082        /// `up [up_dims]` (input 1, the upstream gradient = the forward reduce
7083        /// target). Returns `(x, up, fused)`.
7084        fn rmb_fused_node(
7085            g: &mut Graph,
7086            x_dims: &[usize],
7087            up_dims: &[usize],
7088        ) -> (NodeId, NodeId, NodeId) {
7089            let xsh = Shape::from_dims(x_dims);
7090            let x = g.push(Node {
7091                op: Op::Const,
7092                inputs: vec![],
7093                shape: xsh.clone(),
7094                dtype: DType::F32,
7095            });
7096            let up = g.push(Node {
7097                op: Op::Const,
7098                inputs: vec![],
7099                shape: Shape::from_dims(up_dims),
7100                dtype: DType::F32,
7101            });
7102            let fused = g.push(Node {
7103                op: Op::Fused(
7104                    FusedOps::REDUCE_MAX_TO_BACKWARD,
7105                    FusedOpParams::ReduceMaxToBackward,
7106                ),
7107                inputs: vec![x, up],
7108                shape: xsh,
7109                dtype: DType::F32,
7110            });
7111            (x, up, fused)
7112        }
7113
7114        /// A2 (a): ONE recipe datum decomposes at BOTH rank 2 and rank 3 (the
7115        /// shape polymorphism the baked-shape legacy body never had), and its
7116        /// numerics match the FROZEN legacy builder bit-exactly under the shared
7117        /// reference interpreter — the MaskedFill carrier driving live emission.
7118        #[test]
7119        fn reduce_max_to_backward_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
7120            for (x_dims, up_dims) in [
7121                (vec![2usize, 4], vec![2usize, 1]),
7122                (vec![3, 5, 7], vec![3, 5, 1]),
7123            ] {
7124                let mut g = Graph::new();
7125                let (x, up, fused) = rmb_fused_node(&mut g, &x_dims, &up_dims);
7126                let xsh = Shape::from_dims(&x_dims);
7127                let new_root = reduce_max_to_backward::decompose(
7128                    &mut g,
7129                    fused,
7130                    &FusedOpParams::ReduceMaxToBackward,
7131                );
7132                assert_ne!(new_root, fused, "recipe decompose must fire at {x_dims:?}");
7133                assert_eq!(
7134                    g.node(new_root).shape,
7135                    xsh,
7136                    "reduce_max backward is x-shaped"
7137                );
7138                assert_eq!(g.node(new_root).dtype, DType::F32);
7139
7140                let legacy_root = frozen_legacy_reduce_max_to_backward_decompose(
7141                    &mut g,
7142                    fused,
7143                    &FusedOpParams::ReduceMaxToBackward,
7144                );
7145
7146                let xn: usize = x_dims.iter().product();
7147                let upn: usize = up_dims.iter().product();
7148                let x_data: Vec<f64> = (0..xn)
7149                    .map(|i| ((i as f64) * 0.37).sin() * 2.0 + 0.3)
7150                    .collect();
7151                let up_data: Vec<f64> = (0..upn)
7152                    .map(|i| ((i as f64) * 0.53).cos() * 1.1 - 0.2)
7153                    .collect();
7154                let mut leaves = HashMap::new();
7155                leaves.insert(x, x_data);
7156                leaves.insert(up, up_data);
7157                let got = eval_rmb(&g, new_root, &leaves);
7158                let want = eval_rmb(&g, legacy_root, &leaves);
7159                assert_eq!(got.len(), want.len());
7160                for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
7161                    assert_eq!(
7162                        a.to_bits(),
7163                        b.to_bits(),
7164                        "reduce_max_to_backward[{i}] at {x_dims:?}: recipe={a} vs legacy={b}",
7165                    );
7166                }
7167            }
7168        }
7169
7170        /// A2 (structural): the single `mask_f` is IDENTITY-SHARED across its two
7171        /// use sites (`ReduceSumTo` and the final `Mul`) — exactly ONE MaskedFill
7172        /// node reachable, the same single-compute DAG the imperative body had (a
7173        /// duplicated emit would break this). A direct structural mirror: no D3/D4
7174        /// `Reshape`, and the MaskedFill fill Scalar re-resolved to x's (F32)
7175        /// dtype (evidence the A2 carrier reached emit).
7176        #[test]
7177        fn reduce_max_to_backward_recipe_shares_mask_and_has_no_reshape() {
7178            for (x_dims, up_dims) in [
7179                (vec![2usize, 4], vec![2usize, 1]),
7180                (vec![3, 5, 7], vec![3, 5, 1]),
7181            ] {
7182                let mut g = Graph::new();
7183                let (_x, _up, fused) = rmb_fused_node(&mut g, &x_dims, &up_dims);
7184                let root = reduce_max_to_backward::decompose(
7185                    &mut g,
7186                    fused,
7187                    &FusedOpParams::ReduceMaxToBackward,
7188                );
7189                assert_ne!(root, fused, "recipe decompose fires at {x_dims:?}");
7190                assert!(
7191                    matches!(g.node(root).op, Op::Mul),
7192                    "root is Mul(mask_f, share_b)"
7193                );
7194                let reachable = crate::topo_order_multi(&g, &[root]);
7195                let masked = reachable
7196                    .iter()
7197                    .filter(|&&n| matches!(g.node(n).op, Op::MaskedFill { .. }))
7198                    .count();
7199                assert_eq!(
7200                    masked, 1,
7201                    "mask_f identity-shared across its two uses at {x_dims:?} (ONE MaskedFill)",
7202                );
7203                let reshapes = reachable
7204                    .iter()
7205                    .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
7206                    .count();
7207                assert_eq!(
7208                    reshapes, 0,
7209                    "direct structural mirror — no D3/D4 Reshape at {x_dims:?}"
7210                );
7211                let fill_dtype = reachable
7212                    .iter()
7213                    .find_map(|&n| match &g.node(n).op {
7214                        Op::MaskedFill { value } => Some(value.dtype()),
7215                        _ => None,
7216                    })
7217                    .expect("a MaskedFill is present");
7218                assert_eq!(
7219                    fill_dtype,
7220                    DType::F32,
7221                    "the A2 carrier re-resolved the fill Scalar to x's dtype at {x_dims:?}",
7222                );
7223            }
7224        }
7225
7226        /// A2 (totality): a wrong params payload is a typed decline surfaced as a
7227        /// fixpoint (G2), never a panic, declining BEFORE any emission.
7228        #[test]
7229        fn reduce_max_to_backward_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
7230            let mut g = Graph::new();
7231            let (_x, _up, fused) = rmb_fused_node(&mut g, &[2, 4], &[2, 1]);
7232            let before = g.len();
7233            let out = reduce_max_to_backward::decompose(&mut g, fused, &FusedOpParams::Rope);
7234            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
7235            assert_eq!(g.len(), before, "declined before any emission");
7236        }
7237    }
7238
7239    // powi_backward migration (Increment C carriers, A3) ----------------------
7240    //
7241    // The 3-node imperative closed-form gradient `grad_x = exp · x^(exp-1) ·
7242    // upstream` (PowI(exp-1)→MulScalar(exp)→Mul) becomes a portable `PatternNode`
7243    // DATA recipe, and the FIRST recipe to carry a `PowI` — driven LIVE by the A3
7244    // i32-exponent re-emit carrier (the exponent rides scalars[0]) — and the first
7245    // whose STRUCTURE is param-derived (both `exp` and `exp-1` are constant-folded
7246    // into the datum at build time, the minimal "param-derived const in the
7247    // recipe" C-4 posture, NOT a restructure to dodge the missing thread). A
7248    // DIRECT structural mirror (no D3 keepdim swap, no D4 pad), so the parity
7249    // oracle runs both sides through a toy f64 interpreter and asserts BIT-EXACT
7250    // structure across ranks AND exponents. Binds: `0 = x`, `1 = up`.
7251    mod powi_backward_recipe {
7252        use super::super::*;
7253        use super::frozen_legacy_powi_backward_decompose;
7254        use crate::registry::{FusedOps, powi_backward};
7255        use fuel_ir::{DType, Shape};
7256        use std::collections::HashMap;
7257
7258        /// Tiny f64 reference interpreter over the powi-backward primitive
7259        /// vocabulary (leaf-lookup FIRST, then elementwise `Mul`, `MulScalar`,
7260        /// and integer-power `PowI` via `f64::powi`). BOTH parity sides run
7261        /// through it with identical in-order arithmetic, so a bit-exact assert
7262        /// isolates recipe STRUCTURE — the exponent transported by the A3 carrier
7263        /// and the baked `MulScalar`. `x` and `up` are the same shape (`PowI` is
7264        /// elementwise), so every op is a flat elementwise map.
7265        fn eval_pb(g: &Graph, id: NodeId, leaves: &HashMap<NodeId, Vec<f64>>) -> Vec<f64> {
7266            if let Some(v) = leaves.get(&id) {
7267                return v.clone();
7268            }
7269            let node = g.node(id);
7270            match &node.op {
7271                Op::Mul => {
7272                    let a = eval_pb(g, node.inputs[0], leaves);
7273                    let b = eval_pb(g, node.inputs[1], leaves);
7274                    a.iter().zip(&b).map(|(x, y)| x * y).collect()
7275                }
7276                Op::MulScalar(m) => eval_pb(g, node.inputs[0], leaves)
7277                    .iter()
7278                    .map(|v| v * m)
7279                    .collect(),
7280                Op::PowI(n) => eval_pb(g, node.inputs[0], leaves)
7281                    .iter()
7282                    .map(|v| v.powi(*n))
7283                    .collect(),
7284                other => panic!("eval_pb: unhandled op {other:?}"),
7285            }
7286        }
7287
7288        /// Build a fused PowIBackward node over `x [dims]` (input 0) and `up
7289        /// [dims]` (input 1, the upstream gradient — same shape as x), carrying
7290        /// `exp`. Returns `(x, up, fused)`.
7291        fn pb_fused_node(g: &mut Graph, dims: &[usize], exp: i32) -> (NodeId, NodeId, NodeId) {
7292            let sh = Shape::from_dims(dims);
7293            let x = g.push(Node {
7294                op: Op::Const,
7295                inputs: vec![],
7296                shape: sh.clone(),
7297                dtype: DType::F32,
7298            });
7299            let up = g.push(Node {
7300                op: Op::Const,
7301                inputs: vec![],
7302                shape: sh.clone(),
7303                dtype: DType::F32,
7304            });
7305            let fused = g.push(Node {
7306                op: Op::Fused(FusedOps::POWI_BACKWARD, FusedOpParams::PowIBackward { exp }),
7307                inputs: vec![x, up],
7308                shape: sh,
7309                dtype: DType::F32,
7310            });
7311            (x, up, fused)
7312        }
7313
7314        /// A3 (a): ONE recipe datum decomposes at BOTH rank 2 and rank 3 and
7315        /// across several exponents, and its numerics match the FROZEN legacy
7316        /// builder bit-exactly under the shared reference interpreter — the A3
7317        /// PowI carrier driving live emission of `Op::PowI(exp-1)`.
7318        #[test]
7319        fn powi_backward_recipe_decompose_is_polymorphic_and_matches_frozen_legacy() {
7320            // x strictly positive so exp==0's PowI(-1) reciprocal is finite/clean;
7321            // the exponents span +, the exp==1 identity, exp==0 (MulScalar(0)=0),
7322            // and a negative exponent (PowI(-3)).
7323            for dims in [vec![2usize, 4], vec![3, 5, 7]] {
7324                for exp in [3i32, 2, 5, 1, 0, -2] {
7325                    let mut g = Graph::new();
7326                    let (x, up, fused) = pb_fused_node(&mut g, &dims, exp);
7327                    let sh = Shape::from_dims(&dims);
7328                    let new_root = powi_backward::decompose(
7329                        &mut g,
7330                        fused,
7331                        &FusedOpParams::PowIBackward { exp },
7332                    );
7333                    assert_ne!(
7334                        new_root, fused,
7335                        "recipe decompose must fire at {dims:?} exp={exp}"
7336                    );
7337                    assert_eq!(
7338                        g.node(new_root).shape,
7339                        sh,
7340                        "powi backward is shape-preserving"
7341                    );
7342                    assert_eq!(g.node(new_root).dtype, DType::F32);
7343
7344                    let legacy_root = frozen_legacy_powi_backward_decompose(
7345                        &mut g,
7346                        fused,
7347                        &FusedOpParams::PowIBackward { exp },
7348                    );
7349
7350                    let n: usize = dims.iter().product();
7351                    let x_data: Vec<f64> = (0..n)
7352                        .map(|i| ((i as f64) * 0.41).sin() * 0.5 + 1.5)
7353                        .collect();
7354                    let up_data: Vec<f64> = (0..n)
7355                        .map(|i| ((i as f64) * 0.61).cos() * 1.3 - 0.15)
7356                        .collect();
7357                    let mut leaves = HashMap::new();
7358                    leaves.insert(x, x_data);
7359                    leaves.insert(up, up_data);
7360                    let got = eval_pb(&g, new_root, &leaves);
7361                    let want = eval_pb(&g, legacy_root, &leaves);
7362                    assert_eq!(got.len(), want.len());
7363                    for (i, (a, b)) in got.iter().zip(want.iter()).enumerate() {
7364                        assert_eq!(
7365                            a.to_bits(),
7366                            b.to_bits(),
7367                            "powi_backward[{i}] at {dims:?} exp={exp}: recipe={a} vs legacy={b}",
7368                        );
7369                    }
7370                }
7371            }
7372        }
7373
7374        /// A3 (structural): a DIRECT structural mirror — the root is `Mul(scaled,
7375        /// up)`, the A3 carrier resolves the exponent to `Op::PowI(exp-1)`, the
7376        /// scale is a baked `Op::MulScalar(exp as f64)`, and NO D3/D4 `Reshape` is
7377        /// materialized (evidence the i32 carrier + baked scale reached emit at the
7378        /// right values).
7379        #[test]
7380        fn powi_backward_recipe_is_a_direct_mirror_with_carrier_exponent() {
7381            for exp in [3i32, 0, -2] {
7382                let mut g = Graph::new();
7383                let (_x, _up, fused) = pb_fused_node(&mut g, &[2, 4], exp);
7384                let root =
7385                    powi_backward::decompose(&mut g, fused, &FusedOpParams::PowIBackward { exp });
7386                assert_ne!(root, fused, "recipe decompose fires at exp={exp}");
7387                assert!(
7388                    matches!(g.node(root).op, Op::Mul),
7389                    "root is Mul(scaled, up)"
7390                );
7391                let reachable = crate::topo_order_multi(&g, &[root]);
7392                // The A3 carrier reconstructed exactly one Op::PowI(exp-1).
7393                let powis: Vec<i32> = reachable
7394                    .iter()
7395                    .filter_map(|&n| match g.node(n).op {
7396                        Op::PowI(k) => Some(k),
7397                        _ => None,
7398                    })
7399                    .collect();
7400                assert_eq!(
7401                    powis,
7402                    vec![exp - 1],
7403                    "the A3 carrier resolved Op::PowI(exp-1) at exp={exp}"
7404                );
7405                // The scale is a single baked MulScalar(exp as f64).
7406                let mul_scalars: Vec<f64> = reachable
7407                    .iter()
7408                    .filter_map(|&n| match g.node(n).op {
7409                        Op::MulScalar(v) => Some(v),
7410                        _ => None,
7411                    })
7412                    .collect();
7413                assert_eq!(
7414                    mul_scalars,
7415                    vec![exp as f64],
7416                    "baked MulScalar(exp) at exp={exp}"
7417                );
7418                // Direct mirror — no keepdim/pad Reshape.
7419                let reshapes = reachable
7420                    .iter()
7421                    .filter(|&&n| matches!(g.node(n).op, Op::Reshape(_)))
7422                    .count();
7423                assert_eq!(
7424                    reshapes, 0,
7425                    "direct structural mirror — no D3/D4 Reshape at exp={exp}"
7426                );
7427            }
7428        }
7429
7430        /// A3 (totality): a wrong params payload is a typed decline surfaced as a
7431        /// fixpoint (G2), never a panic, declining BEFORE any emission.
7432        #[test]
7433        fn powi_backward_recipe_wrong_params_is_a_fixpoint_not_a_crash() {
7434            let mut g = Graph::new();
7435            let (_x, _up, fused) = pb_fused_node(&mut g, &[2, 4], 3);
7436            let before = g.len();
7437            let out = powi_backward::decompose(&mut g, fused, &FusedOpParams::Rope);
7438            assert_eq!(out, fused, "wrong params ⇒ typed decline ⇒ fixpoint");
7439            assert_eq!(g.len(), before, "declined before any emission");
7440        }
7441    }
7442}