fuel_dispatch/runtime_fused_kernels.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Runtime fused-op kernel registration — the **adopt/gate facade** over the
3//! ONE binding registry.
4//!
5//! The transitional `FusedOpId`-keyed kernel *sidecar* that used to live here
6//! is **folded** (the 2026-07-08 decisions-log end-state): a runtime kernel now
7//! registers into the global binding table
8//! ([`crate::dispatch::extend_global_bindings`]) under
9//! [`BindingKey::RuntimeFused`], exactly like a startup kernel registers under
10//! `BindingKey::Static` — same table, same entry shape, same dtype-keyed
11//! dispatch lookup. This module keeps the runtime-op-shaped API
12//! ([`adopt_runtime_fused`], [`fused_kernel_available`],
13//! [`lookup_runtime_kernel`]) so callers stay id-oriented, but there is no
14//! second registry behind it.
15//!
16//! Cost note: a runtime row is stored with the binding-native `unknown_cost`
17//! sentinel (never a lying zero). Deriving its Layer-1 estimate from the op's
18//! recipe (`decompose_region`, keyed by the `RuntimeFused` id in the binding
19//! key — no stored closure needed) is the plan-path pricing follow-up; today a
20//! runtime arm is sparse-skip unpriced there, which is safe (arm 0 stays the
21//! runnability fallback).
22
23use fuel_graph::jit::PatternNode;
24use fuel_graph::registry::{FusedOpId, FusedOps};
25use fuel_ir::DType;
26use fuel_ir::dispatch::OpKind;
27use fuel_ir::probe::BackendId;
28
29use crate::dispatch::{extend_global_bindings, global_bindings};
30use crate::fused::{PrecisionGuarantee, default_kernel_registry};
31use crate::kernel::{BindingKey, KernelBindingTable, KernelCaps, KernelRef};
32
33/// A runtime-fused binding row, as [`lookup_runtime_kernel`] returns it — a
34/// diagnostic/test view (dispatch resolves dtype-precisely through the binding
35/// table itself; see `compile_one`'s `is_runtime` arm).
36#[derive(Clone, Debug)]
37pub struct RuntimeKernelBinding {
38 pub kernel: KernelRef,
39 pub caps: KernelCaps,
40 pub precision: PrecisionGuarantee,
41 /// The per-operand dtype tuple the kernel registered for (inputs in
42 /// order, then output) — the binding key's dtype half.
43 pub dtypes: Vec<DType>,
44}
45
46/// Bind a kernel for a runtime fused op (id `>= RUNTIME_FUSED_BASE`) into the
47/// global binding table under `BindingKey::RuntimeFused(id)`. `dtypes` is the
48/// per-operand tuple (inputs in order, then output) — the same key shape every
49/// static registration uses.
50pub fn register_runtime_kernel(
51 id: FusedOpId,
52 dtypes: &[DType],
53 backend: BackendId,
54 kernel: KernelRef,
55) {
56 extend_global_bindings(|t| t.register(BindingKey::RuntimeFused(id), dtypes, backend, kernel));
57}
58
59/// **TEST-ONLY.** Table-scoped sibling of [`register_runtime_kernel`] —
60/// binds into a caller-owned `table` instead of the global binding table, so
61/// a test can construct a `KernelBindingTable` carrying a runtime-fused row
62/// without touching (or being affected by) the global sidecar every other
63/// test in this binary shares. `#[doc(hidden)] pub`, not `#[cfg(test)]`, for
64/// the same reason as [`clear_runtime_fused_for_tests`]: integration tests
65/// compile this crate without `cfg(test)`.
66#[doc(hidden)]
67pub fn register_runtime_kernel_into(
68 table: &mut KernelBindingTable,
69 id: FusedOpId,
70 dtypes: &[DType],
71 backend: BackendId,
72 kernel: KernelRef,
73) {
74 table.register(BindingKey::RuntimeFused(id), dtypes, backend, kernel);
75}
76
77/// Why a runtime-fused lookup returned no kernel. **Two distinct facts, kept
78/// distinct:** an `Option` would make them indistinguishable, and they have
79/// different fixes — *not bound* means register one, *ambiguous* means say
80/// which signature you want.
81#[derive(Clone, Debug, PartialEq, Eq)]
82pub enum FusedLookupMiss {
83 /// Nothing is registered for this `(id, backend)`.
84 NotBound,
85 /// Several dtype tuples share this id, so no single kernel answers the
86 /// question. A [`FusedOpId`] names a RECIPE, not a signature.
87 Ambiguous { signatures: Vec<Vec<DType>> },
88}
89
90/// The runtime-fused binding for `(id, backend)` when exactly one dtype tuple
91/// is registered.
92///
93/// **Refuses to guess.** One `FusedOpId` can legitimately carry several dtype
94/// tuples (`register_runtime_fused` dedups on the recipe's base-map hash), and
95/// resolving by id alone would pick between them by `HashMap` order — a coin
96/// flip per process, capable of returning a kernel of the wrong operand arity.
97/// This is the diagnostic/test path; production dispatch takes the dtype tuple
98/// and does an exact keyed lookup (`lookup_with_caps`). See GAP-213.
99pub fn lookup_runtime_kernel(
100 id: FusedOpId,
101 backend: BackendId,
102) -> Result<RuntimeKernelBinding, FusedLookupMiss> {
103 use crate::kernel::RuntimeFusedLookup;
104 let table = global_bindings();
105 match table.unique_runtime_fused(id, backend) {
106 RuntimeFusedLookup::Unique(dtypes, e) => Ok(RuntimeKernelBinding {
107 kernel: e.kernel,
108 caps: e.caps,
109 precision: e.precision,
110 dtypes: dtypes.to_vec(),
111 }),
112 RuntimeFusedLookup::NotBound => Err(FusedLookupMiss::NotBound),
113 RuntimeFusedLookup::Ambiguous { signatures } => {
114 Err(FusedLookupMiss::Ambiguous { signatures })
115 }
116 }
117}
118
119/// The capability predicate the optimizer's gate consults: is there an
120/// admissible kernel for `(id, backend)` — static **or** runtime-registered?
121/// The dispatch layer passes `|id| fused_kernel_available(id, backend)` to
122/// `capability_gated_rules`, so a runtime op fuses only once its kernel is
123/// bound. Id-level (coarse) by design; the dtype-precise check is the
124/// dispatch-time binding lookup itself.
125///
126/// **SHARED-CONSUMER COORDINATION (dd-shapes / flash-arm registry seam,
127/// 2026-07-08; carried through the binding-key fold).** Two independent
128/// callers with different invariants, and any change here must hold both:
129/// - the **Tier-2 runtime-fusion / JIT program** calls this for *runtime* ids
130/// (`id.is_runtime()`, allocated by [`adopt_runtime_fused`]) — those resolve
131/// only through their `BindingKey::RuntimeFused` rows
132/// (`KernelBindingTable::has_runtime_fused`), never the static registry and
133/// never the static-id bridge below (which returns `false` immediately for
134/// a runtime id);
135/// - the **decode flash-arm gate**
136/// (`crate::decode_flash::FlashArmCapability::production`) calls this for
137/// the *static* id `FusedOps::FLASH_ATTN` on `BackendId::Cuda`, whose CUDA
138/// `flash_decoding` kernel registers ONLY into the binding table under
139/// `(OpKind::FlashAttn, [f16|bf16;4], Cuda)` — never
140/// [`default_kernel_registry`] (a frozen CPU-only `OnceLock`). The
141/// `static_binding_table_bridge` arm makes that reachable: for a static id
142/// with a known `OpKind` mapping, consult the binding table for ANY entry on
143/// `backend` (dtype-blind — this predicate answers "is *a* kernel bound";
144/// dtype admissibility is a separate gate, e.g.
145/// `crate::decode_flash::flash_decode_admissible`).
146///
147/// **LOCK DISCIPLINE (post binding-key fold, 2026-07-08; nesting eliminated
148/// 2026-07-13 — Spec B Task 1).** This re-acquires the `global_bindings()`
149/// READ lock (once per `||` operand it actually evaluates — the checks are
150/// independent, short-lived acquisitions, not one held across the whole
151/// chain). One constraint remains:
152/// - Never call it — nor `adopt_runtime_fused` / `clear_runtime_fused_for_tests`,
153/// which take the WRITE lock — while holding a `global_bindings()` guard on the
154/// same thread. Same-thread read-then-write (or write-then-anything)
155/// self-deadlocks. (Tests scope their read guards around each optimize call and
156/// never hold one across an adopt/reset; production adopts on the G7 *background*
157/// thread, never the realize thread that holds read guards.)
158///
159/// The nested-read hazard this doc used to warn about — the optimizer's
160/// runtime-fusion pathfinder calling this while `optimize_graph` already
161/// holds a *caller-passed* `&KernelBindingTable` derived from a
162/// `global_bindings()` read guard — is now **eliminated**: the pathfinder
163/// calls [`fused_kernel_available_in`] against that already-held table
164/// (threaded in via `OptimizationContext::bindings`) instead of calling this
165/// function and re-acquiring the lock. This function stays the *global*
166/// entry point for callers with no table already in hand (the decode
167/// flash-arm gate, `jit_adopt`'s post-adopt check).
168pub fn fused_kernel_available(id: FusedOpId, backend: BackendId) -> bool {
169 fused_kernel_available_core(
170 id,
171 backend,
172 || global_bindings().has_runtime_fused(id, backend),
173 || static_binding_table_bridge(id, backend),
174 )
175}
176
177/// Table-passing variant of [`fused_kernel_available`] — reads the CALLER'S
178/// binding table (the one `optimize_graph` already holds, threaded via
179/// `OptimizationContext::bindings`) instead of re-acquiring
180/// `global_bindings()`. This is the **non-nesting** gate: a background
181/// adopt's `global_bindings()` WRITE can never queue between two reads this
182/// function takes, because it takes none — `table` is a plain reference the
183/// caller already resolved.
184pub fn fused_kernel_available_in(
185 table: &KernelBindingTable,
186 id: FusedOpId,
187 backend: BackendId,
188) -> bool {
189 fused_kernel_available_core(
190 id,
191 backend,
192 || table.has_runtime_fused(id, backend),
193 || static_binding_table_bridge_in(table, id, backend),
194 )
195}
196
197/// The shared 3-way OR behind [`fused_kernel_available`] and
198/// [`fused_kernel_available_in`]: the (lock-free) default kernel registry,
199/// then a caller-supplied runtime-fused check, then a caller-supplied
200/// static-id bridge check — the latter two as thunks so each public wrapper
201/// controls exactly how (and whether) it touches `global_bindings()`, rather
202/// than this shared core baking in one lock policy for both.
203fn fused_kernel_available_core(
204 id: FusedOpId,
205 backend: BackendId,
206 has_runtime_fused: impl FnOnce() -> bool,
207 bridge: impl FnOnce() -> bool,
208) -> bool {
209 default_kernel_registry().lookup(id, backend).is_some() || has_runtime_fused() || bridge()
210}
211
212/// The static-id half of the [`fused_kernel_available`] bridge (see its doc
213/// comment). Returns `false` immediately for a runtime id — purely additive
214/// for the static-id path; a runtime id resolves only via its
215/// `BindingKey::RuntimeFused` rows. Acquires `global_bindings()` once, for
216/// the duration of the [`static_binding_table_bridge_over`] scan — unchanged
217/// from before this function existed.
218fn static_binding_table_bridge(id: FusedOpId, backend: BackendId) -> bool {
219 static_binding_table_bridge_over(&global_bindings(), id, backend)
220}
221
222/// Table-passing sibling of [`static_binding_table_bridge`] — scans the
223/// caller's `table` instead of `global_bindings()`. See
224/// [`fused_kernel_available_in`].
225fn static_binding_table_bridge_in(
226 table: &KernelBindingTable,
227 id: FusedOpId,
228 backend: BackendId,
229) -> bool {
230 static_binding_table_bridge_over(table, id, backend)
231}
232
233/// The scan shared by [`static_binding_table_bridge`] and
234/// [`static_binding_table_bridge_in`] — the only difference between the two
235/// is which `KernelBindingTable` reference they hand in here.
236fn static_binding_table_bridge_over(
237 table: &KernelBindingTable,
238 id: FusedOpId,
239 backend: BackendId,
240) -> bool {
241 if id.is_runtime() {
242 return false;
243 }
244 let Some(op_kind) = static_fused_id_to_binding_table_op_kind(id) else {
245 return false;
246 };
247 // `iter_keys` is the static-entry view (RuntimeFused rows filtered), which
248 // is exactly the population this bridge should scan.
249 table
250 .iter_keys()
251 .any(|(op, _dtypes, b)| op == op_kind && b == backend)
252}
253
254/// The (deliberately small) set of *static* [`FusedOpId`]s whose kernel is
255/// known to register into the primitive `KernelBindingTable` under a
256/// corresponding [`OpKind`] rather than (only) [`default_kernel_registry`].
257/// See [`fused_kernel_available`]'s doc comment — widen this one id at a
258/// time as each is verified, not speculatively.
259pub(crate) fn static_fused_id_to_binding_table_op_kind(id: FusedOpId) -> Option<OpKind> {
260 if id == FusedOps::FLASH_ATTN {
261 Some(OpKind::FlashAttn)
262 } else if id == FusedOps::AWQ_MATMUL {
263 // CUDA F16 kernel registers under `(OpKind::AwqMatmul, .., Cuda)` from
264 // `kernel-contracts/cuda/awq_matmul.fkc.md` (ROADMAP row 31a).
265 Some(OpKind::AwqMatmul)
266 } else {
267 None
268 }
269}
270
271/// **TEST-ONLY.** Reset the runtime-fused world: drop every
272/// `BindingKey::RuntimeFused` row from the global binding table AND clear the
273/// `fuel-graph` metadata sidecar — together, because clearing metadata restarts
274/// the id allocator and a reused id must never resolve a stale kernel. Bindings
275/// are cleared FIRST so no window exists where a fresh id sees an old row.
276/// Callers in one test binary share the process: serialize with every other
277/// adopting test (a bare reset races — dd-shapes coordination, 2026-07-08).
278/// `#[doc(hidden)] pub`, not `#[cfg(test)]`: integration tests compile this
279/// crate without `cfg(test)`.
280#[doc(hidden)]
281pub fn clear_runtime_fused_for_tests() {
282 extend_global_bindings(|t| t.remove_runtime_fused_for_tests());
283 fuel_graph::runtime_fused::clear_runtime_fused_for_tests();
284}
285
286/// Adopt a synthesized/imported runtime fused op: register its recipe (the
287/// `region`) in the `fuel-graph` metadata sidecar **and** bind its kernel in
288/// the global binding table, then return the freshly-allocated runtime
289/// [`FusedOpId`]. After this the capability gate sees the op as fusable on
290/// `backend`, and the executor's `is_runtime` arm resolves it dtype-precisely
291/// from the same table every static kernel lives in.
292///
293/// Takes the *resolved* parts (the region + the bound `KernelRef`), not a
294/// `JitResponse` — the `JitResponse`/`SynthArtifact` destructuring (+ the
295/// artifact-load step) happens at the seam-call site (`jit_adopt`), so the
296/// core dispatch layer stays free of the `fuel-kernel-seam` envelope crate.
297/// Returns `None` if the region is not registrable (non-decomposable / bad
298/// binds — surfaced by `register_runtime_fused`).
299///
300/// **Idempotent under region dedup (2026-07-15).** `register_runtime_fused`
301/// dedups structurally-identical regions to the SAME [`FusedOpId`] — so a
302/// second `adopt_runtime_fused` call for an already-adopted recipe returns
303/// the same `id` here too. Binding, however, is append-only
304/// ([`KernelBindingTable::register_full_with_source_generic`]): pushing the
305/// *same* `KernelRef` onto the *same* `(id, dtypes, backend)` key a second
306/// time would create a duplicate-identical sibling, which
307/// `KernelBindingTable::finalize` rejects (`Error::Msg`) — and the caller of
308/// `finalize` (`extend_global_bindings`'s `.expect()`) turns that `Err` into
309/// a panic *while holding the global bindings write lock*, poisoning it for
310/// every later caller.
311///
312/// Guard against this exactly at the idempotent-vs-genuinely-new boundary:
313/// before binding, check whether THIS `kernel` is already bound at `(id,
314/// dtypes, backend)` — the precise, dtypes-aware
315/// [`KernelBindingTable::lookup_alternatives`] view — and skip the bind if
316/// so. The check-then-bind runs inside a SINGLE [`extend_global_bindings`]
317/// write-lock critical section (not a separate read-lock check followed by
318/// a separate write-lock bind): two threads racing to adopt the same
319/// structurally-identical region + kernel + dtypes + backend (region dedup
320/// means unrelated call sites can land on the same `id`) would otherwise
321/// both observe "not yet bound" through a read-then-write race window and
322/// both bind, reproducing the duplicate-`KernelRef` panic anyway. A fresh
323/// id, or the same id with a different kernel / dtypes / backend, still
324/// binds exactly as before.
325pub fn adopt_runtime_fused(
326 name: impl Into<String>,
327 region: PatternNode,
328 kernel: KernelRef,
329 dtypes: Vec<DType>,
330 backend: BackendId,
331) -> Option<FusedOpId> {
332 let id = fuel_graph::runtime_fused::register_runtime_fused(name, region).ok()?;
333 extend_global_bindings(|table| {
334 let already_bound = table
335 .lookup_alternatives(BindingKey::RuntimeFused(id), &dtypes, backend)
336 .iter()
337 .any(|entry| std::ptr::fn_addr_eq(entry.kernel, kernel));
338 if !already_bound {
339 table.register(BindingKey::RuntimeFused(id), &dtypes, backend, kernel);
340 }
341 });
342 Some(id)
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use fuel_graph::jit::{OpAttrs, OpTag};
349 use fuel_ir::Layout;
350 use std::sync::{Arc, RwLock as StdRwLock};
351
352 /// A second, distinct `KernelRef` — `adopt_runtime_fused` skips
353 /// registration when the same fn pointer is already bound, so the two
354 /// signatures in the GAP-213 test must not share one.
355 fn other_noop_kernel(
356 _inputs: &[Arc<StdRwLock<fuel_memory::Storage>>],
357 _outputs: &mut [Arc<StdRwLock<fuel_memory::Storage>>],
358 _layouts: &[Layout],
359 _params: &crate::kernel::OpParams,
360 ) -> fuel_ir::Result<()> {
361 Ok(())
362 }
363
364 fn noop_kernel(
365 _inputs: &[Arc<StdRwLock<fuel_memory::Storage>>],
366 _outputs: &mut [Arc<StdRwLock<fuel_memory::Storage>>],
367 _layouts: &[Layout],
368 _params: &crate::kernel::OpParams,
369 ) -> fuel_ir::Result<()> {
370 Ok(())
371 }
372
373 fn relu_add() -> PatternNode {
374 PatternNode::Op {
375 op: OpTag::Relu,
376 attrs: OpAttrs::default(),
377 operands: vec![PatternNode::Op {
378 op: OpTag::Add,
379 attrs: OpAttrs::default(),
380 operands: vec![
381 PatternNode::Bind { index: 0 },
382 PatternNode::Bind { index: 1 },
383 ],
384 }],
385 }
386 }
387
388 /// sqrt(div(a, b)) — deliberately NOT the `relu(add(a, b))` shape every
389 /// other adopted-op test in this crate uses (`fused_cost`, `jit_adopt`,
390 /// `runtime_fused_arm`, and this module's own `relu_add` tests above).
391 /// `register_runtime_fused`'s dedup index is a process-global sidecar
392 /// shared by every `#[test]` in this binary (see
393 /// `runtime_fused_pathfinder`'s `tanh_mul_region` doc comment for the
394 /// full collision rationale): a test asserting on the EXACT kernel
395 /// pointer `lookup_with_caps`/`first_runtime_fused` resolves needs a
396 /// region no other adopting test also registers, or whichever test's
397 /// kernel reaches the shared binding-table key first wins the "first
398 /// alternative" — nondeterministically, by thread scheduling.
399 fn sqrt_div() -> PatternNode {
400 PatternNode::Op {
401 op: OpTag::Sqrt,
402 attrs: OpAttrs::default(),
403 operands: vec![PatternNode::Op {
404 op: OpTag::Div,
405 attrs: OpAttrs::default(),
406 operands: vec![
407 PatternNode::Bind { index: 0 },
408 PatternNode::Bind { index: 1 },
409 ],
410 }],
411 }
412 }
413
414 #[test]
415 fn adopt_registers_op_plus_kernel_and_the_gate_predicate_sees_it() {
416 let id = adopt_runtime_fused(
417 "test::adopt::relu_add",
418 relu_add(),
419 noop_kernel as KernelRef,
420 vec![DType::F32, DType::F32, DType::F32],
421 BackendId::Cpu,
422 )
423 .expect("relu(add) is a registrable region");
424
425 assert!(id.is_runtime(), "allocated a runtime id");
426 let row = lookup_runtime_kernel(id, BackendId::Cpu).expect("row bound on Cpu");
427 assert_eq!(
428 row.dtypes,
429 vec![DType::F32, DType::F32, DType::F32],
430 "the binding key carries the per-operand dtype tuple",
431 );
432 assert!(
433 fused_kernel_available(id, BackendId::Cpu),
434 "the capability predicate sees the adopted op on Cpu",
435 );
436 assert!(
437 !fused_kernel_available(id, BackendId::Cuda),
438 "but not on a backend the kernel wasn't adopted for",
439 );
440 assert!(
441 fuel_graph::runtime_fused::runtime_region(id).is_some(),
442 "the recipe (region) is registered in the fuel-graph metadata sidecar",
443 );
444 }
445
446 /// The fold invariant: a runtime kernel lives in the SAME global binding
447 /// table as static kernels — resolvable through the ordinary dtype-keyed
448 /// `lookup_with_caps` under its `RuntimeFused` key, with the exact adopted
449 /// kernel pointer. (No second registry behind the facade.)
450 #[test]
451 fn adopted_kernel_lives_in_the_one_binding_table() {
452 let id = adopt_runtime_fused(
453 "test::adopt::sqrt_div::one_registry",
454 sqrt_div(),
455 noop_kernel as KernelRef,
456 vec![DType::F32, DType::F32, DType::F32],
457 BackendId::Cpu,
458 )
459 .expect("registrable region");
460 let (kernel, _caps) = global_bindings()
461 .lookup_with_caps(
462 BindingKey::RuntimeFused(id),
463 &[DType::F32, DType::F32, DType::F32],
464 BackendId::Cpu,
465 )
466 .expect("resolves through the ordinary binding lookup");
467 assert!(
468 std::ptr::fn_addr_eq(kernel, noop_kernel as KernelRef),
469 "the exact adopted kernel, from the one registry",
470 );
471 // …and the dtype-precise lookup is an honest miss on a wrong tuple.
472 assert!(
473 global_bindings()
474 .lookup_with_caps(
475 BindingKey::RuntimeFused(id),
476 &[DType::F64, DType::F64, DType::F64],
477 BackendId::Cpu,
478 )
479 .is_err(),
480 "wrong dtype tuple ⇒ NoBackendForOp, not a wrong-kernel bind",
481 );
482 }
483
484 /// THE REGRESSION TEST (Task 7 cross-crate fix, 2026-07-15). Under
485 /// `register_runtime_fused`'s region-dedup, two `adopt_runtime_fused`
486 /// calls for the SAME region + kernel + dtypes + backend must resolve to
487 /// the SAME `FusedOpId` and must NOT panic on the second call (the
488 /// pre-fix bug: the second `register_runtime_kernel` pushed a
489 /// duplicate-identical `KernelRef` binding, and `finalize()`'s
490 /// `.expect()` in `extend_global_bindings` panicked while holding the
491 /// global bindings write lock, poisoning it for every later test in the
492 /// binary). The kernel must remain available after the re-adopt.
493 /// **GAP-213.** One `FusedOpId` can carry several dtype tuples — the id
494 /// names a RECIPE, not a signature — so resolving by id alone has no single
495 /// right answer. It must not invent one.
496 ///
497 /// Born red: the old `first_runtime_fused` scan bound `dtypes` and never
498 /// tested it, so it answered by `HashMap::iter()` order and could hand back
499 /// the 2-operand kernel for a 3-operand call. That is how GAP-001 spent
500 /// eleven days misattributed to a `baracuda-kernelgen` version.
501 ///
502 /// Built on a LOCAL table on purpose: the global-registry tests in this
503 /// module share process state and call `clear_runtime_fused_for_tests`
504 /// concurrently, so an id minted in one can be invalidated by another. This
505 /// test asserts a property of the lookup, and should not be able to fail
506 /// for a reason that has nothing to do with the lookup.
507 #[test]
508 fn one_recipe_two_signatures_refuses_to_guess() {
509 let mut table = KernelBindingTable::new();
510 let id = FusedOpId(FusedOpId::RUNTIME_FUSED_BASE);
511 register_runtime_kernel_into(
512 &mut table,
513 id,
514 &[DType::F32, DType::F32],
515 BackendId::Cpu,
516 noop_kernel as KernelRef,
517 );
518 register_runtime_kernel_into(
519 &mut table,
520 id,
521 &[DType::F32, DType::F32, DType::F32],
522 BackendId::Cpu,
523 other_noop_kernel as KernelRef,
524 );
525
526 match table.unique_runtime_fused(id, BackendId::Cpu) {
527 crate::kernel::RuntimeFusedLookup::Ambiguous { signatures } => {
528 assert_eq!(
529 signatures.len(),
530 2,
531 "both signatures reported: {signatures:?}"
532 );
533 assert!(
534 signatures.iter().any(|s| s.len() == 2)
535 && signatures.iter().any(|s| s.len() == 3),
536 "the report must name the competing ARITIES, got {signatures:?}",
537 );
538 }
539 other => {
540 panic!("an id carrying two signatures must refuse, not guess — got {other:?}",)
541 }
542 }
543 }
544
545 /// Control. With ONE signature there is nothing to be ambiguous about and
546 /// the lookup must still answer. Without this, the assertion above is
547 /// satisfied by a `unique_runtime_fused` that refuses unconditionally.
548 #[test]
549 fn one_signature_still_resolves() {
550 let mut table = KernelBindingTable::new();
551 let id = FusedOpId(FusedOpId::RUNTIME_FUSED_BASE);
552 register_runtime_kernel_into(
553 &mut table,
554 id,
555 &[DType::F32, DType::F32],
556 BackendId::Cpu,
557 noop_kernel as KernelRef,
558 );
559 match table.unique_runtime_fused(id, BackendId::Cpu) {
560 crate::kernel::RuntimeFusedLookup::Unique(dtypes, _) => {
561 assert_eq!(dtypes, &[DType::F32, DType::F32]);
562 }
563 other => panic!("one signature must resolve — got {other:?}"),
564 }
565 }
566
567 /// Second control: an unbound id must report NotBound, not Ambiguous. Keeps
568 /// the two miss reasons from collapsing back into one.
569 #[test]
570 fn unbound_id_reports_not_bound_not_ambiguous() {
571 let table = KernelBindingTable::new();
572 let id = FusedOpId(FusedOpId::RUNTIME_FUSED_BASE);
573 assert!(matches!(
574 table.unique_runtime_fused(id, BackendId::Cpu),
575 crate::kernel::RuntimeFusedLookup::NotBound
576 ));
577 }
578
579 #[test]
580 fn adopt_runtime_fused_is_idempotent_for_an_identical_region_and_kernel() {
581 let id1 = adopt_runtime_fused(
582 "test::adopt::idempotent::sqrt_div",
583 sqrt_div(),
584 noop_kernel as KernelRef,
585 vec![DType::F32, DType::F32, DType::F32],
586 BackendId::Cpu,
587 )
588 .expect("sqrt(div) is a registrable region");
589
590 // Re-adopt: identical region ⇒ dedup returns the same id; identical
591 // kernel/dtypes/backend ⇒ must be a no-op bind, not a panic.
592 let id2 = adopt_runtime_fused(
593 "test::adopt::idempotent::sqrt_div::again",
594 sqrt_div(),
595 noop_kernel as KernelRef,
596 vec![DType::F32, DType::F32, DType::F32],
597 BackendId::Cpu,
598 )
599 .expect("re-adopting the same region is still Some");
600
601 assert_eq!(
602 id1, id2,
603 "structurally-identical regions dedup to the same FusedOpId"
604 );
605 assert!(
606 fused_kernel_available(id1, BackendId::Cpu),
607 "the kernel is still available after the idempotent re-adopt",
608 );
609 let row = lookup_runtime_kernel(id1, BackendId::Cpu).expect("row still bound on Cpu");
610 assert!(
611 std::ptr::fn_addr_eq(row.kernel, noop_kernel as KernelRef),
612 "the (single, non-duplicated) kernel resolves correctly",
613 );
614 }
615
616 // ---- flash-arm registry-seam bridge (dd-shapes, 2026-07-08) -----------
617
618 /// GREEN target (cuda build only): the CUDA `flash_decoding` kernel
619 /// registers only into `KernelBindingTable`, so before the bridge this was
620 /// unconditionally `false` on every host. Forces `global_bindings()` to
621 /// initialize (which runs the CUDA registration under
622 /// `#[cfg(feature = "cuda")]`) then asserts the static-id bridge sees it.
623 #[test]
624 #[cfg(feature = "cuda")]
625 fn fused_kernel_available_bridges_static_flash_attn_from_binding_table_on_cuda() {
626 let _guard = crate::dispatch::global_bindings();
627 drop(_guard);
628 assert!(
629 fused_kernel_available(FusedOps::FLASH_ATTN, BackendId::Cuda),
630 "the CUDA flash_decoding binding registers only into KernelBindingTable; \
631 fused_kernel_available must bridge to it for the static FLASH_ATTN id",
632 );
633 }
634
635 /// GUARD (non-cuda build only): with no CUDA registration ever run, the
636 /// static-id bridge has nothing to find.
637 #[test]
638 #[cfg(not(feature = "cuda"))]
639 fn fused_kernel_available_flash_attn_false_on_cuda_without_cuda_registration() {
640 assert!(
641 !fused_kernel_available(FusedOps::FLASH_ATTN, BackendId::Cuda),
642 "no cuda registration ran in this build; the static bridge finds nothing",
643 );
644 }
645
646 // ---- lock-nesting prerequisite: table-taking variant (Spec B Task 1) --
647
648 /// [`fused_kernel_available_in`] reads ONLY the table it is handed — a
649 /// row bound into a fresh, local `KernelBindingTable` (never touching
650 /// `global_bindings()`) is visible; the same id/backend against an
651 /// unrelated empty table is not. This is the non-nesting gate the
652 /// runtime-fused pathfinder now calls (via `OptimizationContext::bindings`)
653 /// instead of re-acquiring the global read lock it may already be
654 /// nested under.
655 #[test]
656 fn fused_kernel_available_in_reads_the_passed_table_only() {
657 let mut table = KernelBindingTable::new();
658 // A runtime-fused id bound in THIS table (not the global one).
659 let id = fuel_graph::runtime_fused::register_runtime_fused(
660 "test::fused_kernel_available_in::relu_add",
661 relu_add(),
662 )
663 .expect("relu(add) is a registrable region");
664 register_runtime_kernel_into(
665 &mut table,
666 id,
667 &[DType::F32, DType::F32, DType::F32],
668 BackendId::Cuda,
669 noop_kernel as KernelRef,
670 );
671 assert!(
672 fused_kernel_available_in(&table, id, BackendId::Cuda),
673 "the row bound into THIS table is visible",
674 );
675 assert!(
676 !fused_kernel_available_in(&KernelBindingTable::new(), id, BackendId::Cuda),
677 "an unrelated empty table sees nothing — no fallback to the global table",
678 );
679 }
680
681 /// COORDINATION GUARD: the static-id bridge must be a strict no-op for
682 /// *runtime* ids — a runtime id resolves ONLY through its
683 /// `BindingKey::RuntimeFused` rows (post-fold; formerly the sidecar), and
684 /// an id adopted on one backend is never "rescued" by the bridge on
685 /// another.
686 #[test]
687 fn fused_kernel_available_runtime_id_still_routes_through_runtime_rows_untouched() {
688 let id = adopt_runtime_fused(
689 "test::adopt::flash_bridge_guard::relu_add",
690 relu_add(),
691 noop_kernel as KernelRef,
692 vec![DType::F32, DType::F32, DType::F32],
693 BackendId::Cpu,
694 )
695 .expect("relu(add) is a registrable region");
696
697 assert!(id.is_runtime(), "allocated a runtime id");
698 assert!(
699 fused_kernel_available(id, BackendId::Cpu),
700 "a runtime id still resolves via its RuntimeFused binding rows",
701 );
702 assert!(
703 !fused_kernel_available(id, BackendId::Cuda),
704 "not adopted on Cuda, and the static-id bridge must never rescue a \
705 runtime id (it returns false immediately for any id.is_runtime())",
706 );
707 }
708
709 /// The static-id bridge maps exactly the ids whose kernel registers into the
710 /// primitive binding table: FLASH_ATTN and AWQ_MATMUL. Controls: an id with no
711 /// such kernel (NF4_MATMUL, CPU-registry only) maps to `None`, and the bridge
712 /// answers from the binding table, not from the id alone.
713 #[test]
714 fn static_bridge_maps_awq_matmul_to_its_op_kind_and_only_when_bound() {
715 assert_eq!(
716 static_fused_id_to_binding_table_op_kind(FusedOps::AWQ_MATMUL),
717 Some(OpKind::AwqMatmul)
718 );
719 assert_eq!(
720 static_fused_id_to_binding_table_op_kind(FusedOps::FLASH_ATTN),
721 Some(OpKind::FlashAttn)
722 );
723 assert_eq!(
724 static_fused_id_to_binding_table_op_kind(FusedOps::NF4_MATMUL),
725 None
726 );
727 let empty = KernelBindingTable::new();
728 assert!(!fused_kernel_available_in(
729 &empty,
730 FusedOps::AWQ_MATMUL,
731 BackendId::Cuda
732 ));
733 }
734}