Skip to main content

logicaffeine_jit/
lib.rs

1// Native tier is available only where the forge JIT is (x86_64 System V — Linux +
2// macOS). On Windows/aarch64/wasm this crate is empty and the VM runs bytecode.
3#![cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
4#![doc = include_str!("../README.md")]
5
6use std::sync::atomic::{AtomicU32, Ordering};
7
8use logicaffeine_compile::vm::{
9    install_native_tier, ArrayPin, CalleeSig, Constant, FnTable, HoistGuard, NativeCtx, NativeFn,
10    NativeFrame, NativeOutcome, NativeRet, NativeTier, ObservedKind, Op, ParamKind, PinElem,
11    RegBox, RegionFn, RegionOutcome, SlotKind,
12};
13use logicaffeine_forge::jit::{
14    compile_straightline_coded, compile_straightline_pinned_float, compile_straightline_with,
15    ChainOutcome, Cmp, CompiledChain, FOp, IOp, MicroOp, RmwOp,
16};
17
18// The precise/self call stencils BAKE the depth limit (their holes are all
19// spoken for), so the kernel's `MAX_CALL_DEPTH` and the forge's baked value
20// MUST agree — otherwise native recursion would side-exit at a different depth
21// than the kernel raises its error, diverging from the tree-walker. Pinned at
22// compile time so any future change to either constant fails the BUILD here
23// rather than silently miscompiling deep recursion.
24const _: () = assert!(
25    logicaffeine_compile::semantics::MAX_CALL_DEPTH as i64
26        == logicaffeine_forge::jit::BAKED_CALL_DEPTH,
27    "MAX_CALL_DEPTH and the forge's baked call-depth limit have drifted; \
28     update stencils/int_stencils.rs and jit.rs BAKED_CALL_DEPTH to match",
29);
30
31/// Runtime push helpers the push stencil calls indirectly (their addresses
32/// are baked as constants — no relocations). Each pushes the value with the
33/// kernel's representation and refreshes the pinned pointer/length slots
34/// after a possible realloc.
35///
36/// # Safety
37/// The vec-handle slot must hold a live `*mut Vec<…>` of the matching
38/// element type, pinned (borrowed) by the VM for the whole region run.
39/// The NATIVE ALLOCATION REGISTRY: every list a chain allocates lives
40/// here until its boundary. On success the returned list detaches;
41/// everything else drops. On deopt everything drops — fresh lists never
42/// escape mid-run, so replay is leak-free and double-build-free.
43std::thread_local! {
44    static ALLOC_REGISTRY: std::cell::RefCell<Vec<*mut Vec<i64>>> =
45        const { std::cell::RefCell::new(Vec::new()) };
46}
47
48/// Registry depth — drained-to-zero is part of every boundary's contract
49/// (asserted by the test gates).
50pub fn native_alloc_registry_len() -> usize {
51    ALLOC_REGISTRY.with(|r| r.borrow().len())
52}
53
54fn alloc_registry_drain() {
55    ALLOC_REGISTRY.with(|r| {
56        for ptr in r.borrow_mut().drain(..) {
57            drop(unsafe { Box::from_raw(ptr) });
58        }
59    });
60}
61
62/// Detach one allocation by handle (the boundary's success path for a
63/// returned fresh list). `None` when the handle is not registry-owned
64/// (a param passthrough).
65fn alloc_registry_detach(handle: i64) -> Option<Vec<i64>> {
66    ALLOC_REGISTRY.with(|r| {
67        let mut reg = r.borrow_mut();
68        let pos = reg.iter().position(|&p| p as i64 == handle)?;
69        let ptr = reg.swap_remove(pos);
70        Some(*unsafe { Box::from_raw(ptr) })
71    })
72}
73
74/// Allocate a fresh Int list and plant its pin triple.
75///
76/// # Safety
77/// `frame` slots must be this chain's live frame.
78pub unsafe extern "C" fn logos_rt_alloc_list_i64(
79    frame: *mut i64,
80    vec_slot: i64,
81    ptr_slot: i64,
82    len_slot: i64,
83) {
84    let boxed: Box<Vec<i64>> = Box::new(Vec::new());
85    let raw = Box::into_raw(boxed);
86    ALLOC_REGISTRY.with(|r| r.borrow_mut().push(raw));
87    *frame.add(vec_slot as usize) = raw as i64;
88    *frame.add(ptr_slot as usize) = (*raw).as_mut_ptr() as i64;
89    *frame.add(len_slot as usize) = 0;
90}
91
92/// Plant the pin triple for a list handle RETURNED by a self-call (the
93/// caller's view of the callee's result list).
94///
95/// # Safety
96/// `frame[handle_slot]` must be a live `*mut Vec<i64>` (registry-owned or
97/// a pinned param's vec).
98pub unsafe extern "C" fn logos_rt_list_triple(
99    frame: *mut i64,
100    handle_slot: i64,
101    vec_slot: i64,
102    ptr_slot: i64,
103    len_slot: i64,
104) {
105    let raw = *frame.add(handle_slot as usize) as *mut Vec<i64>;
106    *frame.add(vec_slot as usize) = raw as i64;
107    *frame.add(ptr_slot as usize) = (*raw).as_mut_ptr() as i64;
108    *frame.add(len_slot as usize) = (*raw).len() as i64;
109}
110
111pub unsafe extern "C" fn logos_rt_map_get_ii(
112    map: i64,
113    key: i64,
114    frame: *mut i64,
115    dst_slot: i64,
116) -> i64 {
117    use logicaffeine_compile::interpreter::{MapStorage, RuntimeValue};
118    let storage = &*(map as *const MapStorage);
119    match storage.get(&RuntimeValue::Int(key)) {
120        Some(RuntimeValue::Int(v)) => {
121            *frame.add(dst_slot as usize) = *v;
122            1
123        }
124        // Miss or non-Int value: the side exit replays on bytecode, where
125        // the kernel raises its exact error or hands back the boxed value.
126        _ => 0,
127    }
128}
129
130/// Map insert on the kernel's own storage — identical hashing, identical
131/// iteration order.
132///
133/// # Safety
134/// `map` must be a live `*mut MapStorage` pinned for the run.
135pub unsafe extern "C" fn logos_rt_map_set_ii(map: i64, key: i64, value: i64) {
136    use logicaffeine_compile::interpreter::{MapStorage, RuntimeValue};
137    let storage = &mut *(map as *mut MapStorage);
138    storage.insert(RuntimeValue::Int(key), RuntimeValue::Int(value));
139}
140
141/// Map membership for an Int key.
142///
143/// # Safety
144/// As for [`logos_rt_map_set_ii`].
145pub unsafe extern "C" fn logos_rt_map_has_i(map: i64, key: i64) -> i64 {
146    use logicaffeine_compile::interpreter::{MapStorage, RuntimeValue};
147    let storage = &*(map as *const MapStorage);
148    storage.contains_key(&RuntimeValue::Int(key)) as i64
149}
150
151/// Push helper for pinned Int lists.
152///
153/// # Safety
154/// `frame` slots must be the caller's live pins.
155pub unsafe extern "C" fn logos_rt_push_i64(
156    frame: *mut i64,
157    vec_slot: i64,
158    ptr_slot: i64,
159    len_slot: i64,
160    value: i64,
161) {
162    let vec = *frame.add(vec_slot as usize) as *mut Vec<i64>;
163    (*vec).push(value);
164    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
165    *frame.add(len_slot as usize) = (*vec).len() as i64;
166}
167
168/// Push helper for pinned half-width Int lists (`ListRepr::IntsI32` =
169/// `Vec<i32>`): the value TRUNCATES to its low 4 bytes. The narrowing proof
170/// guarantees every pushed value fits `i32`, so the truncation is lossless;
171/// `debug_assert` catches a proof violation in test builds. The refreshed
172/// `ptr_slot` is the `Vec<i32>` element buffer (4-byte stride — the stencil and
173/// regalloc loads `movsxd` from it).
174///
175/// # Safety
176/// See [`logos_rt_push_i64`]; `vec_slot` must be a live `*mut Vec<i32>`.
177pub unsafe extern "C" fn logos_rt_push_i32(
178    frame: *mut i64,
179    vec_slot: i64,
180    ptr_slot: i64,
181    len_slot: i64,
182    value: i64,
183) {
184    let vec = *frame.add(vec_slot as usize) as *mut Vec<i32>;
185    debug_assert!(
186        i32::try_from(value).is_ok(),
187        "LOGOS_NARROW_VM: pushed value {value} out of i32 range on a narrowed buffer"
188    );
189    (*vec).push(value as i32);
190    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
191    *frame.add(len_slot as usize) = (*vec).len() as i64;
192}
193
194/// See [`logos_rt_push_i64`] — f64 value travels as bits.
195///
196/// # Safety
197/// See [`logos_rt_push_i64`].
198pub unsafe extern "C" fn logos_rt_push_f64(
199    frame: *mut i64,
200    vec_slot: i64,
201    ptr_slot: i64,
202    len_slot: i64,
203    value: i64,
204) {
205    let vec = *frame.add(vec_slot as usize) as *mut Vec<f64>;
206    (*vec).push(f64::from_bits(value as u64));
207    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
208    *frame.add(len_slot as usize) = (*vec).len() as i64;
209}
210
211/// See [`logos_rt_push_i64`] — nonzero stores `true`.
212///
213/// # Safety
214/// See [`logos_rt_push_i64`].
215pub unsafe extern "C" fn logos_rt_push_bool(
216    frame: *mut i64,
217    vec_slot: i64,
218    ptr_slot: i64,
219    len_slot: i64,
220    value: i64,
221) {
222    let vec = *frame.add(vec_slot as usize) as *mut Vec<bool>;
223    (*vec).push(value != 0);
224    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
225    *frame.add(len_slot as usize) = (*vec).len() as i64;
226}
227
228/// In-place clear of a pinned list: `vec.clear()` (truncate to empty, KEEP the
229/// buffer/capacity) and refresh the pinned ptr/len slots (ptr unchanged — clear
230/// never reallocs — but refreshed for parity with push). Lowers an in-region
231/// `NewEmptyList` on a pinned array: an in-place mutation (like SetIndex), so the
232/// PRECISE region resumes over it soundly. See [`logos_rt_push_i64`] for the
233/// frame-slot convention.
234///
235/// # Safety
236/// `frame` slots must be the caller's live pins; `vec_slot` a live `*mut Vec<i64>`.
237pub unsafe extern "C" fn logos_rt_clear_i64(
238    frame: *mut i64,
239    vec_slot: i64,
240    ptr_slot: i64,
241    len_slot: i64,
242) {
243    let vec = *frame.add(vec_slot as usize) as *mut Vec<i64>;
244    (*vec).clear();
245    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
246    *frame.add(len_slot as usize) = 0;
247}
248
249/// See [`logos_rt_clear_i64`] — half-width `Vec<i32>` buffer.
250///
251/// # Safety
252/// See [`logos_rt_clear_i64`]; `vec_slot` must be a live `*mut Vec<i32>`.
253pub unsafe extern "C" fn logos_rt_clear_i32(
254    frame: *mut i64,
255    vec_slot: i64,
256    ptr_slot: i64,
257    len_slot: i64,
258) {
259    let vec = *frame.add(vec_slot as usize) as *mut Vec<i32>;
260    (*vec).clear();
261    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
262    *frame.add(len_slot as usize) = 0;
263}
264
265/// See [`logos_rt_clear_i64`] — `Vec<f64>` buffer.
266///
267/// # Safety
268/// See [`logos_rt_clear_i64`].
269pub unsafe extern "C" fn logos_rt_clear_f64(
270    frame: *mut i64,
271    vec_slot: i64,
272    ptr_slot: i64,
273    len_slot: i64,
274) {
275    let vec = *frame.add(vec_slot as usize) as *mut Vec<f64>;
276    (*vec).clear();
277    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
278    *frame.add(len_slot as usize) = 0;
279}
280
281/// See [`logos_rt_clear_i64`] — `Vec<bool>` buffer.
282///
283/// # Safety
284/// See [`logos_rt_clear_i64`].
285pub unsafe extern "C" fn logos_rt_clear_bool(
286    frame: *mut i64,
287    vec_slot: i64,
288    ptr_slot: i64,
289    len_slot: i64,
290) {
291    let vec = *frame.add(vec_slot as usize) as *mut Vec<bool>;
292    (*vec).clear();
293    *frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
294    *frame.add(len_slot as usize) = 0;
295}
296
297/// MUTABLE-Text append helper for the `MicroOp::StrAppend` regalloc lowering of a
298/// `Set text to text + <s>` build loop. `frame[handle_slot]` holds a `*mut Value`
299/// pointing AT THE VM REGISTER CELL holding the accumulator (planted at region
300/// entry). `src`/`src_len` carry the appended operand:
301/// * `src_len < 0` — BYTE form: `src` is a single ASCII byte VALUE (0..=127); the
302///   appended text is that one character.
303/// * `src_len >= 0` — CONST form: `src` is a `*const u8` to `src_len` bytes (a
304///   baked ASCII literal).
305///
306/// The grow reproduces the VM's `Vm::add_assign` semantics EXACTLY so the
307/// tiered run is BIT-IDENTICAL to the tree-walker for every alias case:
308/// * if the accumulator is a sole-owned `Rc<String>` (`Rc::get_mut` succeeds), it
309///   is extended IN PLACE (`String::push_str`);
310/// * otherwise (the `Rc` is aliased — a captured `Set saved to text`) it is grown
311///   COPY-ON-WRITE through the kernel's `arith::add` (`Rc::new(format!("{a}{b}"))`,
312///   the SAME path `add_assign` falls through to), and the FRESH `Rc` replaces the
313///   accumulator's cell — the alias keeps its own (unchanged) `Rc`;
314/// * a non-`Text` accumulator (never planted — the entry pin declines a non-Text)
315///   still falls to the kernel `add`, matching the bytecode.
316///
317/// # Safety
318/// `frame[handle_slot]` must be the live `*mut Value` planted by the region-entry
319/// pin loop; for the CONST form `src` must point to `src_len` readable ASCII bytes
320/// valid for the call. The single accumulator cell is exclusively reachable for
321/// the native run (no other code touches that register), so the `&mut Value` is
322/// non-aliasing.
323pub unsafe extern "C" fn logos_rt_str_append(
324    frame: *mut i64,
325    handle_slot: i64,
326    src: i64,
327    src_len: i64,
328) {
329    use logicaffeine_compile::interpreter::RuntimeValue;
330    use logicaffeine_compile::vm::Value;
331    use std::rc::Rc;
332
333    let cell = *frame.add(handle_slot as usize) as *mut Value;
334    let acc = &mut *cell;
335
336    // The appended bytes as a &str (ASCII — both the byte form and the baked
337    // const are guaranteed ASCII by the region's `TextBytes`/`TextByte` gate).
338    let byte_buf: [u8; 1];
339    let bytes: &[u8] = if src_len < 0 {
340        byte_buf = [src as u8];
341        &byte_buf
342    } else {
343        std::slice::from_raw_parts(src as *const u8, src_len as usize)
344    };
345    let appended: &str = std::str::from_utf8_unchecked(bytes);
346
347    // Mirror `add_assign`: sole-owned Text grows in place; everything else (a
348    // shared Rc, or a non-Text) takes the kernel `add` (COW for Text+Text).
349    if matches!(acc.as_runtime_ref(), Some(RuntimeValue::Text(_))) {
350        if let RuntimeValue::Text(rc) = acc.as_runtime_mut() {
351            if let Some(s) = Rc::get_mut(rc) {
352                s.push_str(appended);
353                return;
354            }
355        }
356    }
357    // COW / non-Text fallthrough — bit-identical to `add_assign`'s
358    // `self.reg(dst).add(self.reg(src))` for `(Text, Text)`.
359    let rhs = Value::text(appended.to_string());
360    match acc.add(&rhs) {
361        Ok(v) => *acc = v,
362        // `add` over two Texts never errors; an Err would mean a non-Text
363        // accumulator the pin should have declined. Leave the cell untouched
364        // (the region's differential gate would catch any divergence).
365        Err(_) => {}
366    }
367}
368
369/// The deopt sentinel `logos_rt_memmem` returns when the recognized search nest
370/// would, on bytecode, take an access the helper cannot reproduce in-bounds — a
371/// checked needle index past the needle buffer (`needleLen > len(needle)`). The
372/// caller's stencil routes this to a side exit so the VM replays from the region
373/// head on bytecode and raises the EXACT same error at the EXACT same point.
374/// `i64::MIN` is never a legitimate count (a count is in `[0, haystack_len]`).
375pub const LOGOS_MEMMEM_DEOPT: i64 = i64::MIN;
376
377/// Count OVERLAPPING occurrences of an ASCII needle in an ASCII haystack,
378/// reproducing the LOGOS naive-search nest BIT-IDENTICALLY (the string_search
379/// benchmark idiom). The recognizer in `adapt_region` collapses the per-byte
380/// nested-loop region to a single call to this helper.
381///
382/// Parameters mirror the nest's live values at region entry:
383/// - `haystack_ptr` / `haystack_len`: the pinned `text` byte buffer (char index
384///   == byte index for the ASCII pin) and `length of text`.
385/// - `needle_ptr` / `needle_buf_len`: the pinned `needle` byte buffer and its
386///   actual length (the pinned len slot).
387/// - `needle_len`: the PROGRAM's `needleLen` value (the inner loop bound), which
388///   may differ from `needle_buf_len`.
389/// - `start`: the 1-based outer index `i` at region entry.
390///
391/// Semantics, matching the nest `while i <= textLen - needleLen + 1 { … }`:
392/// - the outer bound is `haystack_len - needle_len + 1` (recomputed here from
393///   the SAME inputs the nest used, so it agrees exactly);
394/// - a position `i` (1-based) counts iff `haystack[i-1 + j] == needle[j]` for
395///   all `j` in `[0, needle_len)` (0-based byte compare);
396/// - an empty needle (`needle_len == 0`) matches at EVERY outer position;
397/// - a needle longer than the haystack (bound `< start`) yields zero;
398/// - if a needle index `j` would read past `needle_buf_len` (i.e.
399///   `needle_len > needle_buf_len`, the nest's CHECKED `Index` on `needle`),
400///   the helper returns [`LOGOS_MEMMEM_DEOPT`] WITHOUT scanning — the caller
401///   side-exits and bytecode reproduces the exact `index out of bounds` error.
402///   (Text accesses are guaranteed in-bounds by the bound, exactly as the nest's
403///   `IndexUnchecked` on `text` relies on.)
404///
405/// Counting is via a first-byte scan + window verify (`memchr`-style) which is
406/// bit-identical to the nested loop: it visits the same positions and applies
407/// the same byte equality, just without per-op bytecode dispatch.
408///
409/// # Safety
410/// `haystack_ptr` must point to `haystack_len` readable bytes and `needle_ptr`
411/// to `needle_buf_len` readable bytes, both pinned (borrowed) for the call.
412pub unsafe extern "C" fn logos_rt_memmem(
413    haystack_ptr: i64,
414    haystack_len: i64,
415    needle_ptr: i64,
416    needle_buf_len: i64,
417    needle_len: i64,
418    start: i64,
419    _bound_unused: i64,
420) -> i64 {
421    // Lengths arrive as raw `i64` from the JIT frame. A negative (or larger than
422    // `isize::MAX`) value would make `len as usize` a ~2^64 slice length and turn
423    // the `from_raw_parts` calls below into immediate undefined behavior
424    // (`from_raw_parts` requires `len <= isize::MAX`). Treat any out-of-range length
425    // as the recoverable bail path — bytecode then reproduces the exact semantics —
426    // exactly as for an over-long needle access. Without this, a negative
427    // `needle_len`/`haystack_len` is a reachable OOB / arbitrary-length read.
428    if haystack_len < 0
429        || needle_buf_len < 0
430        || needle_len < 0
431        || haystack_len as u64 > isize::MAX as u64
432        || needle_len as u64 > isize::MAX as u64
433    {
434        return LOGOS_MEMMEM_DEOPT;
435    }
436    // A checked needle access past its buffer is the nest's recoverable error
437    // path: bail to bytecode rather than read OOB or guess the count.
438    if needle_len > needle_buf_len {
439        return LOGOS_MEMMEM_DEOPT;
440    }
441    // The nest recomputes `bound = textLen - needleLen + 1` every outer
442    // iteration from loop-invariant inputs; recompute it here from the same
443    // values so the range agrees exactly (the passed `_bound_unused` is the
444    // VM's live copy, kept in the ABI for parity but never trusted).
445    let bound = haystack_len - needle_len + 1;
446    if start > bound {
447        return 0;
448    }
449    let haystack = std::slice::from_raw_parts(haystack_ptr as *const u8, haystack_len as usize);
450    let needle = std::slice::from_raw_parts(needle_ptr as *const u8, needle_len as usize);
451
452    // Empty needle: matches at every outer position in `[start, bound]`.
453    if needle_len == 0 {
454        return bound - start + 1;
455    }
456
457    // 1-based positions `[start, bound]` correspond to 0-based haystack offsets
458    // `[start - 1, bound - 1]`. Each offset `p` matches iff
459    // `haystack[p .. p + needle_len] == needle`.
460    let first = needle[0];
461    let nlen = needle_len as usize;
462    let lo = (start - 1) as usize;
463    // `bound - 1` is the last 0-based start offset; the window end is
464    // `bound - 1 + nlen == haystack_len`, in bounds by construction.
465    let hi = (bound - 1) as usize; // inclusive last start offset
466    let mut count = 0i64;
467    let mut p = lo;
468    while p <= hi {
469        // First-byte scan to the next candidate (memchr semantics).
470        match memchr(first, &haystack[p..=hi]) {
471            Some(off) => {
472                let cand = p + off;
473                if haystack[cand..cand + nlen] == *needle {
474                    count += 1;
475                }
476                p = cand + 1;
477            }
478            None => break,
479        }
480    }
481    count
482}
483
484/// First-byte scan helper (a tiny `memchr`): the offset of the first `needle`
485/// byte in `hay`, or `None`. A plain byte loop — bit-identical to the nest's
486/// position-by-position visit; a SIMD (SSE2/AVX2) replacement can drop in here
487/// once the scalar path is proven against the differential gate.
488#[inline]
489fn memchr(needle: u8, hay: &[u8]) -> Option<usize> {
490    hay.iter().position(|&b| b == needle)
491}
492
493/// Frame-driven entry the [`MicroOp::MemMem`] stencil calls: reads the search
494/// nest's live values out of the JIT frame, runs [`logos_rt_memmem`], and on
495/// success ADDS the count into `count_slot` and advances `i_slot` to the loop's
496/// exit value (`bound + 1`). Returns `1` on success and `0` on the deopt
497/// sentinel — the stencil routes `0` to its side-exit continuation, untouched.
498///
499/// # Safety
500/// `frame` must be the running chain's live frame; the named slots must hold the
501/// pinned haystack/needle pointer+length pairs, the `needleLen` value, the
502/// 1-based `i`, and the `count` accumulator.
503#[allow(clippy::too_many_arguments)]
504pub unsafe extern "C" fn logos_rt_memmem_frame(
505    frame: *mut i64,
506    h_ptr_slot: i64,
507    h_len_slot: i64,
508    n_ptr_slot: i64,
509    n_len_slot: i64,
510    needle_len_slot: i64,
511    i_slot: i64,
512    count_slot: i64,
513) -> i64 {
514    let h_ptr = *frame.add(h_ptr_slot as usize);
515    let h_len = *frame.add(h_len_slot as usize);
516    let n_ptr = *frame.add(n_ptr_slot as usize);
517    let n_buf_len = *frame.add(n_len_slot as usize);
518    let needle_len = *frame.add(needle_len_slot as usize);
519    let start = *frame.add(i_slot as usize);
520    let result = logos_rt_memmem(h_ptr, h_len, n_ptr, n_buf_len, needle_len, start, 0);
521    if result == LOGOS_MEMMEM_DEOPT {
522        return 0;
523    }
524    *frame.add(count_slot as usize) += result;
525    // Advance `i` to the nest's natural exit value: the first index for which
526    // `i > bound` (`bound = h_len - needle_len + 1`). If the loop never ran
527    // (`start > bound`), `i` keeps its entry value — exactly the bytecode's
528    // `while i <= bound { … i = i + 1 }` post-state.
529    let bound = h_len - needle_len + 1;
530    let exit_i = core::cmp::max(start, bound + 1);
531    *frame.add(i_slot as usize) = exit_i;
532    1
533}
534
535/// Register kinds for the adapter's sound dataflow.
536#[derive(Clone, Copy, PartialEq, Debug)]
537enum Kind {
538    Unknown,
539    Int,
540    Bool,
541    Float,
542    /// A pinned unboxed Int list (the register itself never rides a slot —
543    /// its buffer pointer/length live in dedicated pin slots).
544    IntList,
545    /// A pinned half-width (`Vec<i32>`) Int list (`ListRepr::IntsI32`): the
546    /// element access width is 4 bytes (sign-extending loads, truncating
547    /// stores). Behaves exactly like [`Kind::IntList`] for register/pin gating;
548    /// only the array op width differs.
549    IntListI32,
550    /// A pinned unboxed Float list.
551    FloatList,
552    /// A pinned unboxed Bool list.
553    BoolList,
554    /// A pinned MAP (boxed kernel storage behind a pointer pin; int fast
555    /// lane verified per helper call).
556    IntMap,
557    /// A pinned ASCII `Text` carried AS BYTES (char index == byte index, char
558    /// count == byte length): `item i of text` is a 1-byte pinned load and the
559    /// extracted char rides the [`Kind::TextByte`] lane.
560    TextBytes,
561    /// A single ASCII character extracted from a [`Kind::TextBytes`] (or a
562    /// 1-char ASCII text constant): the integer byte value 0..=127. Equality
563    /// between two `TextByte`s is an exact integer compare — which matches the
564    /// tree-walker's `Text == Text` string compare for single ASCII chars.
565    TextByte,
566    /// A pinned MUTABLE-Text accumulator (the `dst` of a `Set text to text + …`
567    /// build loop): it rides the [`PinElem::TextMut`] channel (a `*mut Value`
568    /// handle), not a scalar frame slot. The only op that consumes it is the
569    /// append (`AddAssign`), lowered to [`MicroOp::StrAppend`]; any other use
570    /// (a `Move`, an `Index`, …) is rejected so the region bails — keeping the
571    /// accumulator's growth confined to the proven-sound append path.
572    TextMut,
573    /// A MULTI-character ASCII text CONSTANT (`text + "XXXXX"`): a baked byte
574    /// slice. Valid ONLY as the source of a `StrAppend`; every other use gate
575    /// rejects it (so the region bails).
576    TextConst,
577    Mixed,
578}
579
580impl Kind {
581    fn slot_kind(self) -> Option<SlotKind> {
582        match self {
583            Kind::Int => Some(SlotKind::Int),
584            Kind::Bool => Some(SlotKind::Bool),
585            Kind::Float => Some(SlotKind::Float),
586            _ => None,
587        }
588    }
589}
590
591/// The single ASCII byte of a 1-character ASCII text constant, or `None` if the
592/// constant is not exactly one ASCII char. A `Kind::TextByte` comparison loads
593/// one byte from the pinned text, so a constant on the other side must be that
594/// same single byte; the tree-walker compares the two 1-char Texts as strings,
595/// which agrees with the byte compare exactly for single ASCII chars.
596fn text_const_byte(s: &str) -> Option<u8> {
597    let b = s.as_bytes();
598    (b.len() == 1 && b[0] < 128).then(|| b[0])
599}
600
601/// Recover the constant ASCII byte slice of a `StrAppend`'s `TextConst` source
602/// (`text + "XXXXX"`) and bake it into a `'static` allocation so the regalloc
603/// stencil can hold a stable `(ptr, len)`. The source register `src` is loaded by
604/// a `LoadConst` in the same region; scan BACKWARD from the append for the nearest
605/// def of `src` and require it to be a `LoadConst` of a `Constant::Text`. Returns
606/// `None` (declining the region) if the source is not a direct constant load — a
607/// conservative bail to bytecode. The leak is one-time per compiled region (there
608/// are a handful of such constants) and the bytes outlive the chain.
609fn const_text_slice(
610    ops: &[Op],
611    i: usize,
612    base_pc: usize,
613    src: u16,
614    constants: &[Constant],
615) -> Option<(i64, i64)> {
616    let _ = base_pc;
617    let mut j = i;
618    while j > 0 {
619        j -= 1;
620        let (_, defs) = region_use_def(&ops[j])?;
621        if defs.contains(&src) {
622            if let Op::LoadConst { idx, .. } = ops[j] {
623                if let Some(Constant::Text(s)) = constants.get(idx as usize) {
624                    if s.is_ascii() && !s.is_empty() {
625                        let leaked: &'static [u8] = s.clone().into_bytes().leak();
626                        return Some((leaked.as_ptr() as i64, leaked.len() as i64));
627                    }
628                }
629            }
630            // The nearest def of `src` is not a constant Text load — bail.
631            return None;
632        }
633    }
634    None
635}
636
637fn observed_kind(o: ObservedKind) -> Kind {
638    match o {
639        ObservedKind::Int => Kind::Int,
640        ObservedKind::Bool => Kind::Bool,
641        ObservedKind::Float => Kind::Float,
642        ObservedKind::IntList => Kind::IntList,
643        ObservedKind::IntListI32 => Kind::IntListI32,
644        ObservedKind::FloatList => Kind::FloatList,
645        ObservedKind::BoolList => Kind::BoolList,
646        ObservedKind::Map => Kind::IntMap,
647        ObservedKind::TextBytes => Kind::TextBytes,
648        ObservedKind::Other => Kind::Mixed,
649    }
650}
651
652/// Per-pinned-register slot assignments for translation.
653#[derive(Clone, Copy)]
654struct PinSlots {
655    vec_slot: u16,
656    ptr_slot: u16,
657    len_slot: u16,
658    elem: PinElem,
659}
660
661/// Everything a SELF-call lowering needs baked into its stencil.
662struct CallCtx<'t> {
663    table_addr: i64,
664    depth_addr: i64,
665    status_addr: i64,
666    limit_slot: u16,
667    depth_limit: i64,
668    self_fi: u16,
669    /// The program's function table — non-self calls bake the CALLEE's
670    /// slot address for table dispatch.
671    table: &'t FnTable,
672}
673
674/// The result kind of an arithmetic op over two operand kinds, mirroring the
675/// kernel's promotion: Int×Int→Int, any Float→Float. None = not arithmetic
676/// (the use gate bails).
677fn arith_result(l: Kind, r: Kind) -> Option<Kind> {
678    match (l, r) {
679        (Kind::Int, Kind::Int) => Some(Kind::Int),
680        (Kind::Float, Kind::Float) | (Kind::Int, Kind::Float) | (Kind::Float, Kind::Int) => {
681            Some(Kind::Float)
682        }
683        _ => None,
684    }
685}
686
687fn join(a: Kind, b: Kind) -> Kind {
688    match (a, b) {
689        (Kind::Unknown, x) | (x, Kind::Unknown) => x,
690        (x, y) if x == y => x,
691        _ => Kind::Mixed,
692    }
693}
694
695/// Forward, flow-SENSITIVE kind dataflow over a bytecode slice (a function
696/// body or a loop region). Each reachable op gets the kind vector holding
697/// BEFORE it executes; merge points join per slot (`Unknown ⊔ k = k`,
698/// `k ⊔ k = k`, else `Mixed`). Constants carry their pool type (Int, Float,
699/// Bool); arithmetic promotes like the kernel (any Float operand → Float).
700/// Jump targets outside the slice are exits. Returns per-op kind-in vectors
701/// (None = unreachable), or None when any reachable use violates the gates.
702fn kind_flow(
703    ops: &[Op],
704    base_pc: usize,
705    constants: &[Constant],
706    entry: Vec<Kind>,
707    calls: Option<&dyn Fn(u16) -> Option<(Kind, Vec<Kind>)>>,
708) -> Option<Vec<Option<Vec<Kind>>>> {
709    let n = ops.len();
710    let in_slice = |t: usize| t >= base_pc && t < base_pc + n;
711    let const_kind = |idx: u32| -> Kind {
712        match constants.get(idx as usize) {
713            Some(Constant::Int(_)) => Kind::Int,
714            Some(Constant::Float(_)) => Kind::Float,
715            Some(Constant::Bool(_)) => Kind::Bool,
716            // A 1-char ASCII text/char literal compares against an extracted
717            // `item i of text` as an integer byte (the strings benchmark's
718            // `item i of result equals " "`). A multi-char or non-ASCII text is
719            // not a single byte — it stays Mixed and bails.
720            Some(Constant::Char(c)) if c.is_ascii() => Kind::TextByte,
721            Some(Constant::Text(s)) if text_const_byte(s).is_some() => Kind::TextByte,
722            // A MULTI-char ASCII text constant is valid only as a `StrAppend`
723            // source (`text + "XXXXX"`); every other gate rejects `TextConst`.
724            Some(Constant::Text(s)) if !s.is_empty() && s.is_ascii() => Kind::TextConst,
725            _ => Kind::Mixed,
726        }
727    };
728    let mut kin: Vec<Option<Vec<Kind>>> = vec![None; n];
729    kin[0] = Some(entry);
730    let mut work: Vec<usize> = vec![0];
731    while let Some(i) = work.pop() {
732        let Some(cur) = kin[i].clone() else { continue };
733        let mut out = cur;
734        match ops[i] {
735            Op::LoadConst { dst, idx } => out[dst as usize] = const_kind(idx),
736            Op::Add { dst, lhs, rhs }
737            | Op::Sub { dst, lhs, rhs }
738            | Op::Mul { dst, lhs, rhs }
739            | Op::Div { dst, lhs, rhs } => {
740                out[dst as usize] =
741                    arith_result(out[lhs as usize], out[rhs as usize]).unwrap_or(Kind::Mixed)
742            }
743            Op::AddAssign { dst, src } => {
744                out[dst as usize] = if out[dst as usize] == Kind::TextMut {
745                    // A pinned mutable-Text accumulator stays the accumulator —
746                    // it grows in place / COW through `StrAppend`.
747                    Kind::TextMut
748                } else {
749                    arith_result(out[dst as usize], out[src as usize]).unwrap_or(Kind::Mixed)
750                }
751            }
752            Op::Mod { dst, .. }
753            | Op::DivPow2 { dst, .. }
754            | Op::MagicDivU { dst, .. }
755            | Op::BitXor { dst, .. }
756            | Op::Shl { dst, .. }
757            | Op::Shr { dst, .. } => out[dst as usize] = Kind::Int,
758            Op::BitAnd { dst, lhs, rhs } | Op::BitOr { dst, lhs, rhs } => {
759                out[dst as usize] = match (out[lhs as usize], out[rhs as usize]) {
760                    (Kind::Int, Kind::Int) => Kind::Int,
761                    (Kind::Bool, Kind::Bool) => Kind::Bool,
762                    _ => Kind::Mixed,
763                }
764            }
765            Op::Not { dst, src } => {
766                out[dst as usize] = match out[src as usize] {
767                    Kind::Bool => Kind::Bool,
768                    _ => Kind::Mixed,
769                }
770            }
771            Op::Lt { dst, .. } | Op::Gt { dst, .. } | Op::LtEq { dst, .. }
772            | Op::GtEq { dst, .. } | Op::Eq { dst, .. } | Op::NotEq { dst, .. } => {
773                out[dst as usize] = Kind::Bool
774            }
775            Op::Move { dst, src } => out[dst as usize] = out[src as usize],
776            Op::Index { dst, collection, .. }
777            | Op::IndexUnchecked { dst, collection, .. } => {
778                out[dst as usize] = match out[collection as usize] {
779                    Kind::IntList | Kind::IntListI32 => Kind::Int,
780                    Kind::FloatList => Kind::Float,
781                    Kind::BoolList => Kind::Bool,
782                    // Map int fast lane: a non-Int value is a helper miss
783                    // (side exit), so the in-region kind is Int.
784                    Kind::IntMap => Kind::Int,
785                    // `item i of text` on an ASCII text: a single ASCII byte.
786                    Kind::TextBytes => Kind::TextByte,
787                    _ => Kind::Mixed,
788                }
789            }
790            Op::Length { dst, .. } => out[dst as usize] = Kind::Int,
791            Op::Contains { dst, .. } => out[dst as usize] = Kind::Bool,
792            // Fresh allocations ride the Int lane (mode B sites).
793            Op::NewEmptyList { dst } => out[dst as usize] = Kind::IntList,
794            Op::Call { dst, func, .. } => {
795                let Some((ret, _)) = calls.and_then(|c| c(func)) else { return None };
796                out[dst as usize] = ret;
797            }
798            Op::CallBuiltin {
799                dst,
800                builtin: logicaffeine_compile::semantics::builtins::BuiltinId::Sqrt,
801                ..
802            } => out[dst as usize] = Kind::Float,
803            _ => {}
804        }
805        let mut succs: Vec<usize> = Vec::with_capacity(2);
806        match ops[i] {
807            Op::Jump { target } => {
808                if in_slice(target) {
809                    succs.push(target - base_pc);
810                }
811            }
812            Op::JumpIfFalse { target, .. } | Op::JumpIfTrue { target, .. } => {
813                if i + 1 < n {
814                    succs.push(i + 1);
815                }
816                if in_slice(target) {
817                    succs.push(target - base_pc);
818                }
819            }
820            Op::Return { .. } | Op::ReturnNothing => {}
821            _ => {
822                if i + 1 < n {
823                    succs.push(i + 1);
824                }
825            }
826        }
827        for s in succs {
828            match &mut kin[s] {
829                slot @ None => {
830                    *slot = Some(out.clone());
831                    work.push(s);
832                }
833                Some(prev) => {
834                    let mut changed = false;
835                    for (a, b) in prev.iter_mut().zip(&out) {
836                        let j = join(*a, *b);
837                        if j != *a {
838                            *a = j;
839                            changed = true;
840                        }
841                    }
842                    if changed {
843                        work.push(s);
844                    }
845                }
846            }
847        }
848    }
849    // Gates on every reachable use, at that use's program point.
850    for (i, op) in ops.iter().enumerate() {
851        let Some(k) = &kin[i] else { continue };
852        let int_at = |r: u16| k[r as usize] == Kind::Int;
853        let num_at = |r: u16| matches!(k[r as usize], Kind::Int | Kind::Float);
854        match *op {
855            // Arithmetic and ordered comparisons: numeric operands (mixed
856            // Int/Float promotes via an inserted conversion).
857            Op::Add { lhs, rhs, .. } | Op::Sub { lhs, rhs, .. } | Op::Mul { lhs, rhs, .. }
858            | Op::Div { lhs, rhs, .. }
859            | Op::Lt { lhs, rhs, .. } | Op::Gt { lhs, rhs, .. } | Op::LtEq { lhs, rhs, .. }
860            | Op::GtEq { lhs, rhs, .. } => {
861                if !num_at(lhs) || !num_at(rhs) {
862                    return None;
863                }
864            }
865            Op::AddAssign { dst, src } => {
866                if k[dst as usize] == Kind::TextMut {
867                    // A pinned mutable-Text append: the source must be an
868                    // appendable ASCII text — a 1-char runtime byte (`TextByte`)
869                    // or a multi-char constant (`TextConst`). `StrAppend` lowers it.
870                    if !matches!(k[src as usize], Kind::TextByte | Kind::TextConst) {
871                        return None;
872                    }
873                } else if !num_at(dst) || !num_at(src) {
874                    return None;
875                }
876            }
877            // Equality: numeric pairs (promoting), Bool×Bool, or TextByte×
878            // TextByte (two extracted ASCII chars, or an extracted char vs a
879            // 1-char ASCII literal — both sides are integer bytes, the compare
880            // is exact, matching the tree-walker's single-char Text compare).
881            Op::Eq { lhs, rhs, .. } | Op::NotEq { lhs, rhs, .. } => {
882                let bools = k[lhs as usize] == Kind::Bool && k[rhs as usize] == Kind::Bool;
883                let bytes = k[lhs as usize] == Kind::TextByte && k[rhs as usize] == Kind::TextByte;
884                if !bools && !bytes && (!num_at(lhs) || !num_at(rhs)) {
885                    return None;
886                }
887            }
888            // Int-only ops.
889            Op::Mod { lhs, rhs, .. } | Op::BitXor { lhs, rhs, .. }
890            | Op::Shl { lhs, rhs, .. } | Op::Shr { lhs, rhs, .. } => {
891                if !int_at(lhs) || !int_at(rhs) {
892                    return None;
893                }
894            }
895            // `& |`: both-Int (bitwise) or both-Bool (logical 0/1).
896            Op::BitAnd { lhs, rhs, .. } | Op::BitOr { lhs, rhs, .. } => {
897                let ints = int_at(lhs) && int_at(rhs);
898                let bools = k[lhs as usize] == Kind::Bool && k[rhs as usize] == Kind::Bool;
899                if !ints && !bools {
900                    return None;
901                }
902            }
903            // `not` is LOGICAL (truthiness → Bool); only a Bool operand has a
904            // 0/1 register form the stencils can negate. `not <Int>` (rare —
905            // an emptiness idiom) stays on the VM.
906            Op::Not { src, .. } => {
907                if !matches!(k[src as usize], Kind::Bool) {
908                    return None;
909                }
910            }
911            Op::Move { src, .. } => {
912                // List buffers never ride frame slots (they are pinned
913                // separately) — but a list-kind Move is legal as STAGING:
914                // the slot value is a placeholder and pin identity travels
915                // statically. Any real USE of an unpinned destination still
916                // fails closed at translate (pins lookup).
917                if !matches!(
918                    k[src as usize],
919                    Kind::Int
920                        | Kind::Bool
921                        | Kind::Float
922                        | Kind::IntList
923                        | Kind::IntListI32
924                        | Kind::FloatList
925                        | Kind::BoolList
926                        // A 1-char ASCII byte (`text + ch`'s `ch`) rides a plain
927                        // frame slot as its integer byte value — a raw-copy Move.
928                        | Kind::TextByte
929                ) {
930                    return None;
931                }
932            }
933            Op::JumpIfFalse { cond, .. } | Op::JumpIfTrue { cond, .. } => {
934                if k[cond as usize] != Kind::Bool {
935                    return None;
936                }
937            }
938            Op::Index { collection, index, .. }
939            | Op::IndexUnchecked { collection, index, .. } => {
940                if !matches!(
941                    k[collection as usize],
942                    Kind::IntList | Kind::IntListI32 | Kind::FloatList | Kind::BoolList | Kind::IntMap | Kind::TextBytes
943                ) || !int_at(index)
944                {
945                    return None;
946                }
947            }
948            Op::SetIndex { collection, index, value }
949            | Op::SetIndexUnchecked { collection, index, value } => {
950                let elem_ok = match k[collection as usize] {
951                    Kind::IntList | Kind::IntListI32 => k[value as usize] == Kind::Int,
952                    Kind::FloatList => k[value as usize] == Kind::Float,
953                    Kind::BoolList => k[value as usize] == Kind::Bool,
954                    Kind::IntMap => k[value as usize] == Kind::Int,
955                    _ => false,
956                };
957                if !elem_ok || !int_at(index) {
958                    return None;
959                }
960            }
961            Op::Contains { collection, value, .. } => {
962                if k[collection as usize] != Kind::IntMap || !int_at(value) {
963                    return None;
964                }
965            }
966            Op::Length { collection, .. } => {
967                if !matches!(
968                    k[collection as usize],
969                    Kind::IntList | Kind::IntListI32 | Kind::FloatList | Kind::BoolList | Kind::TextBytes
970                ) {
971                    return None;
972                }
973            }
974            // SELF-calls only (the callee's entry contract is its own: Int
975            // params, known return kind); every argument must be Int at the
976            // call point — exactly the per-call guard the bytecode boundary
977            // enforces.
978            Op::Call { func, args_start, arg_count, .. } => {
979                let Some((_, params)) = calls.and_then(|c| c(func)) else { return None };
980                if arg_count as usize != params.len() {
981                    return None;
982                }
983                for (j, a) in (args_start..args_start + arg_count).enumerate() {
984                    if k[a as usize] != params[j] {
985                        return None;
986                    }
987                }
988            }
989            Op::CallBuiltin {
990                builtin: logicaffeine_compile::semantics::builtins::BuiltinId::Sqrt,
991                args_start,
992                arg_count,
993                ..
994            } => {
995                if arg_count != 1
996                    || !matches!(k[args_start as usize], Kind::Int | Kind::Float)
997                {
998                    return None;
999                }
1000            }
1001            Op::ListPush { list, value } => {
1002                let elem_ok = match k[list as usize] {
1003                    Kind::IntList | Kind::IntListI32 => k[value as usize] == Kind::Int,
1004                    Kind::FloatList => k[value as usize] == Kind::Float,
1005                    Kind::BoolList => k[value as usize] == Kind::Bool,
1006                    _ => false,
1007                };
1008                if !elem_ok {
1009                    return None;
1010                }
1011            }
1012            Op::Return { src } => {
1013                // Regions return scalars (re-boxed by SlotKind) or pinned
1014                // lists (by register); the function adapter's own ret-kind
1015                // check still restricts FUNCTION returns to Int/Bool.
1016                if !matches!(
1017                    k[src as usize],
1018                    Kind::Int
1019                        | Kind::Bool
1020                        | Kind::Float
1021                        | Kind::IntList
1022                        | Kind::IntListI32
1023                        | Kind::FloatList
1024                        | Kind::BoolList
1025                ) {
1026                    return None;
1027                }
1028            }
1029            _ => {}
1030        }
1031    }
1032    Some(kin)
1033}
1034
1035/// Translate WHOLE-PROGRAM Main bytecode into the J2 micro-op subset.
1036///
1037/// Requirements (else `None`):
1038/// - ops drawn from {LoadConst(Int), Move, Add, Sub, Mul, Lt, Gt, Eq,
1039///   Jump, JumpIfFalse, JumpIfTrue, Show};
1040/// - exactly ONE Show, and it is the final op (it becomes Return);
1041/// - no comparison-produced value is the shown register (bool-taint).
1042///
1043/// Returns the micro program and the frame size it needs.
1044#[allow(clippy::type_complexity)]
1045pub fn adapt(
1046    ops: &[Op],
1047    constants: &[Constant],
1048    register_count: usize,
1049) -> Option<(Vec<MicroOp>, usize)> {
1050    // The shown register must not be a comparison result anywhere.
1051    let mut tainted: Vec<u16> = Vec::new();
1052    let mut shows = 0usize;
1053    for op in ops {
1054        match *op {
1055            Op::Lt { dst, .. } | Op::Gt { dst, .. } | Op::LtEq { dst, .. }
1056            | Op::GtEq { dst, .. } | Op::Eq { dst, .. } | Op::NotEq { dst, .. } => {
1057                tainted.push(dst)
1058            }
1059            Op::Show { .. } => shows += 1,
1060            _ => {}
1061        }
1062    }
1063    // Terminal shape: `Show; Halt` (or a bare trailing Show). Anything that
1064    // jumps PAST the Show (to the Halt) would print nothing on the VM while
1065    // the JIT still returns a value — bail on those.
1066    let n = ops.len();
1067    let (show_pc, halt_pc) = match (n.checked_sub(2).map(|i| &ops[i]), ops.last()) {
1068        (Some(Op::Show { .. }), Some(Op::Halt)) => (n - 2, Some(n - 1)),
1069        (_, Some(Op::Show { .. })) => (n - 1, None),
1070        _ => return None,
1071    };
1072    if shows != 1 {
1073        return None;
1074    }
1075    for op in ops {
1076        if let Op::Jump { target } | Op::JumpIfFalse { target, .. } | Op::JumpIfTrue { target, .. } = *op {
1077            if target > show_pc {
1078                return None;
1079            }
1080        }
1081    }
1082    let _ = halt_pc;
1083
1084    let frame_size = register_count;
1085
1086    // Pass 1: translate, recording vm-pc → micro-index. Jump targets hold VM
1087    // pcs until pass 2 remaps them.
1088    let mut micro: Vec<MicroOp> = Vec::with_capacity(ops.len());
1089    let mut pc_to_micro: Vec<usize> = Vec::with_capacity(ops.len());
1090    // (micro index, vm target) needing remap.
1091    let mut fixups: Vec<usize> = Vec::new();
1092
1093    for op in ops {
1094        pc_to_micro.push(micro.len());
1095        match *op {
1096            Op::LoadConst { dst, idx } => match constants.get(idx as usize)? {
1097                Constant::Int(v) => micro.push(MicroOp::LoadConst { dst, value: *v }),
1098                _ => return None,
1099            },
1100            Op::Move { dst, src } => micro.push(MicroOp::Move { dst, src }),
1101            Op::EnsureOwned { .. } => {} // interpreter-only COW barrier — no-op in a native region
1102            Op::Add { dst, lhs, rhs } => micro.push(MicroOp::Add { dst, lhs, rhs }),
1103            Op::AddAssign { dst, src } => micro.push(MicroOp::Add { dst, lhs: dst, rhs: src }),
1104            Op::Div { dst, lhs, rhs } => match const_pow2_div(ops, rhs, constants) {
1105                Some(k) => micro.push(MicroOp::DivPow2 { dst, lhs, k }),
1106                None => micro.push(MicroOp::Div { dst, lhs, rhs }),
1107            },
1108            Op::DivPow2 { dst, lhs, k } => micro.push(MicroOp::DivPow2 { dst, lhs, k: k as u32 }),
1109            Op::MagicDivU { dst, lhs, magic, more, mul_back } => {
1110                micro.push(MicroOp::MagicDivU { dst, lhs, magic, more, mul_back })
1111            }
1112            Op::Mod { dst, lhs, rhs } => micro.push(MicroOp::Mod { dst, lhs, rhs }),
1113            Op::Sub { dst, lhs, rhs } => micro.push(MicroOp::Sub { dst, lhs, rhs }),
1114            Op::Mul { dst, lhs, rhs } => micro.push(MicroOp::Mul { dst, lhs, rhs }),
1115            Op::Lt { dst, lhs, rhs } => micro.push(MicroOp::Lt { dst, lhs, rhs }),
1116            Op::Gt { dst, lhs, rhs } => micro.push(MicroOp::Gt { dst, lhs, rhs }),
1117            Op::Eq { dst, lhs, rhs } => micro.push(MicroOp::Eq { dst, lhs, rhs }),
1118            Op::LtEq { dst, lhs, rhs } => micro.push(MicroOp::LtEq { dst, lhs, rhs }),
1119            Op::GtEq { dst, lhs, rhs } => micro.push(MicroOp::GtEq { dst, lhs, rhs }),
1120            Op::NotEq { dst, lhs, rhs } => micro.push(MicroOp::Neq { dst, lhs, rhs }),
1121            Op::Jump { target } => {
1122                fixups.push(micro.len());
1123                micro.push(MicroOp::Jump { target });
1124            }
1125            Op::JumpIfFalse { cond, target } => {
1126                fixups.push(micro.len());
1127                micro.push(MicroOp::JumpIfFalse { cond, target });
1128            }
1129            Op::JumpIfTrue { cond, target } => {
1130                fixups.push(micro.len());
1131                micro.push(MicroOp::JumpIfTrue { cond, target });
1132            }
1133            Op::Show { src } => {
1134                if tainted.contains(&src) {
1135                    return None;
1136                }
1137                micro.push(MicroOp::Return { src });
1138            }
1139            // The trailing Halt after the Show: unreachable past Return.
1140            Op::Halt => {}
1141            _ => return None,
1142        }
1143    }
1144
1145    // Pass 2: remap jump targets from VM pcs to micro indices.
1146    for &i in &fixups {
1147        match &mut micro[i] {
1148            MicroOp::Jump { target }
1149            | MicroOp::JumpIfFalse { target, .. }
1150            | MicroOp::JumpIfTrue { target, .. } => {
1151                *target = *pc_to_micro.get(*target)?;
1152            }
1153            _ => unreachable!(),
1154        }
1155    }
1156    Some((micro, frame_size))
1157}
1158
1159/// Translate a FUNCTION body (terminal = Op::Return, absolute pcs rebased by
1160/// `entry_pc`) into the J2 subset.
1161///
1162/// J4 kind dataflow (sound fixpoint): params are Int (the entry guard),
1163/// comparisons produce Bool, arithmetic requires Int operands (a Bool
1164/// flowing into `+` errors on the VM — native code must NOT compute it),
1165/// Move propagates, conflicting writes go Mixed (bail on use). All reachable
1166/// returns must agree on Int or Bool; the bool flag re-boxes the result.
1167/// Everything the precise-deopt boundary needs at runtime, built once at
1168/// adapt time (mode B — functions with list parameters).
1169struct PreciseInfo {
1170    register_count: usize,
1171    pins_start: usize,
1172    /// Per-register re-box kinds at every potential pause point: each
1173    /// deoptable op's pc and each self-call's pc.
1174    kinds_by_pc: std::collections::HashMap<usize, Vec<RegBox>>,
1175    /// NATIVE-OWNED list registers per pause point: (register, the frame
1176    /// slot of its pin's vec handle). At materialization the handle either
1177    /// detaches from the allocation registry (fresh list — re-boxed here)
1178    /// or matches a boundary pin (param passthrough — rewritten to
1179    /// ListParam for the VM side).
1180    owned_by_pc: std::collections::HashMap<usize, Vec<(u16, u16)>>,
1181    /// Where each boundary pin triple (param order) lands in the frame:
1182    /// the vec slot of THAT PARAM REGISTER's pin (per-register pins are
1183    /// not in param order).
1184    param_pin_scatter: Vec<u16>,
1185}
1186
1187/// What adapt_function hands back: micro code, frame size, return shape,
1188/// per-micro deopt codes (mode B) and the precise-walk tables (mode B).
1189struct AdaptedFn {
1190    micro: Vec<MicroOp>,
1191    frame_size: usize,
1192    ret: NativeRet,
1193    deopt_codes: Option<Vec<i64>>,
1194    precise: Option<PreciseInfo>,
1195}
1196
1197/// The precise-deopt tables for a REGION (push+SetIndex worklist). `deopt_codes`
1198/// tag every micro with its bytecode op's encoded resume pc; `kinds_by_pc` maps
1199/// each resume pc to the per-register re-box kind used when materializing the
1200/// frame's scalars back into the VM registers (`None` = keep the VM register's
1201/// live value — a pinned array, or a read-only/unknown slot).
1202struct RegionPrecise {
1203    deopt_codes: Vec<i64>,
1204    kinds_by_pc: std::collections::HashMap<usize, Vec<Option<SlotKind>>>,
1205}
1206
1207/// LIST-REGISTER discovery (shared by the adapter and the frame-size
1208/// pre-computation): every register that ever holds a list — params,
1209/// allocation sites, Move aliases, list-returning self-call results.
1210fn discover_list_regs(
1211    ops: &[Op],
1212    list_param_regs: &[u16],
1213    fn_returns_lists: bool,
1214    self_fi: u16,
1215    nregs: usize,
1216) -> Vec<bool> {
1217    let mut is_list = vec![false; nregs];
1218    for &r in list_param_regs {
1219        is_list[r as usize] = true;
1220    }
1221    loop {
1222        let mut changed = false;
1223        for op in ops {
1224            match *op {
1225                Op::NewEmptyList { dst } => {
1226                    if !is_list[dst as usize] {
1227                        is_list[dst as usize] = true;
1228                        changed = true;
1229                    }
1230                }
1231                Op::Move { dst, src } => {
1232                    if is_list[src as usize] && !is_list[dst as usize] {
1233                        is_list[dst as usize] = true;
1234                        changed = true;
1235                    }
1236                }
1237                Op::Call { dst, func, .. } if func == self_fi && fn_returns_lists => {
1238                    if !is_list[dst as usize] {
1239                        is_list[dst as usize] = true;
1240                        changed = true;
1241                    }
1242                }
1243                _ => {}
1244            }
1245        }
1246        if !changed {
1247            break;
1248        }
1249    }
1250    is_list
1251}
1252
1253fn jt(which: &str) {
1254    if std::env::var_os("LOGOS_JIT_TRACE").is_some() {
1255        eprintln!("jit-trace: mode-B bail at {which}");
1256    }
1257}
1258
1259fn adapt_function(
1260    ops: &[Op],
1261    entry_pc: usize,
1262    constants: &[Constant],
1263    param_count: u16,
1264    register_count: u16,
1265    self_fi: u16,
1266    param_kinds: &[Option<ParamKind>],
1267    declared_ret: Option<SlotKind>,
1268    call_ctx_in: &CallCtx,
1269    callees: &[CalleeSig],
1270) -> Option<AdaptedFn> {
1271    // Every jump must stay inside the body slice — a function is self-contained.
1272    for op in ops {
1273        if let Op::Jump { target }
1274        | Op::JumpIfFalse { target, .. }
1275        | Op::JumpIfTrue { target, .. } = *op
1276        {
1277            if target < entry_pc || target >= entry_pc + ops.len() {
1278                return None;
1279            }
1280        }
1281    }
1282
1283    // SOUNDNESS STOPGAP (BUG-002, NOT the root-cause fix): a function that both
1284    // (a) RETURNS/recurses on a list it MUTATES in place AND (b) loads an array
1285    // element that survives a conditional branch into an in-place `SetIndex`
1286    // miscompiles in the native tier (segfault) — the quicksort `Let vj … set
1287    // item i to vj` shape. Until the codegen is fixed, refuse to tier such a
1288    // function (it runs on the correct VM). Scoped to recursion + in-place store
1289    // so non-recursive swap loops (bubble_sort/fannkuch) still tier.
1290    // `LOGOS_BUG002_BAIL=0` disables the stopgap (to reproduce the raw crash).
1291    if std::env::var("LOGOS_BUG002_BAIL").as_deref() != Ok("0")
1292        && ops.iter().any(|op| matches!(op, Op::Call { .. }))
1293        && has_crossbranch_array_value_store_hazard(ops)
1294    {
1295        return None;
1296    }
1297
1298    // Flow-sensitive kinds: every parameter starts at its DECLARED kind
1299    // (the per-call boundary guard checks the matching discriminant before
1300    // entry, so the seed is sound, not speculative); everything else starts
1301    // Unknown and the gates inside kind_flow enforce soundness at every
1302    // reachable use.
1303    // Return kind: a declared return type pins it in one pass; otherwise
1304    // the self-recursive Int/Bool inference (assume, verify, retry) stands.
1305    if param_kinds.len() != param_count as usize {
1306        return None;
1307    }
1308    let mut param_entry: Vec<Kind> = Vec::with_capacity(param_count as usize);
1309    let mut list_params: Vec<(u16, PinElem)> = Vec::new();
1310    for (i, pk) in param_kinds.iter().enumerate() {
1311        param_entry.push(match (*pk)? {
1312            ParamKind::Scalar(SlotKind::Int) => Kind::Int,
1313            ParamKind::Scalar(SlotKind::Bool) => Kind::Bool,
1314            ParamKind::Scalar(SlotKind::Float) => Kind::Float,
1315            ParamKind::List(elem) => {
1316                list_params.push((i as u16, elem));
1317                match elem {
1318                    PinElem::Int => Kind::IntList,
1319                    PinElem::Float => Kind::FloatList,
1320                    PinElem::Bool => Kind::BoolList,
1321                    // A half-width buffer is a VM-local storage decision, never a
1322                    // declared list-param type — bail rather than speculate it.
1323                    PinElem::IntI32 => return None,
1324                    // Declared kinds never produce a Map or Text list-param.
1325                    PinElem::Map | PinElem::TextBytes | PinElem::TextMut => return None,
1326                }
1327            }
1328        });
1329    }
1330    // ALLOCATION SITES make a function mode B even without list params.
1331    let has_sites = ops.iter().any(|op| matches!(op, Op::NewEmptyList { .. }));
1332    let mode_b = !list_params.is_empty() || has_sites;
1333    // Mode B leans on the PRECISE call stencil, whose depth limit is baked
1334    // at the kernel's locked MAX_CALL_DEPTH.
1335    if mode_b && call_ctx_in.depth_limit != logicaffeine_compile::semantics::MAX_CALL_DEPTH as i64 {
1336        return None;
1337    }
1338    // Does this function RETURN lists? Declared scalar returns say no;
1339    // otherwise list params or sites make it possible — the fixpoint
1340    // verifies every return site agrees.
1341    let fn_returns_lists = declared_ret.is_none() && mode_b;
1342
1343    // LIST REGISTERS (mode B): one pin triple PER REGISTER that ever
1344    // holds a list — every list assignment (param entry, allocation,
1345    // Move alias, self-call result) REFRESHES that register's own triple,
1346    // so reassignment patterns (quicksort's `result`) cannot conflict.
1347    // Push eligibility is static: a register every one of whose list
1348    // sources is an allocation site is definitely registry-owned — a push
1349    // anywhere else could stale another holder's pinned slots.
1350    let nregs_pin = register_count as usize + 2;
1351    let list_param_regs: Vec<u16> = list_params.iter().map(|(r, _)| *r).collect();
1352    let is_list = discover_list_regs(ops, &list_param_regs, fn_returns_lists, self_fi, nregs_pin);
1353    // Provenance TAINT, FLOW-SENSITIVE: at each program point, which
1354    // registers may hold a list some OTHER holder pins (params; call
1355    // results, which may be param passthroughs). Moves propagate taint;
1356    // a NewEmptyList CLEANSES its destination (fresh, registry-owned).
1357    // Pushes are legal only into point-wise clean registers — mergesort's
1358    // `Set left to mergeSort(left)` taints `left` only AFTER the pushes.
1359    let mut ret_any_list = false;
1360    if mode_b {
1361        let in_body = |t: usize| t >= entry_pc && t < entry_pc + ops.len();
1362        let mut taint_in: Vec<Option<Vec<bool>>> = vec![None; ops.len()];
1363        let mut entry_taint = vec![false; nregs_pin];
1364        for (preg, _) in &list_params {
1365            entry_taint[*preg as usize] = true;
1366        }
1367        taint_in[0] = Some(entry_taint);
1368        let mut work: Vec<usize> = vec![0];
1369        while let Some(i) = work.pop() {
1370            let Some(cur) = taint_in[i].clone() else { continue };
1371            let mut out = cur;
1372            match ops[i] {
1373                Op::NewEmptyList { dst } => out[dst as usize] = false,
1374                Op::Move { dst, src } => out[dst as usize] = out[src as usize],
1375                Op::Call { dst, func, .. } if func == self_fi && fn_returns_lists => {
1376                    out[dst as usize] = true;
1377                }
1378                _ => {}
1379            }
1380            let mut succs: Vec<usize> = Vec::with_capacity(2);
1381            match ops[i] {
1382                Op::Jump { target } => {
1383                    if in_body(target) {
1384                        succs.push(target - entry_pc);
1385                    }
1386                }
1387                Op::JumpIfFalse { target, .. }
1388                | Op::JumpIfTrue { target, .. }
1389                => {
1390                    if i + 1 < ops.len() {
1391                        succs.push(i + 1);
1392                    }
1393                    if in_body(target) {
1394                        succs.push(target - entry_pc);
1395                    }
1396                }
1397                Op::Return { .. } | Op::ReturnNothing => {}
1398                _ => {
1399                    if i + 1 < ops.len() {
1400                        succs.push(i + 1);
1401                    }
1402                }
1403            }
1404            for sidx in succs {
1405                match &mut taint_in[sidx] {
1406                    slot @ None => {
1407                        *slot = Some(out.clone());
1408                        work.push(sidx);
1409                    }
1410                    Some(prev) => {
1411                        let mut changed = false;
1412                        for (p, &o) in prev.iter_mut().zip(&out) {
1413                            if !*p && o {
1414                                *p = true;
1415                                changed = true;
1416                            }
1417                        }
1418                        if changed {
1419                            work.push(sidx);
1420                        }
1421                    }
1422                }
1423            }
1424        }
1425        for (i, op) in ops.iter().enumerate() {
1426            match *op {
1427                Op::Return { src } => {
1428                    if is_list[src as usize] {
1429                        ret_any_list = true;
1430                    }
1431                }
1432                Op::ListPush { list, .. } => {
1433                    let clean = taint_in[i]
1434                        .as_ref()
1435                        .map(|t| is_list[list as usize] && !t[list as usize])
1436                        // Unreachable pushes never run.
1437                        .unwrap_or(true);
1438                    if !clean {
1439                        jt("push-outside-site");
1440                        return None;
1441                    }
1442                }
1443                _ => {}
1444            }
1445        }
1446    }
1447    let list_regs: Vec<u16> = (0..nregs_pin as u16).filter(|&r| is_list[r as usize]).collect();
1448    let total_pins = list_regs.len();
1449    if total_pins > 24 {
1450        return None;
1451    }
1452    let n_params = list_params.len();
1453    let _ = n_params;
1454    // pin id = index into list_regs; pin_of mirrors it per register.
1455    let mut pin_of: Vec<Option<u8>> = vec![None; nregs_pin];
1456    for (id, &r) in list_regs.iter().enumerate() {
1457        pin_of[r as usize] = Some(id as u8);
1458    }
1459    // Any list-returning site makes the self-call result an Int list
1460    // (sites are Int-lane; param pins must agree). Otherwise the declared
1461    // scalar pins one pass and undeclared falls back to Int/Bool inference.
1462    let assumed_set: Vec<Kind> = if ret_any_list {
1463        // Sites are Int-lane; list params must agree.
1464        if list_params.iter().any(|(_, e)| *e != PinElem::Int) {
1465            return None;
1466        }
1467        vec![Kind::IntList]
1468    } else {
1469        match declared_ret {
1470            Some(SlotKind::Int) => vec![Kind::Int],
1471            Some(SlotKind::Bool) => vec![Kind::Bool],
1472            Some(SlotKind::Float) => vec![Kind::Float],
1473            None => vec![Kind::Int, Kind::Bool],
1474        }
1475    };
1476    let mut chosen: Option<(Vec<Option<Vec<Kind>>>, Kind)> = None;
1477    for assumed in assumed_set {
1478        let mut entry = vec![Kind::Unknown; register_count as usize + 2];
1479        entry[..param_count as usize].copy_from_slice(&param_entry);
1480        // Self-calls resolve against the assumed signature; calls to OTHER
1481        // functions against their DECLARED all-scalar signatures (the call
1482        // stencil dispatches through the published table; an uncompiled
1483        // callee deopts at the call until it tiers up on its own). Mode B
1484        // keeps self-calls only — its pin staging is self-shaped.
1485        let resolver = |fi: u16| -> Option<(Kind, Vec<Kind>)> {
1486            if fi == self_fi {
1487                return Some((assumed, param_entry.clone()));
1488            }
1489            if mode_b {
1490                return None;
1491            }
1492            let sig = callees.get(fi as usize)?;
1493            let ret = match sig.ret? {
1494                SlotKind::Int => Kind::Int,
1495                SlotKind::Bool => Kind::Bool,
1496                SlotKind::Float => Kind::Float,
1497            };
1498            let params = sig
1499                .param_kinds
1500                .iter()
1501                .map(|k| match k {
1502                    Some(ParamKind::Scalar(SlotKind::Int)) => Some(Kind::Int),
1503                    Some(ParamKind::Scalar(SlotKind::Bool)) => Some(Kind::Bool),
1504                    Some(ParamKind::Scalar(SlotKind::Float)) => Some(Kind::Float),
1505                    _ => None,
1506                })
1507                .collect::<Option<Vec<Kind>>>()?;
1508            Some((ret, params))
1509        };
1510        let Some(kin) = kind_flow(ops, entry_pc, constants, entry, Some(&resolver))
1511        else {
1512            continue;
1513        };
1514        // The trailing implicit ReturnNothing must be dead code — a
1515        // reachable one would return Nothing, which native code cannot
1516        // represent. All reachable returns must agree on Int or Bool.
1517        let mut ret_kind: Option<Kind> = None;
1518        let mut ok = true;
1519        for (i, op) in ops.iter().enumerate() {
1520            let Some(k) = &kin[i] else { continue };
1521            match *op {
1522                Op::ReturnNothing => {
1523                    ok = false;
1524                    break;
1525                }
1526                Op::Return { src } => {
1527                    let rk = k[src as usize];
1528                    match ret_kind {
1529                        None => ret_kind = Some(rk),
1530                        Some(prev) if prev == rk => {}
1531                        _ => {
1532                            ok = false;
1533                            break;
1534                        }
1535                    }
1536                }
1537                _ => {}
1538            }
1539        }
1540        if !ok {
1541            continue;
1542        }
1543        let Some(rk) = ret_kind else { continue };
1544        if rk == assumed {
1545            chosen = Some((kin, rk));
1546            break;
1547        }
1548    }
1549    if chosen.is_none() {
1550        jt("chosen");
1551    }
1552    let (kin, ret_kind) = chosen?;
1553    let ret = if ret_any_list {
1554        NativeRet::ListByHandle
1555    } else {
1556        NativeRet::Scalar(ret_kind.slot_kind()?)
1557    };
1558
1559    // Liveness for the cmp+branch fusion (functions have no write-back, so
1560    // deadness inside the body is the whole story).
1561    let live = liveness(ops, entry_pc, register_count as usize + 2);
1562    if live.is_none() {
1563        jt("liveness");
1564    }
1565    let live = live?;
1566    let jump_targets: std::collections::HashSet<usize> = ops
1567        .iter()
1568        .filter_map(|op| match *op {
1569            Op::Jump { target }
1570            | Op::JumpIfFalse { target, .. }
1571            | Op::JumpIfTrue { target, .. }
1572            => Some(target),
1573            _ => None,
1574        })
1575        .collect();
1576    let scratch_ok = |_: u16| true;
1577
1578    let conv = (register_count, register_count + 1);
1579    let rc = register_count as usize;
1580    // Frame layout: mode A keeps the classic [regs | conv ×2 | limit]
1581    // (size rc+3). Mode B inserts the plant slots and pin triples BEFORE
1582    // the limit: [regs | conv ×2 | window | resume | dst | pins ×3p |
1583    // limit] — the published table value is frame_size − 3 either way, so
1584    // the call stencil's bound math and limit planting hold unchanged.
1585    let npins = total_pins;
1586    let frame_size = if mode_b { rc + 6 + 3 * npins } else { rc + 3 };
1587    let plant_window = (rc + 2) as u16;
1588    let plant_resume = (rc + 3) as u16;
1589    let plant_dst = (rc + 4) as u16;
1590    let pins_start = rc + 5;
1591    let mut micro: Vec<MicroOp> = Vec::with_capacity(ops.len());
1592    let mut pc_to_micro: Vec<usize> = Vec::with_capacity(ops.len());
1593    let mut fixups: Vec<usize> = Vec::new();
1594
1595    // Every pin-aliased register resolves to its pin's slot triple.
1596    let pin_elem = |j: usize| -> PinElem {
1597        let reg = list_regs[j];
1598        list_params
1599            .iter()
1600            .find(|(preg, _)| *preg == reg)
1601            .map(|(_, e)| *e)
1602            .unwrap_or(PinElem::Int)
1603    };
1604    let pin_slots_of = |j: usize| -> PinSlots {
1605        let base = pins_start + 3 * j;
1606        PinSlots {
1607            vec_slot: base as u16,
1608            ptr_slot: (base + 1) as u16,
1609            len_slot: (base + 2) as u16,
1610            elem: pin_elem(j),
1611        }
1612    };
1613    let mut pins: std::collections::HashMap<u16, PinSlots> = std::collections::HashMap::new();
1614    if mode_b {
1615        for (r, p) in pin_of.iter().enumerate() {
1616            if let Some(j) = p {
1617                pins.insert(r as u16, pin_slots_of(*j as usize));
1618            }
1619        }
1620        // Prologue: my plant is invalid until I make a call (the precise
1621        // walk stops at the first invalid plant).
1622        micro.push(MicroOp::LoadConst { dst: plant_window, value: -1 });
1623    }
1624
1625    // Per-pause-point register re-box kinds (deopt sites + call sites).
1626    // Param-pinned list registers re-box as their argument; NATIVE-OWNED
1627    // pins (sites, call results) record their vec-handle slot so the
1628    // deopt walk can detach-or-match them.
1629    let mut kinds_by_pc: std::collections::HashMap<usize, Vec<RegBox>> =
1630        std::collections::HashMap::new();
1631    let mut owned_by_pc: std::collections::HashMap<usize, Vec<(u16, u16)>> =
1632        std::collections::HashMap::new();
1633    let mut capture = |i: usize,
1634                       kin: &Vec<Option<Vec<Kind>>>,
1635                       pin_of: &Vec<Option<u8>>|
1636     -> Option<()> {
1637        let k = kin[i].as_ref()?;
1638        let mut row = Vec::with_capacity(rc);
1639        let mut owned: Vec<(u16, u16)> = Vec::new();
1640        for r in 0..rc {
1641            row.push(match k[r] {
1642                Kind::Int => RegBox::Int,
1643                Kind::Bool => RegBox::Bool,
1644                Kind::Float => RegBox::Float,
1645                Kind::IntList | Kind::IntListI32 | Kind::FloatList | Kind::BoolList => {
1646                    let j = pin_of[r]?;
1647                    if let Some(pidx) =
1648                        list_params.iter().position(|(preg, _)| *preg as usize == r)
1649                    {
1650                        RegBox::ListParam(pidx as u8)
1651                    } else {
1652                        owned.push((r as u16, pin_slots_of(j as usize).vec_slot));
1653                        RegBox::Resolved
1654                    }
1655                }
1656                _ => RegBox::Dead,
1657            });
1658        }
1659        kinds_by_pc.insert(entry_pc + i, row);
1660        owned_by_pc.insert(entry_pc + i, owned);
1661        Some(())
1662    };
1663
1664    let mut i = 0usize;
1665    while i < ops.len() {
1666        if mode_b {
1667            let deoptable = matches!(
1668                ops[i],
1669                Op::Div { .. }
1670                    | Op::Mod { .. }
1671                    | Op::Index { .. }
1672                    | Op::IndexUnchecked { .. }
1673                    | Op::SetIndex { .. }
1674                    | Op::SetIndexUnchecked { .. }
1675                    | Op::Call { .. }
1676            );
1677            if deoptable && kin[i].is_some() {
1678                if capture(i, &kin, &pin_of).is_none() {
1679                    jt("capture");
1680                    return None;
1681                }
1682            }
1683            // Self-call, mode B: the callee windows at base + FRAME_SIZE —
1684            // fully DISJOINT from this frame, because the interpreter's
1685            // compact windowing (base + args_start) would let the callee's
1686            // registers overlap this frame's plant and pin slots and
1687            // clobber them. Arguments are copied into the fresh window,
1688            // pins pass through, the plant records the linkage for the
1689            // precise walk, and the post-return store invalidates it.
1690            if let Op::Call { dst, args_start, arg_count, func } = ops[i] {
1691                // Mode B stages pins for OUR OWN parameter shape — a call
1692                // to any other function cannot use this seam.
1693                if func != self_fi {
1694                    jt("non-self-call-mode-b");
1695                    return None;
1696                }
1697                pc_to_micro.push(micro.len());
1698                let window = frame_size as u16;
1699                micro.push(MicroOp::LoadConst { dst: plant_window, value: window as i64 });
1700                micro.push(MicroOp::LoadConst {
1701                    dst: plant_resume,
1702                    value: (entry_pc + i + 1) as i64,
1703                });
1704                micro.push(MicroOp::LoadConst { dst: plant_dst, value: dst as i64 });
1705                for j in 0..arg_count {
1706                    micro.push(MicroOp::Move { dst: window + j, src: args_start + j });
1707                }
1708                // Each callee list-param pin receives the triple of WHATEVER
1709                // pin the staged argument carries (a param passthrough, a
1710                // fresh site, or a previous call's result).
1711                for (slot, (preg, pelem)) in list_params.iter().enumerate() {
1712                    if *pelem != PinElem::Int && *pelem != PinElem::Float && *pelem != PinElem::Bool
1713                    {
1714                        return None;
1715                    }
1716                    let Some(arg_pin) = pin_of[(args_start + preg) as usize] else {
1717                        jt("staged-arg-unpinned");
1718                        return None;
1719                    };
1720                    let src_slots = pin_slots_of(arg_pin as usize);
1721                    let dst_base = window + (pins_start + 3 * slot) as u16;
1722                    micro.push(MicroOp::Move { dst: dst_base, src: src_slots.vec_slot });
1723                    micro.push(MicroOp::Move { dst: dst_base + 1, src: src_slots.ptr_slot });
1724                    micro.push(MicroOp::Move { dst: dst_base + 2, src: src_slots.len_slot });
1725                }
1726                let c = call_ctx_in;
1727                micro.push(MicroOp::Call {
1728                    dst,
1729                    args_start: window,
1730                    table_addr: c.table_addr,
1731                    depth_addr: c.depth_addr,
1732                    status_addr: c.status_addr,
1733                    limit_slot: c.limit_slot,
1734                    depth_limit: c.depth_limit,
1735                });
1736                micro.push(MicroOp::LoadConst { dst: plant_window, value: -1 });
1737                // A list-returning self-call: plant the result's triple
1738                // from the returned handle.
1739                if fn_returns_lists {
1740                    if let Some(j) = pin_of[dst as usize] {
1741                        let slots = pin_slots_of(j as usize);
1742                        micro.push(MicroOp::ListTriple {
1743                            handle_slot: dst,
1744                            vec_slot: slots.vec_slot,
1745                            ptr_slot: slots.ptr_slot,
1746                            len_slot: slots.len_slot,
1747                            helper_addr: logos_rt_list_triple as usize as i64,
1748                        });
1749                    }
1750                }
1751                i += 1;
1752                continue;
1753            }
1754            // Fresh-list allocation: registry-owned, triple planted.
1755            if let Op::NewEmptyList { dst } = ops[i] {
1756                pc_to_micro.push(micro.len());
1757                let j = pin_of[dst as usize].expect("site pins were assigned");
1758                let slots = pin_slots_of(j as usize);
1759                micro.push(MicroOp::NewList {
1760                    vec_slot: slots.vec_slot,
1761                    ptr_slot: slots.ptr_slot,
1762                    len_slot: slots.len_slot,
1763                    helper_addr: logos_rt_alloc_list_i64 as usize as i64,
1764                });
1765                // INVARIANT: a list register's frame slot mirrors its vec
1766                // handle (Moves, staging and returns all ride it).
1767                micro.push(MicroOp::Move { dst, src: slots.vec_slot });
1768                i += 1;
1769                continue;
1770            }
1771            // A Move BETWEEN list registers transfers the handle and
1772            // refreshes the destination's own pin triple from the live
1773            // vector (per-register pins make reassignment conflict-free).
1774            if let Op::Move { dst, src } = ops[i] {
1775                if is_list[src as usize] {
1776                    pc_to_micro.push(micro.len());
1777                    let j = pin_of[dst as usize].expect("list regs are pinned");
1778                    let slots = pin_slots_of(j as usize);
1779                    micro.push(MicroOp::Move { dst, src });
1780                    micro.push(MicroOp::ListTriple {
1781                        handle_slot: dst,
1782                        vec_slot: slots.vec_slot,
1783                        ptr_slot: slots.ptr_slot,
1784                        len_slot: slots.len_slot,
1785                        helper_addr: logos_rt_list_triple as usize as i64,
1786                    });
1787                    i += 1;
1788                    continue;
1789                }
1790            }
1791            // List return: the vec HANDLE rides the return value (the
1792            // boundary detaches registry-owned lists or matches a param).
1793            if let Op::Return { src } = ops[i] {
1794                if let Some(j) = pin_of[src as usize] {
1795                    pc_to_micro.push(micro.len());
1796                    let slots = pin_slots_of(j as usize);
1797                    micro.push(MicroOp::Move { dst: conv.0, src: slots.vec_slot });
1798                    micro.push(MicroOp::Return { src: conv.0 });
1799                    i += 1;
1800                    continue;
1801                }
1802            }
1803            // OPERATION FUSION (nbody's hot lever): collapse
1804            //   `t1 = item I of A; t2 = item J of A; D = t1 <op> t2`
1805            // — two loads from the SAME pinned 8-byte float array feeding one
1806            // float add/sub/mul — into a single ArrLoad2F so the two loaded
1807            // f64s never round-trip through the frame. Both scratch loads must
1808            // be single-use (dead after the binop, read only by it), the two
1809            // collapsed ops must not be jump targets (no entry lands between),
1810            // and a bounds failure side-exits to the FIRST Index's bytecode pc
1811            // (captured above, since Index is deoptable) — resuming there
1812            // re-runs all three pure ops, every effect still confined.
1813            if let Some(fused) = fuse_index2_fbinop(
1814                ops, i, entry_pc, &kin, &live, &jump_targets, &pins,
1815            ) {
1816                if std::env::var_os("LOGOS_JIT_TRACE").is_some() {
1817                    eprintln!("jit-trace: FUSED arrld2 at pc {} -> {:?}", entry_pc + i, fused);
1818                }
1819                pc_to_micro.push(micro.len());
1820                micro.push(fused);
1821                // The j-Index (i+1) and the binop (i+2) map PAST the fused
1822                // micro, so the first Index's deopt range covers exactly it.
1823                pc_to_micro.push(micro.len());
1824                pc_to_micro.push(micro.len());
1825                i += 3;
1826                continue;
1827            }
1828        }
1829        let step = translate_op(
1830            ops,
1831            i,
1832            entry_pc,
1833            constants,
1834            &kin,
1835            &live,
1836            &jump_targets,
1837            &scratch_ok,
1838            conv,
1839            &pins,
1840            Some(call_ctx_in),
1841            None,
1842            true,
1843            false,
1844            &mut micro,
1845            &mut pc_to_micro,
1846            &mut fixups,
1847        );
1848        if step.is_none() {
1849            jt("translate");
1850            if std::env::var_os("LOGOS_JIT_TRACE").is_some() {
1851                eprintln!("jit-trace:   op {:?}", ops[i]);
1852            }
1853        }
1854        i += step?;
1855    }
1856    for &fi in &fixups {
1857        match &mut micro[fi] {
1858            MicroOp::Jump { target }
1859            | MicroOp::JumpIfFalse { target, .. }
1860            | MicroOp::JumpIfTrue { target, .. }
1861            | MicroOp::Branch { target, .. }
1862            | MicroOp::BranchF { target, .. } => {
1863                let rebased = target.checked_sub(entry_pc)?;
1864                *target = *pc_to_micro.get(rebased)?;
1865            }
1866            _ => unreachable!(),
1867        }
1868    }
1869    // Function bodies may end on a Return-less path only if the VM never
1870    // reaches it; the chain compiler requires structural termination.
1871    if !matches!(micro.last(), Some(MicroOp::Return { .. }) | Some(MicroOp::Jump { .. })) {
1872        return None;
1873    }
1874    // Mode B: every micro of bytecode op i carries that op's encoded pc
1875    // so its side exit resumes precisely.
1876    let deopt_codes = if mode_b {
1877        let mut codes = vec![1i64; micro.len()];
1878        for opi in 0..ops.len() {
1879            let lo = pc_to_micro[opi];
1880            let hi = pc_to_micro.get(opi + 1).copied().unwrap_or(micro.len());
1881            let code = (((entry_pc + opi) as i64) << 2) | 3;
1882            for c in codes.iter_mut().take(hi).skip(lo) {
1883                *c = code;
1884            }
1885        }
1886        Some(codes)
1887    } else {
1888        None
1889    };
1890    let param_pin_scatter: Vec<u16> = list_params
1891        .iter()
1892        .map(|(preg, _)| pin_slots_of(pin_of[*preg as usize].unwrap() as usize).vec_slot)
1893        .collect();
1894    let precise = if mode_b {
1895        Some(PreciseInfo {
1896            register_count: rc,
1897            pins_start,
1898            kinds_by_pc,
1899            owned_by_pc,
1900            param_pin_scatter,
1901        })
1902    } else {
1903        None
1904    };
1905    Some(AdaptedFn { micro, frame_size, ret, deopt_codes, precise })
1906}
1907
1908/// If slot `rhs` is a region-constant power of two — its ONLY writer in `ops`
1909/// is a `LoadConst` of `2^k` with `1 <= k <= 62` — return `k`, so an integer
1910/// `x / rhs` can lower to the side-exit-free [`MicroOp::DivPow2`] shift instead
1911/// of an `idiv`. Sound: a single constant writer means `rhs` holds exactly
1912/// `2^k` at every `Div`, in every loop iteration. Any other writer (or an op
1913/// whose defs we cannot determine) disqualifies it.
1914fn const_pow2_div(ops: &[Op], rhs: u16, constants: &[Constant]) -> Option<u32> {
1915    let mut k_found: Option<u32> = None;
1916    for op in ops {
1917        let defs = region_use_def(op)?.1;
1918        if !defs.contains(&rhs) {
1919            continue;
1920        }
1921        match op {
1922            Op::LoadConst { dst, idx } if *dst == rhs => match constants.get(*idx as usize) {
1923                Some(Constant::Int(d)) if *d >= 2 && (*d & (*d - 1)) == 0 => {
1924                    let k = d.trailing_zeros();
1925                    if !(1..=62).contains(&k) || k_found.is_some() {
1926                        return None;
1927                    }
1928                    k_found = Some(k);
1929                }
1930                _ => return None,
1931            },
1932            _ => return None,
1933        }
1934    }
1935    k_found
1936}
1937
1938/// The use/def sets of one op from the adaptable subset, or None when the op
1939/// is outside it. `AddAssign` reads AND writes its dst.
1940fn region_use_def(op: &Op) -> Option<(Vec<u16>, Vec<u16>)> {
1941    Some(match *op {
1942        Op::Return { src } => (vec![src], vec![]),
1943        Op::ReturnNothing => (vec![], vec![]),
1944        Op::LoadConst { dst, .. } => (vec![], vec![dst]),
1945        Op::DivPow2 { dst, lhs, .. } => (vec![lhs], vec![dst]),
1946        Op::MagicDivU { dst, lhs, .. } => (vec![lhs], vec![dst]),
1947        Op::Move { dst, src } => (vec![src], vec![dst]),
1948        // Interpreter-only call-site COW barrier: a no-op in a native region (the
1949        // pinned-buffer / list-param mechanism already governs value semantics here,
1950        // exactly as it did before the barrier existed). Reads `reg` to keep it live.
1951        Op::EnsureOwned { reg } => (vec![reg], vec![]),
1952        Op::BitXor { dst, lhs, rhs }
1953        | Op::BitAnd { dst, lhs, rhs }
1954        | Op::BitOr { dst, lhs, rhs }
1955        | Op::Shl { dst, lhs, rhs }
1956        | Op::Shr { dst, lhs, rhs } => (vec![lhs, rhs], vec![dst]),
1957        Op::Not { dst, src } => (vec![src], vec![dst]),
1958        Op::Index { dst, collection, index }
1959        | Op::IndexUnchecked { dst, collection, index } => (vec![collection, index], vec![dst]),
1960        Op::Contains { dst, collection, value } => (vec![collection, value], vec![dst]),
1961        Op::NewEmptyList { dst } => (vec![], vec![dst]),
1962        Op::SetIndex { collection, index, value }
1963        | Op::SetIndexUnchecked { collection, index, value } => {
1964            (vec![collection, index, value], vec![])
1965        }
1966        Op::Length { dst, collection } => (vec![collection], vec![dst]),
1967        Op::ListPush { list, value } => (vec![list, value], vec![]),
1968        Op::Call { dst, args_start, arg_count, .. } => {
1969            ((args_start..args_start + arg_count).collect(), vec![dst])
1970        }
1971        Op::CallBuiltin {
1972            dst,
1973            builtin: logicaffeine_compile::semantics::builtins::BuiltinId::Sqrt,
1974            args_start,
1975            arg_count,
1976        } if arg_count == 1 => (vec![args_start], vec![dst]),
1977        Op::Add { dst, lhs, rhs } | Op::Sub { dst, lhs, rhs } | Op::Mul { dst, lhs, rhs }
1978        | Op::Div { dst, lhs, rhs } | Op::Mod { dst, lhs, rhs }
1979        | Op::Lt { dst, lhs, rhs } | Op::Gt { dst, lhs, rhs } | Op::LtEq { dst, lhs, rhs }
1980        | Op::GtEq { dst, lhs, rhs } | Op::Eq { dst, lhs, rhs } | Op::NotEq { dst, lhs, rhs } => {
1981            (vec![lhs, rhs], vec![dst])
1982        }
1983        Op::AddAssign { dst, src } => (vec![dst, src], vec![dst]),
1984        Op::JumpIfFalse { cond, .. } | Op::JumpIfTrue { cond, .. } => (vec![cond], vec![]),
1985        Op::Jump { .. } => (vec![], vec![]),
1986        // Region-entry metadata: emits no native code (the VM verifies it at
1987        // entry from its own registers + the pinned length). No frame use/def.
1988        Op::RegionBoundsGuard { .. } => (vec![], vec![]),
1989        _ => return None,
1990    })
1991}
1992
1993/// Backward liveness over a bytecode slice to a fixpoint: per-op LIVE-IN
1994/// vectors. Successors are the fall-through and the in-slice jump target;
1995/// out-of-slice targets are exits with empty live-out (exit-observable slots
1996/// flow through the caller's write-back, never the native frame), and
1997/// `Return`/`ReturnNothing` are terminals. None when the slice contains an
1998/// op outside the adaptable subset.
1999fn liveness(ops: &[Op], base_pc: usize, nregs: usize) -> Option<Vec<Vec<bool>>> {
2000    let in_slice = |t: usize| (base_pc..base_pc + ops.len()).contains(&t);
2001    fn merge_into(out: &mut [bool], row: &[bool]) {
2002        for (o, &l) in out.iter_mut().zip(row) {
2003            *o |= l;
2004        }
2005    }
2006    let mut live: Vec<Vec<bool>> = vec![vec![false; nregs]; ops.len()];
2007    loop {
2008        let mut changed = false;
2009        for i in (0..ops.len()).rev() {
2010            let Some((uses, defs)) = region_use_def(&ops[i]) else {
2011                if std::env::var_os("LOGOS_RDIAG").is_some() {
2012                    eprintln!("RDIAG-BAIL liveness use_def-unsupported op={:?}", ops[i]);
2013                }
2014                return None;
2015            };
2016            let mut out = vec![false; nregs];
2017            match ops[i] {
2018                Op::Jump { target } => {
2019                    if in_slice(target) {
2020                        merge_into(&mut out, &live[target - base_pc]);
2021                    }
2022                }
2023                Op::JumpIfFalse { target, .. }
2024                | Op::JumpIfTrue { target, .. }
2025                => {
2026                    if i + 1 < ops.len() {
2027                        merge_into(&mut out, &live[i + 1]);
2028                    }
2029                    if in_slice(target) {
2030                        merge_into(&mut out, &live[target - base_pc]);
2031                    }
2032                }
2033                Op::Return { .. } | Op::ReturnNothing => {}
2034                _ => {
2035                    if i + 1 < ops.len() {
2036                        merge_into(&mut out, &live[i + 1]);
2037                    }
2038                }
2039            }
2040            for &d in &defs {
2041                out[d as usize] = false;
2042            }
2043            for &u in &uses {
2044                out[u as usize] = true;
2045            }
2046            if out != live[i] {
2047                live[i] = out;
2048                changed = true;
2049            }
2050        }
2051        if !changed {
2052            break;
2053        }
2054    }
2055    Some(live)
2056}
2057
2058/// The [`Cmp`] a comparison op computes, if it is one.
2059fn cmp_of(op: &Op) -> Option<(Cmp, u16, u16, u16)> {
2060    Some(match *op {
2061        Op::Lt { dst, lhs, rhs } => (Cmp::Lt, dst, lhs, rhs),
2062        Op::Gt { dst, lhs, rhs } => (Cmp::Gt, dst, lhs, rhs),
2063        Op::LtEq { dst, lhs, rhs } => (Cmp::LtEq, dst, lhs, rhs),
2064        Op::GtEq { dst, lhs, rhs } => (Cmp::GtEq, dst, lhs, rhs),
2065        Op::Eq { dst, lhs, rhs } => (Cmp::Eq, dst, lhs, rhs),
2066        Op::NotEq { dst, lhs, rhs } => (Cmp::NotEq, dst, lhs, rhs),
2067        _ => return None,
2068    })
2069}
2070
2071/// When `ops[i]` is a comparison whose dst feeds ONLY the immediately
2072/// following conditional jump, return the fused [`MicroOp::Branch`]
2073/// parameters `(cmp, lhs, rhs, vm_target)` — `cmp` already negated for the
2074/// JumpIfTrue polarity (Branch jumps when its cmp is FALSE). Conditions:
2075/// the dst is dead after the jump (per `live`), no jump lands BETWEEN the
2076/// pair, and `scratch_ok(dst)` (regions: the dst must be an unobservable
2077/// scratch — its value write disappears under fusion).
2078fn fuse_cmp_branch(
2079    ops: &[Op],
2080    i: usize,
2081    base_pc: usize,
2082    live: &[Vec<bool>],
2083    jump_targets: &std::collections::HashSet<usize>,
2084    scratch_ok: &dyn Fn(u16) -> bool,
2085) -> Option<(Cmp, u16, u16, usize)> {
2086    let (cmp, dst, lhs, rhs) = cmp_of(&ops[i])?;
2087    if i + 1 >= ops.len() || jump_targets.contains(&(base_pc + i + 1)) {
2088        return None;
2089    }
2090    let (fused_cmp, target) = match ops[i + 1] {
2091        Op::JumpIfFalse { cond, target } if cond == dst => (cmp, target),
2092        Op::JumpIfTrue { cond, target } if cond == dst => (cmp.negated(), target),
2093        _ => return None,
2094    };
2095    if !scratch_ok(dst) {
2096        return None;
2097    }
2098    // Dead after the jump: not live-in at the fall-through nor at an
2099    // in-slice target (out-of-slice exits are covered by scratch_ok).
2100    let in_slice = |t: usize| (base_pc..base_pc + ops.len()).contains(&t);
2101    if i + 2 < ops.len() && live[i + 2][dst as usize] {
2102        return None;
2103    }
2104    if in_slice(target) && live[target - base_pc][dst as usize] {
2105        return None;
2106    }
2107    Some((fused_cmp, lhs, rhs, target))
2108}
2109
2110/// Detect the fusable triple at `ops[i]`:
2111///   `t1 = item I of A; t2 = item J of A; D = t1 <op> t2`
2112/// — two loads from the SAME pinned 8-byte float array (`A`), both feeding a
2113/// single float add/sub/mul. Returns the collapsed [`MicroOp::ArrLoad2F`].
2114///
2115/// Conditions (all required for soundness/no-loss):
2116/// - the two `item` reads target the SAME pinned collection, which is a
2117///   list of 8-byte FLOAT elements (not a map, not a byte/Bool buffer);
2118/// - both loaded values are FLOATS at the binop (no Int→Float promotion is
2119///   folded — the fused stencil reads raw f64 bits straight from the buffer);
2120/// - the binop's operands are exactly the two scratch loads (for the
2121///   commutative Add/Mul either order; for Sub only `t1 - t2`);
2122/// - both scratches are SINGLE-USE: distinct, dead after the binop, and read
2123///   by nothing between (the three ops are adjacent and i+1/i+2 are not jump
2124///   targets, so no other reader can sneak in);
2125/// - the destination is not one of the scratch slots (the binop overwrites
2126///   its inputs only after both are read — the fused stencil does the same,
2127///   but keeping them distinct avoids any aliasing surprise).
2128fn fuse_index2_fbinop(
2129    ops: &[Op],
2130    i: usize,
2131    base_pc: usize,
2132    kin: &[Option<Vec<Kind>>],
2133    live: &[Vec<bool>],
2134    jump_targets: &std::collections::HashSet<usize>,
2135    pins: &std::collections::HashMap<u16, PinSlots>,
2136) -> Option<MicroOp> {
2137    if i + 3 > ops.len() {
2138        return None;
2139    }
2140    // No fresh entry may land on the second load or the binop, or a jump
2141    // target would skip the first load's effects.
2142    if jump_targets.contains(&(base_pc + i + 1)) || jump_targets.contains(&(base_pc + i + 2)) {
2143        return None;
2144    }
2145    // The first load is the deopt resume point; its kind capture must exist
2146    // (the mode-B loop captures every deoptable op with a known kind row).
2147    if kin.get(i)?.is_none() {
2148        return None;
2149    }
2150    let (t1, coll_a, ix) = match ops[i] {
2151        Op::Index { dst, collection, index }
2152        | Op::IndexUnchecked { dst, collection, index } => (dst, collection, index),
2153        _ => return None,
2154    };
2155    let (t2, coll_b, jx) = match ops[i + 1] {
2156        Op::Index { dst, collection, index }
2157        | Op::IndexUnchecked { dst, collection, index } => (dst, collection, index),
2158        _ => return None,
2159    };
2160    if coll_a != coll_b || t1 == t2 {
2161        return None;
2162    }
2163    let (op, dst, lhs, rhs) = match ops[i + 2] {
2164        Op::Add { dst, lhs, rhs } => (FOp::Add, dst, lhs, rhs),
2165        Op::Sub { dst, lhs, rhs } => (FOp::Sub, dst, lhs, rhs),
2166        Op::Mul { dst, lhs, rhs } => (FOp::Mul, dst, lhs, rhs),
2167        _ => return None,
2168    };
2169    // The binop must consume exactly the two scratch loads, mapping i→j so
2170    // the result equals `f(A[I]) <op> f(A[J])`. Add/Mul commute; Sub does not.
2171    let (load_i, load_j) = if lhs == t1 && rhs == t2 {
2172        (ix, jx)
2173    } else if (op == FOp::Add || op == FOp::Mul) && lhs == t2 && rhs == t1 {
2174        (jx, ix)
2175    } else {
2176        return None;
2177    };
2178    if dst == t1 || dst == t2 {
2179        return None;
2180    }
2181    // The collection is a pinned list of 8-byte float elements.
2182    let p = pins.get(&coll_a)?;
2183    if p.elem != PinElem::Float {
2184        return None;
2185    }
2186    // Both loaded values are floats at the binop (the fused stencil reads raw
2187    // f64 bits — an Int element would need a promotion the stencil omits).
2188    let k = kin.get(i + 2)?.as_ref()?;
2189    if k[t1 as usize] != Kind::Float || k[t2 as usize] != Kind::Float {
2190        return None;
2191    }
2192    // Single-use: both scratches dead entering the op after the binop, so the
2193    // collapse drops their frame writes with no observable loss.
2194    let after = live.get(i + 3)?;
2195    if after[t1 as usize] || after[t2 as usize] {
2196        return None;
2197    }
2198    Some(MicroOp::ArrLoad2F {
2199        dst,
2200        i: load_i,
2201        j: load_j,
2202        ptr_slot: p.ptr_slot,
2203        len_slot: p.len_slot,
2204        op,
2205    })
2206}
2207
2208/// Translate the VM op at `i` (or a fused cmp+branch pair) into micro-ops,
2209/// choosing Int or Float forms from the kinds holding at that point and
2210/// inserting the kernel's Int→Float promotion through the two conversion
2211/// scratch slots. Jump-family micro targets keep their RAW VM pcs — the
2212/// caller remaps them (and routes out-of-slice exits). Pushes the pc→micro
2213/// map entries for every VM op it consumes; returns how many it consumed,
2214/// or None when the op (or its kind shape) is outside the subset.
2215#[allow(clippy::too_many_arguments)]
2216fn translate_op(
2217    ops: &[Op],
2218    i: usize,
2219    base_pc: usize,
2220    constants: &[Constant],
2221    kin: &[Option<Vec<Kind>>],
2222    live: &[Vec<bool>],
2223    jump_targets: &std::collections::HashSet<usize>,
2224    scratch_ok: &dyn Fn(u16) -> bool,
2225    conv: (u16, u16),
2226    pins: &std::collections::HashMap<u16, PinSlots>,
2227    call_ctx: Option<&CallCtx>,
2228    region_return: Option<(u16, u16)>,
2229    allow_return: bool,
2230    region: bool,
2231    micro: &mut Vec<MicroOp>,
2232    pc_to_micro: &mut Vec<usize>,
2233    fixups: &mut Vec<usize>,
2234) -> Option<usize> {
2235    pc_to_micro.push(micro.len());
2236    let k = kin[i].as_ref();
2237    // Unreachable ops never execute — emit a placeholder of valid shape so
2238    // pc mapping and structural termination hold.
2239    let kind_of = |r: u16| k.map(|kk| kk[r as usize]).unwrap_or(Kind::Int);
2240    // Resolve a numeric operand to (slot, is_float), inserting a conversion
2241    // into `cslot` when the other side forces a Float promotion.
2242    let promote = |micro: &mut Vec<MicroOp>, slot: u16, kind: Kind, want_float: bool, cslot: u16| -> u16 {
2243        if want_float && kind == Kind::Int {
2244            micro.push(MicroOp::IntToFloat { dst: cslot, src: slot });
2245            cslot
2246        } else {
2247            slot
2248        }
2249    };
2250
2251    // Fused compare-and-branch first.
2252    if let Some((cmp, lhs, rhs, target)) =
2253        fuse_cmp_branch(ops, i, base_pc, live, jump_targets, scratch_ok)
2254    {
2255        let (lk, rk) = (kind_of(lhs), kind_of(rhs));
2256        let bools = lk == Kind::Bool && rk == Kind::Bool;
2257        if bools && matches!(cmp, Cmp::Eq | Cmp::NotEq) {
2258            pc_to_micro.push(micro.len());
2259            fixups.push(micro.len());
2260            micro.push(MicroOp::Branch { cmp, lhs, rhs, target });
2261            return Some(2);
2262        }
2263        let want_float = lk == Kind::Float || rk == Kind::Float;
2264        if want_float {
2265            let l = promote(micro, lhs, lk, true, conv.0);
2266            let r = promote(micro, rhs, rk, true, conv.1);
2267            pc_to_micro.push(micro.len());
2268            fixups.push(micro.len());
2269            micro.push(MicroOp::BranchF { cmp, lhs: l, rhs: r, target });
2270        } else {
2271            pc_to_micro.push(micro.len());
2272            fixups.push(micro.len());
2273            micro.push(MicroOp::Branch { cmp, lhs, rhs, target });
2274        }
2275        return Some(2);
2276    }
2277
2278    match ops[i] {
2279        Op::LoadConst { dst, idx } => match constants.get(idx as usize)? {
2280            Constant::Int(v) => micro.push(MicroOp::LoadConst { dst, value: *v }),
2281            Constant::Float(f) => {
2282                micro.push(MicroOp::LoadConst { dst, value: f.to_bits() as i64 })
2283            }
2284            Constant::Bool(b) => micro.push(MicroOp::LoadConst { dst, value: *b as i64 }),
2285            // A 1-char ASCII text/char literal lowers to its single byte so a
2286            // `Kind::TextByte` comparison against it is an integer compare (the
2287            // strings benchmark's `item i of result equals " "`).
2288            Constant::Char(c) if c.is_ascii() => {
2289                micro.push(MicroOp::LoadConst { dst, value: *c as i64 })
2290            }
2291            Constant::Text(s) if text_const_byte(s).is_some() => {
2292                micro.push(MicroOp::LoadConst { dst, value: text_const_byte(s).unwrap() as i64 })
2293            }
2294            // A MULTI-char ASCII text constant (`text + "XXXXX"`) is consumed ONLY
2295            // as a `StrAppend` source (`Kind::TextConst`, rejected by every other
2296            // gate), which bakes the literal's bytes directly — its frame slot is
2297            // never read as a value. Emit a harmless placeholder so the pc→micro
2298            // mapping stays 1:1 without materializing the (non-scalar) string.
2299            Constant::Text(s) if s.is_ascii() && !s.is_empty() => {
2300                micro.push(MicroOp::LoadConst { dst, value: 0 })
2301            }
2302            _ => return None,
2303        },
2304        Op::Move { dst, src } => micro.push(MicroOp::Move { dst, src }),
2305        Op::EnsureOwned { .. } => {} // interpreter-only COW barrier — no-op in a native region
2306        Op::DivPow2 { dst, lhs, k } => micro.push(MicroOp::DivPow2 { dst, lhs, k: k as u32 }),
2307        Op::MagicDivU { dst, lhs, magic, more, mul_back } => {
2308            micro.push(MicroOp::MagicDivU { dst, lhs, magic, more, mul_back })
2309        }
2310        Op::Add { dst, lhs, rhs }
2311        | Op::Sub { dst, lhs, rhs }
2312        | Op::Mul { dst, lhs, rhs }
2313        | Op::Div { dst, lhs, rhs } => {
2314            let (lk, rk) = (kind_of(lhs), kind_of(rhs));
2315            if lk == Kind::Float || rk == Kind::Float {
2316                let l = promote(micro, lhs, lk, true, conv.0);
2317                let r = promote(micro, rhs, rk, true, conv.1);
2318                micro.push(match ops[i] {
2319                    Op::Add { .. } => MicroOp::AddF { dst, lhs: l, rhs: r },
2320                    Op::Sub { .. } => MicroOp::SubF { dst, lhs: l, rhs: r },
2321                    Op::Mul { .. } => MicroOp::MulF { dst, lhs: l, rhs: r },
2322                    _ => MicroOp::DivF { dst, lhs: l, rhs: r },
2323                });
2324            } else {
2325                micro.push(match ops[i] {
2326                    Op::Add { .. } => MicroOp::Add { dst, lhs, rhs },
2327                    Op::Sub { .. } => MicroOp::Sub { dst, lhs, rhs },
2328                    Op::Mul { .. } => MicroOp::Mul { dst, lhs, rhs },
2329                    // integer `x / 2^k` → side-exit-free shift when the divisor
2330                    // is a region-constant power of two (collatz, heap_sort i/2,
2331                    // mergesort (lo+hi)/2).
2332                    _ => match const_pow2_div(ops, rhs, constants) {
2333                        Some(k) => MicroOp::DivPow2 { dst, lhs, k },
2334                        None => MicroOp::Div { dst, lhs, rhs },
2335                    },
2336                });
2337            }
2338        }
2339        Op::AddAssign { dst, src } => {
2340            let (dk, sk) = (kind_of(dst), kind_of(src));
2341            if dk == Kind::TextMut {
2342                // A pinned mutable-Text accumulator grows through the `StrAppend`
2343                // helper. The handle is the pin's vec slot (a `*mut Value`). The
2344                // source is a 1-char runtime byte (`TextByte` — the byte VALUE
2345                // rides its frame slot) or a multi-char ASCII constant
2346                // (`TextConst` — recover the literal's bytes and bake a `'static`
2347                // slice). The handle slot is force-frame-resident so a non-frame
2348                // (register-resident) handle is impossible here.
2349                let p = pins.get(&dst)?;
2350                let strsrc = match sk {
2351                    Kind::TextByte => logicaffeine_forge::jit::StrSrc::Byte(src),
2352                    Kind::TextConst => {
2353                        let (ptr, len) = const_text_slice(ops, i, base_pc, src, constants)?;
2354                        logicaffeine_forge::jit::StrSrc::Const { ptr, len }
2355                    }
2356                    _ => return None,
2357                };
2358                micro.push(MicroOp::StrAppend {
2359                    text_handle_slot: p.vec_slot,
2360                    src: strsrc,
2361                    helper_addr: crate::logos_rt_str_append as usize as i64,
2362                });
2363            } else if dk == Kind::Float || sk == Kind::Float {
2364                let l = promote(micro, dst, dk, true, conv.0);
2365                let r = promote(micro, src, sk, true, conv.1);
2366                micro.push(MicroOp::AddF { dst, lhs: l, rhs: r });
2367            } else {
2368                micro.push(MicroOp::Add { dst, lhs: dst, rhs: src });
2369            }
2370        }
2371        Op::Mod { dst, lhs, rhs } => micro.push(MicroOp::Mod { dst, lhs, rhs }),
2372        Op::BitXor { dst, lhs, rhs } => micro.push(MicroOp::BitXor { dst, lhs, rhs }),
2373        Op::Shl { dst, lhs, rhs } => micro.push(MicroOp::Shl { dst, lhs, rhs }),
2374        Op::Shr { dst, lhs, rhs } => micro.push(MicroOp::Shr { dst, lhs, rhs }),
2375        Op::BitAnd { dst, lhs, rhs } => micro.push(MicroOp::BitAnd { dst, lhs, rhs }),
2376        Op::BitOr { dst, lhs, rhs } => micro.push(MicroOp::BitOr { dst, lhs, rhs }),
2377        Op::Not { dst, src } => match kind_of(src) {
2378            Kind::Bool => micro.push(MicroOp::NotBool { dst, src }),
2379            // The region guard admits `Not` only on Bool; a bitwise NotInt
2380            // here would silently miscompile the logical `not` — fail closed.
2381            _ => return None,
2382        },
2383        Op::Lt { dst, lhs, rhs }
2384        | Op::Gt { dst, lhs, rhs }
2385        | Op::LtEq { dst, lhs, rhs }
2386        | Op::GtEq { dst, lhs, rhs }
2387        | Op::Eq { dst, lhs, rhs }
2388        | Op::NotEq { dst, lhs, rhs } => {
2389            let (lk, rk) = (kind_of(lhs), kind_of(rhs));
2390            if lk == Kind::Float || rk == Kind::Float {
2391                let l = promote(micro, lhs, lk, true, conv.0);
2392                let r = promote(micro, rhs, rk, true, conv.1);
2393                micro.push(match ops[i] {
2394                    Op::Lt { .. } => MicroOp::LtF { dst, lhs: l, rhs: r },
2395                    Op::Gt { .. } => MicroOp::GtF { dst, lhs: l, rhs: r },
2396                    Op::LtEq { .. } => MicroOp::LtEqF { dst, lhs: l, rhs: r },
2397                    Op::GtEq { .. } => MicroOp::GtEqF { dst, lhs: l, rhs: r },
2398                    Op::Eq { .. } => MicroOp::EqF { dst, lhs: l, rhs: r },
2399                    _ => MicroOp::NeqF { dst, lhs: l, rhs: r },
2400                });
2401            } else {
2402                micro.push(match ops[i] {
2403                    Op::Lt { .. } => MicroOp::Lt { dst, lhs, rhs },
2404                    Op::Gt { .. } => MicroOp::Gt { dst, lhs, rhs },
2405                    Op::LtEq { .. } => MicroOp::LtEq { dst, lhs, rhs },
2406                    Op::GtEq { .. } => MicroOp::GtEq { dst, lhs, rhs },
2407                    Op::Eq { .. } => MicroOp::Eq { dst, lhs, rhs },
2408                    _ => MicroOp::Neq { dst, lhs, rhs },
2409                });
2410            }
2411        }
2412        Op::Jump { target } => {
2413            fixups.push(micro.len());
2414            micro.push(MicroOp::Jump { target });
2415        }
2416        Op::JumpIfFalse { cond, target } => {
2417            fixups.push(micro.len());
2418            micro.push(MicroOp::JumpIfFalse { cond, target });
2419        }
2420        Op::JumpIfTrue { cond, target } => {
2421            fixups.push(micro.len());
2422            micro.push(MicroOp::JumpIfTrue { cond, target });
2423        }
2424        // `IndexUnchecked` carries the Oracle's in-bounds proof (M9 range
2425        // analysis): its array load drops the bounds branch entirely
2426        // (V8/LLVM-style bounds-check elimination). Maps always check
2427        // through their helper, so the proof only affects the list lane.
2428        Op::Index { dst, collection, index }
2429        | Op::IndexUnchecked { dst, collection, index } => {
2430            // Elision is region-only: the function tier has no per-loop entry
2431            // guard, so it keeps every access checked (a speculative
2432            // `IndexUnchecked` there would be unguarded). Static proofs lose
2433            // nothing measurable in functions.
2434            let p = pins.get(&collection)?;
2435            if p.elem == PinElem::Map {
2436                micro.push(MicroOp::MapGet {
2437                    dst,
2438                    key: index,
2439                    map_slot: p.vec_slot,
2440                    helper_addr: logos_rt_map_get_ii as usize as i64,
2441                });
2442            } else {
2443                // A Text-as-bytes pin loads ONE byte (the ASCII char codepoint)
2444                // and is ALWAYS bounds-checked: indexing past an ASCII text must
2445                // raise the same error at the same point as the tree-walker's
2446                // per-char path (`IndexUnchecked` elision is never applied to a
2447                // text — no RegionBoundsGuard is emitted for it). Bool buffers
2448                // are 1-byte too; Int/Float are 8-byte.
2449                let text = p.elem == PinElem::TextBytes;
2450                let byte = text || p.elem == PinElem::Bool;
2451                let narrow32 = p.elem == PinElem::IntI32;
2452                let checked =
2453                    text || !region || matches!(ops[i], Op::Index { .. });
2454                micro.push(MicroOp::ArrLoad {
2455                    dst,
2456                    idx: index,
2457                    ptr_slot: p.ptr_slot,
2458                    len_slot: p.len_slot,
2459                    byte,
2460                    narrow32,
2461                    checked,
2462                });
2463            }
2464        }
2465        Op::SetIndex { collection, index, value }
2466        | Op::SetIndexUnchecked { collection, index, value } => {
2467            // Region-only elision (see `Index`); maps always check.
2468            let checked = !region || matches!(ops[i], Op::SetIndex { .. });
2469            let p = pins.get(&collection)?;
2470            // A Text pin is read-only — `Set item i of text` never tiers (the
2471            // kind gate already rejects it; bail defensively too).
2472            if p.elem == PinElem::TextBytes {
2473                return None;
2474            }
2475            if p.elem == PinElem::Map {
2476                micro.push(MicroOp::MapSet {
2477                    src: value,
2478                    key: index,
2479                    map_slot: p.vec_slot,
2480                    helper_addr: logos_rt_map_set_ii as usize as i64,
2481                });
2482            } else {
2483                let byte = p.elem == PinElem::Bool;
2484                let narrow32 = p.elem == PinElem::IntI32;
2485                micro.push(MicroOp::ArrStore {
2486                    src: value,
2487                    idx: index,
2488                    ptr_slot: p.ptr_slot,
2489                    len_slot: p.len_slot,
2490                    byte,
2491                    narrow32,
2492                    checked,
2493                });
2494            }
2495        }
2496        Op::Contains { dst, collection, value } => {
2497            let p = pins.get(&collection)?;
2498            if p.elem != PinElem::Map {
2499                return None;
2500            }
2501            micro.push(MicroOp::MapHas {
2502                dst,
2503                key: value,
2504                map_slot: p.vec_slot,
2505                helper_addr: logos_rt_map_has_i as usize as i64,
2506            });
2507        }
2508        Op::Length { dst, collection } => {
2509            let p = pins.get(&collection)?;
2510            micro.push(MicroOp::Move { dst, src: p.len_slot });
2511        }
2512        Op::ListPush { list, value } => {
2513            let p = pins.get(&list)?;
2514            let helper_addr = match p.elem {
2515                PinElem::Int => crate::logos_rt_push_i64 as usize as i64,
2516                PinElem::IntI32 => crate::logos_rt_push_i32 as usize as i64,
2517                PinElem::Float => crate::logos_rt_push_f64 as usize as i64,
2518                PinElem::Bool => crate::logos_rt_push_bool as usize as i64,
2519                // No push onto a map or a text accumulator.
2520                PinElem::Map | PinElem::TextBytes | PinElem::TextMut => return None,
2521            };
2522            micro.push(MicroOp::ArrPush {
2523                src: value,
2524                vec_slot: p.vec_slot,
2525                ptr_slot: p.ptr_slot,
2526                len_slot: p.len_slot,
2527                helper_addr,
2528                // A `Seq of Bool` buffer is 1-byte; the inline fast-path store
2529                // writes the boolean normalization. Int/Float are 8-byte raw;
2530                // a narrowed `IntsI32` buffer is 4-byte (truncating push).
2531                byte: p.elem == PinElem::Bool,
2532                narrow32: p.elem == PinElem::IntI32,
2533            });
2534        }
2535        Op::NewEmptyList { dst } => {
2536            // In-region `Let x be a new Seq` on a PINNED array: clear it in place
2537            // (truncate + reset the pinned length, keep the buffer) and reuse it,
2538            // rather than allocating fresh. An in-place mutation — the precise
2539            // region resumes over it soundly. Bails (`?`) for a non-pinned dst:
2540            // such a fresh list has no pin channel to clear here.
2541            let p = pins.get(&dst)?;
2542            // SOUNDNESS: reusing the buffer is only valid if the old contents
2543            // are dead. A `Move { src: dst }` that copies THIS list's handle to
2544            // another register ALIASES the buffer past the clear — reusing it
2545            // would wipe the alias's live data (knapsack's `Set prev to curr`,
2546            // where the cleared row is the one `prev` now points at). But the
2547            // compiler reuses register slots, so `dst` may also be a Move source
2548            // while it transiently holds an UNRELATED scalar (fannkuch's `perm`
2549            // slot is a `Sub` result moved out one op before the `NewEmptyList`
2550            // re-binds it to the list). Such a move reads the scalar, not the
2551            // list, and is irrelevant to buffer reuse. The kind flow already
2552            // resolves which value `dst` holds at each op: a move aliases the
2553            // list iff `dst` holds a LIST kind there (Mixed = a list joined with
2554            // a scalar across paths ⇒ conservatively an alias). A scalar kind
2555            // (or an unreachable/`Unknown` op) provably does not observe the
2556            // list, so clear-reuse stays sound. This is the same alias test the
2557            // VM makes dynamically (sole-ownership), lifted to the static kind
2558            // flow.
2559            let list_handle_escapes = ops.iter().enumerate().any(|(j, o)| {
2560                matches!(o, Op::Move { src, .. } if *src == dst)
2561                    && matches!(
2562                        kin[j].as_ref().map(|kk| kk[dst as usize]),
2563                        Some(
2564                            Kind::IntList
2565                                | Kind::IntListI32
2566                                | Kind::FloatList
2567                                | Kind::BoolList
2568                                | Kind::IntMap
2569                                | Kind::Mixed
2570                        )
2571                    )
2572            });
2573            if list_handle_escapes {
2574                return None;
2575            }
2576            let helper_addr = match p.elem {
2577                PinElem::Int => crate::logos_rt_clear_i64 as usize as i64,
2578                PinElem::IntI32 => crate::logos_rt_clear_i32 as usize as i64,
2579                PinElem::Float => crate::logos_rt_clear_f64 as usize as i64,
2580                PinElem::Bool => crate::logos_rt_clear_bool as usize as i64,
2581                // No empty-list clear for a map or text pin.
2582                PinElem::Map | PinElem::TextBytes | PinElem::TextMut => return None,
2583            };
2584            micro.push(MicroOp::ListClear {
2585                vec_slot: p.vec_slot,
2586                ptr_slot: p.ptr_slot,
2587                len_slot: p.len_slot,
2588                helper_addr,
2589            });
2590        }
2591        Op::CallBuiltin {
2592            dst,
2593            builtin: logicaffeine_compile::semantics::builtins::BuiltinId::Sqrt,
2594            args_start,
2595            arg_count,
2596        } => {
2597            if arg_count != 1 {
2598                return None;
2599            }
2600            let src = match kind_of(args_start) {
2601                Kind::Float => args_start,
2602                Kind::Int => {
2603                    micro.push(MicroOp::IntToFloat { dst: conv.0, src: args_start });
2604                    conv.0
2605                }
2606                _ => return None,
2607            };
2608            micro.push(MicroOp::SqrtF { dst, src });
2609        }
2610        Op::Call { dst, args_start, arg_count, func } => {
2611            let c = call_ctx?;
2612            // The callee windows at a DISJOINT offset past the limit slot —
2613            // the interpreter's overlapping windowing (callee base = caller
2614            // base + args_start) would let the callee's registers land on
2615            // the caller's conversion scratches and arena-limit slot, so
2616            // the caller's NEXT call would read a clobbered bound and
2617            // deopt. Args are staged across with explicit moves.
2618            let window = c.limit_slot + 1;
2619            // LEVER A — the pinned-argument self-call ABI. A DIRECT self-call
2620            // whose arguments are a contiguous all-scalar block lying wholly
2621            // below the callee window stages them inside ONE fused stencil
2622            // (`CallSelfCopy`) instead of `arg_count` separate frame-to-frame
2623            // `Move` pieces — the dispatch-reduction win on this piece-bound
2624            // engine. The copy is bit-identical to the Moves: the same callee
2625            // slots receive the same 8-byte values (Int/Bool/Float alike),
2626            // and the depth/deopt machinery is untouched. The disjointness
2627            // guard (`args_start + arg_count <= window`) is always true for
2628            // the function tier (`window = limit_slot + 1` sits past every
2629            // register), but is checked so the fallback is provably safe; any
2630            // list/map-kinded argument (which the all-scalar mode-A signature
2631            // never produces, but is guarded for soundness) keeps the Moves.
2632            let scalar_args = (0..arg_count).all(|j| {
2633                matches!(kind_of(args_start + j), Kind::Int | Kind::Bool | Kind::Float)
2634            });
2635            let fuse_self = func == c.self_fi
2636                && arg_count >= 1
2637                && args_start + arg_count <= window
2638                && scalar_args
2639                && std::env::var_os("LOGOS_NO_PINNED_ARGS").is_none();
2640            if fuse_self {
2641                // DIRECT self-call with the fused argument copy: the entry
2642                // pool word is patched with this chain's own base after
2643                // layout; the frame bound is static (self-recursion: the
2644                // callee frame is OUR OWN size).
2645                micro.push(MicroOp::CallSelfCopy {
2646                    dst,
2647                    args_start: window,
2648                    src_start: args_start,
2649                    arg_count,
2650                    depth_addr: c.depth_addr,
2651                    status_addr: c.status_addr,
2652                    limit_slot: c.limit_slot,
2653                    frame_size: (c.limit_slot as i64) + 1,
2654                });
2655                return Some(1);
2656            }
2657            for j in 0..arg_count {
2658                micro.push(MicroOp::Move { dst: window + j, src: args_start + j });
2659            }
2660            if func == c.self_fi {
2661                // DIRECT self-call: the entry pool word is patched with
2662                // this chain's own base after layout; the frame bound is
2663                // static (self-recursion: the callee frame is OUR OWN
2664                // size).
2665                micro.push(MicroOp::CallSelf {
2666                    dst,
2667                    args_start: window,
2668                    depth_addr: c.depth_addr,
2669                    status_addr: c.status_addr,
2670                    limit_slot: c.limit_slot,
2671                    frame_size: (c.limit_slot as i64) + 1,
2672                });
2673            } else {
2674                // Cross-function call: table dispatch through the CALLEE's
2675                // slot pair (the kind fixpoint already validated its
2676                // all-scalar signature); an unpublished entry deopts and
2677                // the boundary replay stays exact.
2678                micro.push(MicroOp::Call {
2679                    dst,
2680                    args_start: window,
2681                    table_addr: c.table.slot_addr(func as usize),
2682                    depth_addr: c.depth_addr,
2683                    status_addr: c.status_addr,
2684                    limit_slot: c.limit_slot,
2685                    depth_limit: c.depth_limit,
2686                });
2687            }
2688        }
2689        // In-region Return: set the flag, stash the value, leave via the
2690        // synthetic exit (usize::MAX is the out-of-region marker the remap
2691        // routes there).
2692        Op::Return { src } if region_return.is_some() => {
2693            let (flag_slot, value_slot) = region_return.unwrap();
2694            micro.push(MicroOp::LoadConst { dst: flag_slot, value: 1 });
2695            if matches!(kind_of(src), Kind::IntList | Kind::FloatList | Kind::BoolList) {
2696                // Lists return BY REGISTER: stash the register number; the
2697                // VM returns that register's current value.
2698                micro.push(MicroOp::LoadConst { dst: value_slot, value: src as i64 });
2699            } else {
2700                micro.push(MicroOp::Move { dst: value_slot, src });
2701            }
2702            fixups.push(micro.len());
2703            micro.push(MicroOp::Jump { target: usize::MAX });
2704        }
2705        Op::Return { src } if allow_return => micro.push(MicroOp::Return { src }),
2706        // Statically dead (gated by the caller); valid-shape placeholder.
2707        Op::ReturnNothing if allow_return => micro.push(MicroOp::Return { src: 0 }),
2708        // Region-entry length hoist: pure metadata, no native code. The VM
2709        // verifies it once at region entry; `adapt_region` collects it.
2710        Op::RegionBoundsGuard { .. } => {}
2711        _ => return None,
2712    }
2713    Some(1)
2714}
2715
2716/// Translate a MAIN-LOOP REGION into the J2 subset.
2717///
2718/// The register roles of a recognized naive substring-search nest (the
2719/// string_search benchmark idiom): the count of OVERLAPPING `needle`
2720/// occurrences in `text` over the outer index range.
2721struct MemMemShape {
2722    text_reg: u16,
2723    needle_reg: u16,
2724    needle_len_reg: u16,
2725    i_reg: u16,
2726    count_reg: u16,
2727}
2728
2729/// Recognize the EXACT naive substring-search nest the LOGOS compiler lowers
2730/// for `string_search`'s count loop, so the JIT can collapse the per-byte
2731/// nested-loop region to one [`MicroOp::MemMem`] call.
2732///
2733/// The matched outer-loop region (head at `ops[0]`) is, in bytecode:
2734///
2735/// ```text
2736///   Sub(t1 = textLen - needleLen); LoadConst(c=1); Add(bound = t1 + c)
2737///   LtEq(cmp = i <= bound); JumpIfFalse(cmp -> EXIT)
2738///   LoadConst(c=1); Move(match = c); LoadConst(c=0); Move(j = c)
2739///   Lt(cj = j < needleLen); JumpIfFalse(cj -> after_inner)
2740///     Add(t3 = i + j); IndexUnchecked(ch1 = text[t3])
2741///     LoadConst(c=1); Add(t4 = j + c); Index(ch2 = needle[t4])     // checked
2742///     NotEq(ne = ch1 != ch2); JumpIfFalse(ne -> cont)
2743///       LoadConst(c=0); Move(match = c); Move(t5 = needleLen); Move(j = t5)
2744///   cont:   LoadConst(c=1); AddAssign(j += c); Jump(-> inner head)
2745///   after_inner: LoadConst(c=1); Eq(eq = match == c); JumpIfFalse(eq -> skip)
2746///     LoadConst(c=1); AddAssign(count += c)
2747///   skip:   LoadConst(c=1); AddAssign(i += c); Jump(-> outer head)
2748/// ```
2749///
2750/// Recognition is STRICT — the shape, the operand wiring (`i`/`j`/`needleLen`/
2751/// `count`/`text`/`needle` register threading), the two `Index` collections, the
2752/// jump topology, and the `1`/`0` constants are all checked — so the helper is
2753/// only ever substituted when it is bit-identical to this nest. Any deviation
2754/// returns `None` and the region falls back to the per-byte tiered loop.
2755/// `LOGOS_MEMMEM=0` disables the recognizer entirely.
2756fn match_memmem_nest(
2757    ops: &[Op],
2758    head_pc: usize,
2759    exit_pc: usize,
2760    constants: &[Constant],
2761) -> Option<MemMemShape> {
2762    if std::env::var("LOGOS_MEMMEM").as_deref() == Ok("0") {
2763        return None;
2764    }
2765    // The template is exactly 33 ops (the outer back-edge is the last).
2766    if ops.len() != 33 {
2767        return None;
2768    }
2769    let is_const = |i: usize, want: i64| -> bool {
2770        matches!(ops.get(i), Some(Op::LoadConst { idx, .. })
2771            if matches!(constants.get(*idx as usize), Some(Constant::Int(v)) if *v == want))
2772    };
2773    let const_dst = |i: usize| -> Option<u16> {
2774        match ops.get(i) {
2775            Some(Op::LoadConst { dst, .. }) => Some(*dst),
2776            _ => None,
2777        }
2778    };
2779    // The region-relative targets the jumps must hit.
2780    let inner_head = head_pc + 9; // op [38] Lt(j < needleLen)
2781    let after_inner = head_pc + 25; // op [54]
2782    let cont = head_pc + 22; // op [51]
2783    let skip = head_pc + 30; // op [59]
2784
2785    // [0] Sub(t1 = textLen - needleLen)
2786    let Op::Sub { dst: t1, lhs: text_len, rhs: needle_len } = ops[0] else { return None };
2787    // [1] LoadConst(c1 = 1); [2] Add(bound = t1 + c1)
2788    if !is_const(1, 1) {
2789        return None;
2790    }
2791    let c1 = const_dst(1)?;
2792    let Op::Add { dst: bound, lhs, rhs } = ops[2] else { return None };
2793    if lhs != t1 || rhs != c1 {
2794        return None;
2795    }
2796    // [3] LtEq(cmp = i <= bound); [4] JumpIfFalse(cmp -> EXIT)
2797    let Op::LtEq { dst: cmp, lhs: i_reg, rhs } = ops[3] else { return None };
2798    if rhs != bound {
2799        return None;
2800    }
2801    let Op::JumpIfFalse { cond, target } = ops[4] else { return None };
2802    if cond != cmp || target != exit_pc {
2803        return None;
2804    }
2805    // [5] LoadConst(=1); [6] Move(match = it)
2806    if !is_const(5, 1) {
2807        return None;
2808    }
2809    let c5 = const_dst(5)?;
2810    let Op::Move { dst: match_reg, src } = ops[6] else { return None };
2811    if src != c5 {
2812        return None;
2813    }
2814    // [7] LoadConst(=0); [8] Move(j = it)
2815    if !is_const(7, 0) {
2816        return None;
2817    }
2818    let c7 = const_dst(7)?;
2819    let Op::Move { dst: j_reg, src } = ops[8] else { return None };
2820    if src != c7 {
2821        return None;
2822    }
2823    // [9] Lt(cj = j < needleLen); [10] JumpIfFalse(cj -> after_inner)
2824    let Op::Lt { dst: cj, lhs, rhs } = ops[9] else { return None };
2825    if lhs != j_reg || rhs != needle_len {
2826        return None;
2827    }
2828    let Op::JumpIfFalse { cond, target } = ops[10] else { return None };
2829    if cond != cj || target != after_inner {
2830        return None;
2831    }
2832    // [11] Add(t3 = i + j); [12] IndexUnchecked(ch1 = text[t3])
2833    let Op::Add { dst: t3, lhs, rhs } = ops[11] else { return None };
2834    if lhs != i_reg || rhs != j_reg {
2835        return None;
2836    }
2837    let Op::IndexUnchecked { dst: ch1, collection: text_reg, index } = ops[12] else {
2838        return None;
2839    };
2840    if index != t3 {
2841        return None;
2842    }
2843    // [13] LoadConst(=1); [14] Add(t4 = j + it); [15] Index(ch2 = needle[t4])
2844    if !is_const(13, 1) {
2845        return None;
2846    }
2847    let c13 = const_dst(13)?;
2848    let Op::Add { dst: t4, lhs, rhs } = ops[14] else { return None };
2849    if lhs != j_reg || rhs != c13 {
2850        return None;
2851    }
2852    let Op::Index { dst: ch2, collection: needle_reg, index } = ops[15] else { return None };
2853    if index != t4 {
2854        return None;
2855    }
2856    // [16] NotEq(ne = ch1 != ch2); [17] JumpIfFalse(ne -> cont)
2857    let Op::NotEq { dst: ne, lhs, rhs } = ops[16] else { return None };
2858    if !((lhs == ch1 && rhs == ch2) || (lhs == ch2 && rhs == ch1)) {
2859        return None;
2860    }
2861    let Op::JumpIfFalse { cond, target } = ops[17] else { return None };
2862    if cond != ne || target != cont {
2863        return None;
2864    }
2865    // [18] LoadConst(=0); [19] Move(match = it)
2866    if !is_const(18, 0) {
2867        return None;
2868    }
2869    let c18 = const_dst(18)?;
2870    let Op::Move { dst, src } = ops[19] else { return None };
2871    if dst != match_reg || src != c18 {
2872        return None;
2873    }
2874    // [20] Move(t5 = needleLen); [21] Move(j = t5)
2875    let Op::Move { dst: t5, src } = ops[20] else { return None };
2876    if src != needle_len {
2877        return None;
2878    }
2879    let Op::Move { dst, src } = ops[21] else { return None };
2880    if dst != j_reg || src != t5 {
2881        return None;
2882    }
2883    // [22] cont: LoadConst(=1); [23] AddAssign(j += it); [24] Jump(-> inner head)
2884    if !is_const(22, 1) {
2885        return None;
2886    }
2887    let c22 = const_dst(22)?;
2888    let Op::AddAssign { dst, src } = ops[23] else { return None };
2889    if dst != j_reg || src != c22 {
2890        return None;
2891    }
2892    let Op::Jump { target } = ops[24] else { return None };
2893    if target != inner_head {
2894        return None;
2895    }
2896    // [25] after_inner: LoadConst(=1); [26] Eq(eq = match == it); [27] JumpIfFalse(eq -> skip)
2897    if !is_const(25, 1) {
2898        return None;
2899    }
2900    let c25 = const_dst(25)?;
2901    let Op::Eq { dst: eq, lhs, rhs } = ops[26] else { return None };
2902    if !((lhs == match_reg && rhs == c25) || (lhs == c25 && rhs == match_reg)) {
2903        return None;
2904    }
2905    let Op::JumpIfFalse { cond, target } = ops[27] else { return None };
2906    if cond != eq || target != skip {
2907        return None;
2908    }
2909    // [28] LoadConst(=1); [29] AddAssign(count += it)
2910    if !is_const(28, 1) {
2911        return None;
2912    }
2913    let c28 = const_dst(28)?;
2914    let Op::AddAssign { dst: count_reg, src } = ops[29] else { return None };
2915    if src != c28 {
2916        return None;
2917    }
2918    // [30] skip: LoadConst(=1); [31] AddAssign(i += it); [32] Jump(-> outer head)
2919    if !is_const(30, 1) {
2920        return None;
2921    }
2922    let c30 = const_dst(30)?;
2923    let Op::AddAssign { dst, src } = ops[31] else { return None };
2924    if dst != i_reg || src != c30 {
2925        return None;
2926    }
2927    let Op::Jump { target } = ops[32] else { return None };
2928    if target != head_pc {
2929        return None;
2930    }
2931    // text and needle must be DISTINCT collections; the count accumulator must
2932    // be distinct from the loop induction `i`.
2933    let _ = text_len;
2934    if text_reg == needle_reg || count_reg == i_reg {
2935        return None;
2936    }
2937    Some(MemMemShape {
2938        text_reg,
2939        needle_reg,
2940        needle_len_reg: needle_len,
2941        i_reg,
2942        count_reg,
2943    })
2944}
2945
2946/// Region contract: `ops[0]` is the loop head; the slice ends at the
2947/// back-edge; every jump out targets `exit_pc` (mapped to a synthetic
2948/// terminal). The guard set is the LIVE-IN set at the head — computed by
2949/// textbook backward liveness over the region's CFG — so any slot whose
2950/// incoming value can be observed is Int-guarded and copied in, while
2951/// incoming-dead slots (loop scratches, comparison temporaries anywhere in
2952/// the body) need no guard. Writes are re-boxed by inferred kind.
2953#[allow(clippy::type_complexity)]
2954/// BUG-002 detector: does an array-loaded value survive a conditional branch and
2955/// reach an in-place `SetIndex`? A forward scan tracks, per register, whether it
2956/// currently holds a value loaded from an array and whether a branch has occurred
2957/// since that load (propagating through `Move`s). A `SetIndex`/`SetIndexUnchecked`
2958/// whose stored value is such a branch-surviving array-loaded register is the
2959/// miscompiling shape. Conservative (a branch marks ALL live array-loaded
2960/// registers), but the safe in-branch-load shape never trips it because the load
2961/// happens AFTER the guard branch.
2962fn has_crossbranch_array_value_store_hazard(ops: &[Op]) -> bool {
2963    use std::collections::HashMap;
2964    // reg -> has a branch occurred since this reg was loaded from an array?
2965    let mut loaded: HashMap<u16, bool> = HashMap::new();
2966    for op in ops {
2967        match op {
2968            Op::Index { dst, .. } | Op::IndexUnchecked { dst, .. } => {
2969                loaded.insert(*dst, false);
2970            }
2971            Op::Move { dst, src } => match loaded.get(src).copied() {
2972                Some(b) => {
2973                    loaded.insert(*dst, b);
2974                }
2975                None => {
2976                    loaded.remove(dst);
2977                }
2978            },
2979            Op::JumpIfFalse { .. } | Op::JumpIfTrue { .. } | Op::Jump { .. } => {
2980                for v in loaded.values_mut() {
2981                    *v = true;
2982                }
2983            }
2984            Op::SetIndex { value, .. } | Op::SetIndexUnchecked { value, .. } => {
2985                if loaded.get(value) == Some(&true) {
2986                    return true;
2987                }
2988            }
2989            other => {
2990                // Any other op that DEFINES a register clears its array-loaded
2991                // status (the value is no longer a raw array element).
2992                if let Some((_, defs)) = region_use_def(other) {
2993                    for d in defs {
2994                        loaded.remove(&d);
2995                    }
2996                }
2997            }
2998        }
2999    }
3000    false
3001}
3002
3003fn adapt_region(
3004    ops: &[Op],
3005    head_pc: usize,
3006    exit_pc: usize,
3007    constants: &[Constant],
3008    register_count: u16,
3009    named: &[bool],
3010    observed: &[ObservedKind],
3011    ctx: &NativeCtx,
3012    callees: &[logicaffeine_compile::vm::CalleeSig],
3013) -> Option<(
3014    Vec<MicroOp>,
3015    usize,
3016    Vec<(u16, SlotKind)>,
3017    Vec<u16>,
3018    Vec<(u16, SlotKind)>,
3019    Vec<ArrayPin>,
3020    Option<logicaffeine_compile::vm::RegionReturn>,
3021    Vec<HoistGuard>,
3022    // Precise-deopt tables (Some ⇒ this region resumes AT the faulting op on a
3023    // side exit, re-boxing each register by kind, instead of replaying from the
3024    // head — required for a sound push+SetIndex region).
3025    Option<RegionPrecise>,
3026)> {
3027    // SOUNDNESS (Bug Report #1, BUG-001): a region runs under a
3028    // discard-and-replay-from-head deopt contract that is sound only for
3029    // replay-idempotent effects. `ListPush` APPENDS — not idempotent on its own
3030    // — but the VM's region deopt path now rolls every pinned buffer back to its
3031    // entry length before replaying (see machine.rs), so a push followed by a
3032    // PURE side-exit (Div/Mod div-by-zero, Index/Contains out-of-bounds or
3033    // fast-lane miss — all read-only or arithmetic) replays cleanly. What stays
3034    // unsound is push coexisting with another NON-idempotent effect whose replay
3035    // the truncate cannot undo: an in-place `SetIndex`/`SetIndexUnchecked` write
3036    // (a read-modify-write would double-apply) or a `Call` (arbitrary effects).
3037    // Those still disqualify the region; it falls back to the always-correct
3038    // bytecode interpreter.
3039    if std::env::var("LOGOS_DUMP_REGION").ok().and_then(|v| v.parse::<usize>().ok()) == Some(head_pc) {
3040        eprintln!("=== REGION head_pc={head_pc} ops ({}) ===", ops.len());
3041        for (i, op) in ops.iter().enumerate() {
3042            eprintln!("  [{}] {op:?}", head_pc + i);
3043        }
3044    }
3045    let has_list_push = ops.iter().any(|op| matches!(op, Op::ListPush { .. }));
3046    let has_setindex = ops
3047        .iter()
3048        .any(|op| matches!(op, Op::SetIndex { .. } | Op::SetIndexUnchecked { .. }));
3049    let has_call_op = ops.iter().any(|op| matches!(op, Op::Call { .. }));
3050    // A `ListPush` coexisting with an in-place `SetIndex` or a `Call` is NOT
3051    // replay-safe under the classic discard-replay-from-head deopt (truncate
3052    // rolls the pushes back but the in-place write persists, so a self-gated
3053    // re-push is skipped — the BFS lost-node bug). It can still tier up with
3054    // PRECISE region deopt (resume AT the faulting op, no replay): sound for any
3055    // pattern. Precise materialization re-boxes the frame's scalars, so it is
3056    // admitted only for an ALL-INT region with NO call (a call frame would need
3057    // the full function-precise walk). A push beside a call is therefore always
3058    // disqualified; a push beside a SetIndex is admitted here and re-checked for
3059    // all-int after lowering (where the precise `deopt_codes` are built).
3060    let needs_precise = has_list_push && (has_setindex || has_call_op);
3061    if needs_precise && has_call_op {
3062        return None;
3063    }
3064
3065    // Backward liveness: out-of-region exits contribute empty live-out —
3066    // exit-observable slots flow through the WRITE-BACK, never the frame.
3067    let nregs = register_count as usize + 2;
3068    let in_region = |t: usize| (head_pc..head_pc + ops.len()).contains(&t);
3069    let Some(live) = liveness(ops, head_pc, nregs) else {
3070        if std::env::var_os("LOGOS_RDIAG").is_some() {
3071            eprintln!("RDIAG-BAIL head_pc={head_pc} liveness");
3072        }
3073        return None;
3074    };
3075
3076    // Guard = live-in at the head; touched = every use or def; free =
3077    // touched − guard; writes = every def (re-boxed by kind on completion).
3078    let mut guard: Vec<u16> = Vec::new();
3079    let mut free: Vec<u16> = Vec::new();
3080    let mut writes: Vec<u16> = Vec::new();
3081    let mut touched: Vec<u16> = Vec::new();
3082    for op in ops {
3083        let Some((uses, defs)) = region_use_def(op) else {
3084            if std::env::var_os("LOGOS_RDIAG").is_some() {
3085                eprintln!("RDIAG-BAIL head_pc={head_pc} use_def-unsupported op={op:?}");
3086            }
3087            return None;
3088        };
3089        for d in defs {
3090            if !writes.contains(&d) {
3091                writes.push(d);
3092            }
3093            if !touched.contains(&d) {
3094                touched.push(d);
3095            }
3096        }
3097        for u in uses {
3098            if !touched.contains(&u) {
3099                touched.push(u);
3100            }
3101        }
3102    }
3103    // Array pins: every register used as an Index/SetIndex/Length collection
3104    // must hold an UNBOXED list right now (the speculation the entry guard
3105    // re-checks); each gets a pinned (pointer, length) slot pair past the
3106    // conversion scratches.
3107    let mut pin_regs: Vec<(u16, PinElem)> = Vec::new();
3108    for op in ops {
3109        let coll = match *op {
3110            Op::Index { collection, .. }
3111            | Op::IndexUnchecked { collection, .. }
3112            | Op::SetIndex { collection, .. }
3113            | Op::SetIndexUnchecked { collection, .. }
3114            | Op::Length { collection, .. }
3115            | Op::Contains { collection, .. } => Some(collection),
3116            Op::ListPush { list, .. } => Some(list),
3117            _ => None,
3118        };
3119        if let Some(c) = coll {
3120            let elem = match observed.get(c as usize)? {
3121                ObservedKind::IntList => PinElem::Int,
3122                ObservedKind::IntListI32 => PinElem::IntI32,
3123                ObservedKind::FloatList => PinElem::Float,
3124                ObservedKind::BoolList => PinElem::Bool,
3125                ObservedKind::Map => PinElem::Map,
3126                ObservedKind::TextBytes => {
3127                    // A Text-as-bytes pin is read-only: the byte buffer pointer
3128                    // is captured at entry and the `Rc<String>` is never written
3129                    // in place. A region that REASSIGNS the pinned register (a
3130                    // `Move`/rebind into `c`, or string growth via `Op::Concat`
3131                    // — already out of `region_use_def` so it bails on its own)
3132                    // would leave a stale buffer pointer. Bail if the pinned
3133                    // register is ever a def in this region.
3134                    if writes.contains(&c) {
3135                        if std::env::var_os("LOGOS_RDIAG").is_some() {
3136                            eprintln!("RDIAG-BAIL head_pc={head_pc} textbytes-pin-reassigned reg={c}");
3137                        }
3138                        return None;
3139                    }
3140                    PinElem::TextBytes
3141                }
3142                _ => {
3143                    if std::env::var_os("LOGOS_RDIAG").is_some() {
3144                        eprintln!("RDIAG-BAIL head_pc={head_pc} non-list-collection reg={c}");
3145                    }
3146                    return None;
3147                }
3148            };
3149            if !pin_regs.iter().any(|&(r, _)| r == c) {
3150                pin_regs.push((c, elem));
3151            }
3152        }
3153    }
3154    // MUTABLE-TEXT accumulator (`Set text to text + ch`): a register that is the
3155    // `dst` of an `AddAssign` and is OBSERVED as an ASCII Text rides the
3156    // `TextMut` pin channel (a `*mut Value` handle to its VM register cell), so
3157    // the append lowers to a `StrAppend` helper call instead of bailing the
3158    // region on a non-numeric `AddAssign`. Emitted ONLY for the contiguous
3159    // regalloc backend (the per-piece stencil tier has no `StrAppend` lowering
3160    // and declines it); when regalloc is off, a `text + ch` region falls back to
3161    // bytecode exactly as before. A register that is ALSO used as a collection
3162    // (already pinned above) is not re-pinned as TextMut.
3163    let strappend_on = logicaffeine_forge::regalloc::regalloc_enabled();
3164    if strappend_on {
3165        for op in ops {
3166            if let Op::AddAssign { dst, .. } = *op {
3167                if matches!(observed.get(dst as usize), Some(ObservedKind::TextBytes))
3168                    && !pin_regs.iter().any(|&(r, _)| r == dst)
3169                {
3170                    pin_regs.push((dst, PinElem::TextMut));
3171                }
3172            }
3173        }
3174    }
3175    // LEVER B (ON by default; kill-switch `LOGOS_LEVERB=0`): a list that flows
3176    // into a list-parameter CALL but is never directly Indexed here (e.g.
3177    // `arr = siftDown(arr, …)`) is still a pinnable array — pin every touched
3178    // slot observed as a list, so it rides the array channel rather than the
3179    // scalar guard set (which has no `slot_kind` for a list and would bail).
3180    // Validated across ~2180 tests (878 JIT + 1301 e2e) + the deopt-rollback
3181    // RED/GREEN; regions calling list-param functions now tier (heap_sort,
3182    // mergesort, any worklist-via-helper) with discard-replay soundness from
3183    // the array snapshot/rollback.
3184    let leverb_on = std::env::var("LOGOS_LEVERB").as_deref() != Ok("0");
3185    // Does this region CALL a list-parameter function? Only then must a list
3186    // that's merely passed through (not Indexed here) be pinned. Pinning EVERY
3187    // touched list would spuriously re-pin lists in SCALAR-call regions and
3188    // reintroduce the float-pin / mem-form clobber (spectral_norm), so this is
3189    // strictly scoped to list-param-call regions.
3190    let has_list_param_call = leverb_on
3191        && ops.iter().any(|op| match *op {
3192            Op::Call { func, .. } => callees.get(func as usize).is_some_and(|s| {
3193                s.param_kinds.iter().any(|p| matches!(p, Some(ParamKind::List(_))))
3194            }),
3195            _ => false,
3196        });
3197    if has_list_param_call {
3198        for &r in &touched {
3199            if pin_regs.iter().any(|&(p, _)| p == r) {
3200                continue;
3201            }
3202            let elem = match observed.get(r as usize) {
3203                Some(ObservedKind::IntList) => Some(PinElem::Int),
3204                Some(ObservedKind::IntListI32) => Some(PinElem::IntI32),
3205                Some(ObservedKind::FloatList) => Some(PinElem::Float),
3206                Some(ObservedKind::BoolList) => Some(PinElem::Bool),
3207                _ => None,
3208            };
3209            if let Some(e) = elem {
3210                pin_regs.push((r, e));
3211            }
3212        }
3213    }
3214    // In-region Returns: the value's kind must agree across every return
3215    // site (re-boxed by SlotKind at the boundary).
3216    let has_return = ops.iter().any(|op| matches!(op, Op::Return { .. }));
3217    if ops.iter().any(|op| matches!(op, Op::ReturnNothing)) {
3218        if std::env::var_os("LOGOS_RDIAG").is_some() {
3219            eprintln!("RDIAG-BAIL head_pc={head_pc} return-nothing");
3220        }
3221        return None;
3222    }
3223    // An in-place `SetIndex` write is NOT replay-idempotent (a read-modify-write
3224    // or swap double-applies), so a buffer it targets must be snapshotted on
3225    // region entry and restored on a classic replay-from-head `Deopt`. That cost
3226    // is only owed when the region can ACTUALLY take a recoverable side exit:
3227    // a checked list `Index` (bounds), ANY map `Index`/`Contains` (int-lane
3228    // miss), `Div`/`Mod` (div-by-zero), or a `Call` (callee deopt). Unchecked
3229    // list reads, pure arithmetic, and control flow never side-exit — so a
3230    // deopt-free swap loop (bubble/quick sort) pays nothing. A PRECISE region
3231    // resumes AT the faulting op (no replay), so it never needs the snapshot.
3232    let is_map = |c: u16| matches!(observed.get(c as usize), Some(ObservedKind::Map));
3233    let region_has_deopt_source = ops.iter().any(|op| match *op {
3234        Op::Div { .. } | Op::Mod { .. } | Op::Call { .. } | Op::Index { .. } => true,
3235        Op::IndexUnchecked { collection, .. } | Op::Contains { collection, .. } => is_map(collection),
3236        _ => false,
3237    });
3238    let is_setindexed = |reg: u16| {
3239        ops.iter().any(|op| {
3240            matches!(*op,
3241                Op::SetIndex { collection, .. } | Op::SetIndexUnchecked { collection, .. }
3242                    if collection == reg)
3243        })
3244    };
3245    // A pinned mutable-Text accumulator is grown directly in its VM register cell
3246    // by the `StrAppend` helper — like an in-place `SetIndex`, that growth is NOT
3247    // replay-idempotent (a classic replay-from-head would double-append), so its
3248    // entry `Value` must be snapshotted and restored on a classic deopt.
3249    let is_text_mut = |reg: u16| pin_regs.iter().any(|&(r, e)| r == reg && e == PinElem::TextMut);
3250    // LEVER B: a region that CALLS a list-parameter function (`has_list_param_call`,
3251    // computed above) lets the callee mutate the passed buffer IN PLACE. The
3252    // region can't see which pinned array flows into the call (it's staged through
3253    // Moves into the arg window), so conservatively snapshot EVERY pinned array in
3254    // such a region — its contents must roll back on a classic deopt like a SetIndex.
3255    let needs_snapshot = !needs_precise && region_has_deopt_source;
3256    let pin_base = register_count + 2;
3257    let array_pins: Vec<ArrayPin> = pin_regs
3258        .iter()
3259        .enumerate()
3260        .map(|(k, &(reg, elem))| ArrayPin {
3261            reg,
3262            vec_slot: pin_base + 3 * k as u16,
3263            ptr_slot: pin_base + 3 * k as u16 + 1,
3264            len_slot: pin_base + 3 * k as u16 + 2,
3265            elem,
3266            mutated: needs_snapshot && (is_setindexed(reg) || has_list_param_call || is_text_mut(reg)),
3267        })
3268        .collect();
3269    // Collect the loop's region-entry bounds hoists, resolving each guarded
3270    // array to its pinned length slot. The guarded array MUST be pinned (it is
3271    // — the compiler only emits a guard for an array it also accesses); a
3272    // guard whose array is not pinned is dropped (its accesses then stay
3273    // checked, which is safe).
3274    let mut hoist_guards: Vec<HoistGuard> = Vec::new();
3275    for op in ops {
3276        if let Op::RegionBoundsGuard { array, bound, iv, add_max, add_min } = *op {
3277            // The guarded array must be pinned (so its length is available to
3278            // check). If not, bail the region entirely — never run the
3279            // speculative accesses without their guard.
3280            let Some(pin) = array_pins.iter().find(|p| p.reg == array) else {
3281                if std::env::var_os("LOGOS_RDIAG").is_some() {
3282                    eprintln!("RDIAG-BAIL head_pc={head_pc} bounds-guard-array-not-pinned");
3283                }
3284                return None;
3285            };
3286            hoist_guards.push(HoistGuard {
3287                len_slot: pin.len_slot,
3288                bound_reg: bound,
3289                iv_reg: iv,
3290                add_max,
3291                add_min,
3292            });
3293        }
3294    }
3295    let pins: std::collections::HashMap<u16, PinSlots> = array_pins
3296        .iter()
3297        .map(|p| {
3298            (
3299                p.reg,
3300                PinSlots {
3301                    vec_slot: p.vec_slot,
3302                    ptr_slot: p.ptr_slot,
3303                    len_slot: p.len_slot,
3304                    elem: p.elem,
3305                },
3306            )
3307        })
3308        .collect();
3309
3310    // Must-write: slots DEFINITELY written on every path from the head to
3311    // op i (state holding before i executes). Forward dataflow with
3312    // intersection at merges. A written slot that is not must-written at
3313    // some exit edge is only written on SOME completing paths — bytecode
3314    // would leave its old value on the others, so the region must copy the
3315    // old value IN (guard it) for write-back to restore when the taken path
3316    // skipped the write. (The primes `isPrime`-flag shape.)
3317    let mut mw: Vec<Option<Vec<bool>>> = vec![None; ops.len()];
3318    mw[0] = Some(vec![false; nregs]);
3319    let mut work: Vec<usize> = vec![0];
3320    while let Some(i) = work.pop() {
3321        let Some(cur) = mw[i].clone() else { continue };
3322        let mut out = cur;
3323        let Some((_, defs)) = region_use_def(&ops[i]) else {
3324            if std::env::var_os("LOGOS_RDIAG").is_some() {
3325                eprintln!("RDIAG-BAIL head_pc={head_pc} mw-use_def-unsupported op={:?}", ops[i]);
3326            }
3327            return None;
3328        };
3329        for d in defs {
3330            out[d as usize] = true;
3331        }
3332        let mut succs: Vec<usize> = Vec::with_capacity(2);
3333        match ops[i] {
3334            Op::Jump { target } => {
3335                if in_region(target) {
3336                    succs.push(target - head_pc);
3337                }
3338            }
3339            Op::JumpIfFalse { target, .. }
3340            | Op::JumpIfTrue { target, .. }
3341            => {
3342                if i + 1 < ops.len() {
3343                    succs.push(i + 1);
3344                }
3345                if in_region(target) {
3346                    succs.push(target - head_pc);
3347                }
3348            }
3349            _ => {
3350                if i + 1 < ops.len() {
3351                    succs.push(i + 1);
3352                }
3353            }
3354        }
3355        for s in succs {
3356            match &mut mw[s] {
3357                slot @ None => {
3358                    *slot = Some(out.clone());
3359                    work.push(s);
3360                }
3361                Some(prev) => {
3362                    let mut changed = false;
3363                    for (a, &b) in prev.iter_mut().zip(&out) {
3364                        if *a && !b {
3365                            *a = false;
3366                            changed = true;
3367                        }
3368                    }
3369                    if changed {
3370                        work.push(s);
3371                    }
3372                }
3373            }
3374        }
3375    }
3376    // Intersection of must-write across every reachable exit edge.
3377    let mut exit_must: Vec<bool> = vec![true; nregs];
3378    let mut saw_exit = false;
3379    for (i, op) in ops.iter().enumerate() {
3380        let Some(m) = &mw[i] else { continue };
3381        let exits_here = match *op {
3382            Op::Jump { target }
3383            | Op::JumpIfFalse { target, .. }
3384            | Op::JumpIfTrue { target, .. }
3385            => !in_region(target),
3386            Op::Return { .. } => true,
3387            _ => false,
3388        };
3389        if exits_here {
3390            saw_exit = true;
3391            for (e, &b) in exit_must.iter_mut().zip(m) {
3392                *e &= b;
3393            }
3394        }
3395    }
3396    if !saw_exit {
3397        exit_must.iter_mut().for_each(|e| *e = false);
3398    }
3399
3400    // Observability policy: only NAMED slots can be read after the region
3401    // (scratches are statement-local and dead at every statement boundary by
3402    // the compiler's allocation discipline). A named slot written only on
3403    // SOME completing paths is also copied IN, so write-back restores the
3404    // pre-region value when the taken path skipped the write.
3405    let is_named = |r: u16| named.get(r as usize).copied().unwrap_or(false);
3406    for &r in &touched {
3407        if pins.contains_key(&r) {
3408            continue; // pinned arrays ride their own channel
3409        }
3410        let conditionally_written =
3411            is_named(r) && writes.contains(&r) && !exit_must[r as usize];
3412        if live[0][r as usize] || conditionally_written {
3413            guard.push(r);
3414        } else {
3415            free.push(r);
3416        }
3417    }
3418
3419    // Flow-sensitive kinds: guarded (live-in or conditionally-written) slots
3420    // enter with the kind the VM OBSERVED at this hot crossing — exactly what
3421    // the runtime guard will re-check on every entry — free slots as Unknown.
3422    // A guard slot holding something the frame can't carry kills the region.
3423    let mut entry = vec![Kind::Unknown; nregs];
3424    let mut guard_kinds: Vec<(u16, SlotKind)> = Vec::with_capacity(guard.len());
3425    for &g in &guard {
3426        let k = observed_kind(*observed.get(g as usize)?);
3427        let Some(sk) = k.slot_kind() else {
3428            if std::env::var_os("LOGOS_RDIAG").is_some() {
3429                eprintln!("RDIAG-BAIL head_pc={head_pc} guard-slot-no-slotkind g={g} kind={k:?}");
3430            }
3431            return None;
3432        };
3433        entry[g as usize] = k;
3434        guard_kinds.push((g, sk));
3435    }
3436    for &(reg, elem) in &pin_regs {
3437        entry[reg as usize] = match elem {
3438            PinElem::Int => Kind::IntList,
3439            PinElem::IntI32 => Kind::IntListI32,
3440            PinElem::Float => Kind::FloatList,
3441            PinElem::Bool => Kind::BoolList,
3442            PinElem::Map => Kind::IntMap,
3443            PinElem::TextBytes => Kind::TextBytes,
3444            PinElem::TextMut => Kind::TextMut,
3445        };
3446    }
3447    // LEVER B: admit a region that CALLS a list-parameter function. Sound only
3448    // for a region-safe callee (never reallocs a list param) whose list args are
3449    // pinned arrays the region snapshots; the region also releases its array
3450    // borrows around the call so the callee can borrow them. Gated ON by
3451    // `LOGOS_LEVERB=1` while the call path is being hardened (default rejects,
3452    // exactly as before). `leverb_on` is bound above (at the pin-regs stage).
3453    let resolve = |fi: u16| -> Option<(Kind, Vec<Kind>)> {
3454        let sig = callees.get(fi as usize)?;
3455        let list_kind = |e: PinElem| -> Option<Kind> {
3456            match e {
3457                PinElem::Int => Some(Kind::IntList),
3458                PinElem::Float => Some(Kind::FloatList),
3459                PinElem::Bool => Some(Kind::BoolList),
3460                // A half-width buffer is never a declared list-param.
3461                PinElem::IntI32 => None,
3462                PinElem::Map | PinElem::TextBytes | PinElem::TextMut => None,
3463            }
3464        };
3465        let ret = match sig.ret {
3466            Some(SlotKind::Int) => Kind::Int,
3467            Some(SlotKind::Bool) => Kind::Bool,
3468            Some(SlotKind::Float) => Kind::Float,
3469            // A LIST (or void) return: admitted only when it aliases a list
3470            // parameter (no fresh buffer ⇒ no stale pin) of a stable callee.
3471            None => {
3472                if !leverb_on || !sig.list_params_stable || !sig.returns_list_param {
3473                    if std::env::var_os("LOGOS_RDIAG").is_some() {
3474                        eprintln!("RDIAG-BAIL head_pc={head_pc} list-return-call");
3475                    }
3476                    return None;
3477                }
3478                let elem = sig.param_kinds.iter().find_map(|pk| match pk {
3479                    Some(ParamKind::List(e)) => Some(*e),
3480                    _ => None,
3481                })?;
3482                list_kind(elem)?
3483            }
3484        };
3485        let mut ps = Vec::with_capacity(sig.param_kinds.len());
3486        for pk in &sig.param_kinds {
3487            match (*pk)? {
3488                ParamKind::Scalar(SlotKind::Int) => ps.push(Kind::Int),
3489                ParamKind::Scalar(SlotKind::Bool) => ps.push(Kind::Bool),
3490                ParamKind::Scalar(SlotKind::Float) => ps.push(Kind::Float),
3491                // A list param: the callee mutates the shared buffer in place.
3492                // Sound iff the callee is stable (no realloc) — the pinned arg's
3493                // contents ride the deopt snapshot/rollback, and the region
3494                // releases its borrow so the callee can access it.
3495                ParamKind::List(elem) => {
3496                    if !leverb_on || !sig.list_params_stable {
3497                        if std::env::var_os("LOGOS_RDIAG").is_some() {
3498                            eprintln!("RDIAG-BAIL head_pc={head_pc} list-param-call");
3499                        }
3500                        return None;
3501                    }
3502                    ps.push(list_kind(elem)?);
3503                }
3504            }
3505        }
3506        Some((ret, ps))
3507    };
3508    let Some(kin) = kind_flow(ops, head_pc, constants, entry, Some(&resolve)) else {
3509        if std::env::var_os("LOGOS_RDIAG").is_some() {
3510            eprintln!("RDIAG-BAIL head_pc={head_pc} kind_flow");
3511        }
3512        return None;
3513    };
3514
3515    // Write-back kinds come from the EXIT EDGES: for each written slot, join
3516    // its kind at every reachable out-of-region jump. Mixed (exits disagree)
3517    // bails; Unknown means no completing path writes it — leave the VM
3518    // register untouched (exactly what bytecode reaching the exit would do).
3519    let mut exit_kind: Vec<Kind> = vec![Kind::Unknown; nregs];
3520    for (i, op) in ops.iter().enumerate() {
3521        let Some(k) = &kin[i] else { continue };
3522        let exits_here = match *op {
3523            Op::Jump { target } => !in_region(target),
3524            Op::JumpIfFalse { target, .. }
3525            | Op::JumpIfTrue { target, .. }
3526            => !in_region(target),
3527            // An in-region Return leaves the loop too — its named writes
3528            // flow through the same write-back.
3529            Op::Return { .. } => true,
3530            _ => false,
3531        };
3532        if exits_here {
3533            for (e, kk) in exit_kind.iter_mut().zip(k) {
3534                *e = join(*e, *kk);
3535            }
3536        }
3537    }
3538    let mut write_set: Vec<(u16, SlotKind)> = Vec::new();
3539    for &r in &writes {
3540        // Scratches are unobservable after the region — never written back,
3541        // and their exit kind (often Mixed: the compiler reuses them across
3542        // Int and Bool statements) is irrelevant.
3543        if !is_named(r) {
3544            continue;
3545        }
3546        // LEVER B: a pinned array REBOUND to a return-aliased call result
3547        // (`arr = siftDown(arr, …)`) carries a list exit-kind, but it rides the
3548        // pin channel, not the scalar write-back: the callee returns its own
3549        // argument (`returns_list_param`), so the VM register already holds the
3550        // correct Rc and no write-back is owed. Skip it (else `slot_kind()` on a
3551        // list kind below would bail the region).
3552        if leverb_on && array_pins.iter().any(|a| a.reg == r) {
3553            continue;
3554        }
3555        match exit_kind[r as usize] {
3556            Kind::Unknown => {}
3557            // A Mixed exit-kind means the compiler REUSED this register across
3558            // an Int and a Float on different paths — it is never a single
3559            // typed observable variable. If it is NOT loop-carried (not live-in
3560            // at the region head, per the region's own backward liveness) it is
3561            // a per-iteration scratch, dead at the region boundary: skip its
3562            // write-back rather than decline the whole region (the VM's stale
3563            // register value is unobservable — the scratch is recomputed every
3564            // iteration). A genuinely loop-carried Mixed slot still bails.
3565            Kind::Mixed => {
3566                if live[0][r as usize] {
3567                    if std::env::var_os("LOGOS_RDIAG").is_some() {
3568                        eprintln!("RDIAG-BAIL head_pc={head_pc} mixed-write-set slot={r}");
3569                    }
3570                    return None;
3571                }
3572            }
3573            // A text kind has no scalar write-back representation. The pinned
3574            // `TextMut` accumulator already rode the `array_pins` skip above; a
3575            // `TextByte`/`TextConst` here is a per-iteration char/const scratch
3576            // (the `ch` selection, a literal load) — dead at the region boundary
3577            // when NOT loop-carried, so skip its write-back exactly like a
3578            // non-loop-carried Mixed scratch. A genuinely loop-carried text
3579            // (live-in at the head, e.g. an unpinned Text variable) still bails.
3580            Kind::TextByte | Kind::TextConst | Kind::TextMut => {
3581                if live[0][r as usize] {
3582                    if std::env::var_os("LOGOS_RDIAG").is_some() {
3583                        eprintln!("RDIAG-BAIL head_pc={head_pc} text-write-set-loop-carried slot={r} kind={:?}", exit_kind[r as usize]);
3584                    }
3585                    return None;
3586                }
3587            }
3588            k => {
3589                let Some(sk) = k.slot_kind() else {
3590                    if std::env::var_os("LOGOS_RDIAG").is_some() {
3591                        eprintln!("RDIAG-BAIL head_pc={head_pc} write-set-no-slotkind r={r} kind={k:?}");
3592                    }
3593                    return None;
3594                };
3595                write_set.push((r, sk));
3596            }
3597        }
3598    }
3599
3600    let ret_base = pin_base + 3 * array_pins.len() as u16;
3601    let region_return_slots = has_return.then_some((ret_base, ret_base + 1));
3602
3603    // The agreed return kind across every reachable Return site.
3604    use logicaffeine_compile::vm::RegionReturnKind;
3605    let mut ret_kind: Option<RegionReturnKind> = None;
3606    if has_return {
3607        for (i, op) in ops.iter().enumerate() {
3608            let Some(k) = &kin[i] else { continue };
3609            if let Op::Return { src } = *op {
3610                let rk = match k[src as usize] {
3611                    Kind::IntList | Kind::FloatList | Kind::BoolList => {
3612                        RegionReturnKind::Register
3613                    }
3614                    other => RegionReturnKind::Slot(other.slot_kind()?),
3615                };
3616                match ret_kind {
3617                    None => ret_kind = Some(rk),
3618                    Some(prev) if prev == rk => {}
3619                    _ => return None,
3620                }
3621            }
3622        }
3623        // A region whose every Return is unreachable: treat as returnless.
3624        if ret_kind.is_none() {
3625            return None;
3626        }
3627    }
3628    let region_return = match (region_return_slots, ret_kind) {
3629        (Some((flag, val)), Some(kind)) => {
3630            Some(logicaffeine_compile::vm::RegionReturn { flag_slot: flag, value_slot: val, kind })
3631        }
3632        _ => None,
3633    };
3634
3635    // Emit: out-of-region jumps go to the synthetic exit terminal. Adjacent
3636    // cmp + conditional-jump pairs whose scratch is dead FUSE into one
3637    // Branch micro-op (both VM pcs map to it). Two scratch slots past the
3638    // register file carry Int→Float promotions; two more per pinned array
3639    // carry its buffer pointer and length.
3640    let has_calls = ops.iter().any(|op| matches!(op, Op::Call { .. }));
3641    let frame_size = register_count as usize
3642        + 2
3643        + 3 * array_pins.len()
3644        + if has_return { 2 } else { 0 }
3645        + if has_calls { 1 } else { 0 };
3646    // The arena-limit slot is LAST; callees window immediately past the
3647    // frame (region frames get real arena headroom when they call).
3648    let limit_slot = (frame_size - 1) as u16;
3649    let jump_targets: std::collections::HashSet<usize> = ops
3650        .iter()
3651        .filter_map(|op| match *op {
3652            Op::Jump { target }
3653            | Op::JumpIfFalse { target, .. }
3654            | Op::JumpIfTrue { target, .. }
3655            => Some(target),
3656            _ => None,
3657        })
3658        .collect();
3659    let scratch_ok = |r: u16| !is_named(r);
3660    let conv = (register_count, register_count + 1);
3661    let mut micro: Vec<MicroOp> = Vec::with_capacity(ops.len());
3662    let mut pc_to_micro: Vec<usize> = Vec::with_capacity(ops.len());
3663    let mut fixups: Vec<usize> = Vec::new();
3664
3665    // NAIVE SUBSTRING-SEARCH COLLAPSE (string_search): if the WHOLE outer-loop
3666    // region is the exact naive-search nest over two pinned ASCII `TextBytes`
3667    // buffers (text + needle), replace the per-byte nested loop with ONE
3668    // `MemMem` helper call. The helper counts overlapping matches, adds them to
3669    // `count`, and advances `i` to the loop's exit value — bit-identical to the
3670    // nest, with a deopt exit for the one recoverable disagreement (a checked
3671    // needle index past the needle buffer). Gated on: no return, no precise
3672    // requirement, both `text` and `needle` pinned as `TextBytes`, and `count`/
3673    // `i`/`needleLen` carried as plain Int write-back/guard slots.
3674    if !has_return && !needs_precise {
3675        if let Some(shape) = match_memmem_nest(ops, head_pc, exit_pc, constants) {
3676            let text_pin = pins.get(&shape.text_reg);
3677            let needle_pin = pins.get(&shape.needle_reg);
3678            if let (Some(tp), Some(np)) = (text_pin, needle_pin) {
3679                let both_text = tp.elem == PinElem::TextBytes && np.elem == PinElem::TextBytes;
3680                // The accumulators must be plain Int write-back slots (the nest's
3681                // `count`/`i`); `needleLen` must be a guarded Int input. If the
3682                // shape's registers don't line up as Int the recognizer declines.
3683                let count_int = write_set
3684                    .iter()
3685                    .any(|&(r, k)| r == shape.count_reg && k == SlotKind::Int);
3686                let i_int = write_set
3687                    .iter()
3688                    .any(|&(r, k)| r == shape.i_reg && k == SlotKind::Int);
3689                let needle_len_int = guard_kinds
3690                    .iter()
3691                    .any(|&(r, k)| r == shape.needle_len_reg && k == SlotKind::Int);
3692                if both_text && count_int && i_int && needle_len_int {
3693                    let mut mm: Vec<MicroOp> = Vec::with_capacity(2);
3694                    mm.push(MicroOp::MemMem {
3695                        h_ptr_slot: tp.ptr_slot,
3696                        h_len_slot: tp.len_slot,
3697                        n_ptr_slot: np.ptr_slot,
3698                        n_len_slot: np.len_slot,
3699                        needle_len_slot: shape.needle_len_reg,
3700                        i_slot: shape.i_reg,
3701                        count_slot: shape.count_reg,
3702                        helper_addr: logos_rt_memmem_frame as usize as i64,
3703                    });
3704                    mm.push(MicroOp::Return { src: 0 });
3705                    // Only `count` and `i` are observable after the loop (they are
3706                    // the nest's loop-carried write-back); restrict the write-set
3707                    // to them so nothing stale is copied back.
3708                    let mm_write_set: Vec<(u16, SlotKind)> = write_set
3709                        .iter()
3710                        .copied()
3711                        .filter(|&(r, _)| r == shape.count_reg || r == shape.i_reg)
3712                        .collect();
3713                    return Some((
3714                        mm,
3715                        frame_size,
3716                        guard_kinds,
3717                        free,
3718                        mm_write_set,
3719                        array_pins,
3720                        None,
3721                        hoist_guards,
3722                        None,
3723                    ));
3724                }
3725            }
3726        }
3727    }
3728
3729    // The frame buffer is reused across entries — the return flag must be
3730    // explicitly cleared at the head of every run.
3731    if let Some((flag, _)) = region_return_slots {
3732        micro.push(MicroOp::LoadConst { dst: flag, value: 0 });
3733    }
3734
3735    let mut i = 0usize;
3736    while i < ops.len() {
3737        // Region call: the callee windows at base + frame_size (disjoint
3738        // from registers, pins, return and limit slots), arguments copied
3739        // into the fresh window. Entry deopt (callee not yet compiled,
3740        // depth, arena) writes the PLAIN marker — region replay is sound
3741        // because the resolver admitted only scalar-pure callees.
3742        if let Op::Call { dst, args_start, arg_count, func } = ops[i] {
3743            if kin[i].is_some() {
3744                if resolve(func).is_none() {
3745                    if std::env::var_os("LOGOS_RDIAG").is_some() {
3746                        let s = callees.get(func as usize);
3747                        eprintln!(
3748                            "RDIAG-BAIL head_pc={head_pc} lowering-resolve func={func} stable={:?} retparam={:?} ret={:?}",
3749                            s.map(|x| x.list_params_stable),
3750                            s.map(|x| x.returns_list_param),
3751                            s.map(|x| x.ret),
3752                        );
3753                    }
3754                    return None;
3755                }
3756                pc_to_micro.push(micro.len());
3757                let window = frame_size as u16;
3758                for j in 0..arg_count {
3759                    micro.push(MicroOp::Move { dst: window + j, src: args_start + j });
3760                }
3761                micro.push(MicroOp::Call {
3762                    dst,
3763                    args_start: window,
3764                    table_addr: ctx.table.slot_addr(func as usize),
3765                    depth_addr: std::sync::Arc::as_ptr(&ctx.depth) as i64,
3766                    status_addr: std::sync::Arc::as_ptr(&ctx.status) as i64,
3767                    limit_slot,
3768                    depth_limit: logicaffeine_compile::semantics::MAX_CALL_DEPTH as i64,
3769                });
3770            } else {
3771                pc_to_micro.push(micro.len());
3772            }
3773            i += 1;
3774            continue;
3775        }
3776        let Some(advance) = translate_op(
3777            ops,
3778            i,
3779            head_pc,
3780            constants,
3781            &kin,
3782            &live,
3783            &jump_targets,
3784            &scratch_ok,
3785            conv,
3786            &pins,
3787            None,
3788            region_return_slots,
3789            false,
3790            true,
3791            &mut micro,
3792            &mut pc_to_micro,
3793            &mut fixups,
3794        ) else {
3795            if std::env::var_os("LOGOS_RDIAG").is_some() {
3796                eprintln!("RDIAG-BAIL head_pc={head_pc} translate_op-failed op={:?}", ops[i]);
3797            }
3798            return None;
3799        };
3800        i += advance;
3801    }
3802    // Out-of-region jumps route to the synthetic exit terminal.
3803    let exit_micro = micro.len();
3804    micro.push(MicroOp::Return { src: 0 });
3805    let _ = exit_pc;
3806    for &fi in &fixups {
3807        match &mut micro[fi] {
3808            MicroOp::Jump { target }
3809            | MicroOp::JumpIfFalse { target, .. }
3810            | MicroOp::JumpIfTrue { target, .. }
3811            | MicroOp::Branch { target, .. }
3812            | MicroOp::BranchF { target, .. } => {
3813                *target = if in_region(*target) {
3814                    *pc_to_micro.get(*target - head_pc)?
3815                } else {
3816                    exit_micro
3817                };
3818            }
3819            _ => unreachable!(),
3820        }
3821    }
3822    // A push+SetIndex region is admitted ONLY with PRECISE deopt: on a side
3823    // exit the VM materializes the frame's scalars back into the VM registers
3824    // and resumes AT the faulting op. Materialization re-boxes each register by
3825    // its kind at that op (from the kind flow) — Int/Bool/Float by value;
3826    // LIST/MAP and read-only/unknown registers are KEPT (the VM register holds
3827    // the live value: a pinned array is mutated in place through its pins, and a
3828    // read-only slot still holds its entry value). The ONE thing that breaks
3829    // "keep" is a list/map REBIND — a def whose value is a collection, which
3830    // would leave the VM register holding the stale Rc. In-place array
3831    // mutations (`ListPush`/`SetIndex`) are USES of the handle, never defs, so
3832    // they never appear in `writes`; a genuine rebind does, and disqualifies.
3833    let array_reg_set: std::collections::HashSet<u16> =
3834        array_pins.iter().map(|a| a.reg).collect();
3835    let rebinds_collection = writes.iter().any(|&r| {
3836        !array_reg_set.contains(&r)
3837            && matches!(
3838                exit_kind[r as usize],
3839                Kind::IntList | Kind::FloatList | Kind::BoolList | Kind::IntMap
3840            )
3841    });
3842    let precise_region = needs_precise && !rebinds_collection;
3843    if needs_precise && !precise_region {
3844        if std::env::var_os("LOGOS_RDIAG").is_some() {
3845            eprintln!("RDIAG-BAIL head_pc={head_pc} needs-precise-but-rebinds-collection");
3846        }
3847        return None;
3848    }
3849    // Per-op resume codes + per-op materialization kinds. Each micro of bytecode
3850    // op `opi` carries `((head_pc+opi) << 2) | 3`; the matching `kinds_by_pc`
3851    // row says how to re-box every register when the VM resumes there.
3852    let precise = if precise_region {
3853        let mut codes = vec![1i64; micro.len()];
3854        let mut kinds_by_pc: std::collections::HashMap<usize, Vec<Option<SlotKind>>> =
3855            std::collections::HashMap::new();
3856        for opi in 0..ops.len() {
3857            let lo = pc_to_micro[opi];
3858            let hi = pc_to_micro.get(opi + 1).copied().unwrap_or(micro.len());
3859            let resume_pc = head_pc + opi;
3860            let code = ((resume_pc as i64) << 2) | 3;
3861            for c in codes.iter_mut().take(hi).skip(lo) {
3862                *c = code;
3863            }
3864            if let Some(k) = kin.get(opi).and_then(|x| x.as_ref()) {
3865                let row: Vec<Option<SlotKind>> = (0..register_count as usize)
3866                    .map(|r| {
3867                        if array_reg_set.contains(&(r as u16)) {
3868                            return None; // pinned array — keep its live Rc
3869                        }
3870                        match k.get(r) {
3871                            Some(Kind::Int) => Some(SlotKind::Int),
3872                            Some(Kind::Bool) => Some(SlotKind::Bool),
3873                            Some(Kind::Float) => Some(SlotKind::Float),
3874                            // list/map/unknown/mixed read-only → keep VM value
3875                            _ => None,
3876                        }
3877                    })
3878                    .collect();
3879                kinds_by_pc.insert(resume_pc, row);
3880            }
3881        }
3882        Some(RegionPrecise { deopt_codes: codes, kinds_by_pc })
3883    } else {
3884        None
3885    };
3886    Some((
3887        micro,
3888        frame_size,
3889        guard_kinds,
3890        free,
3891        write_set,
3892        array_pins,
3893        region_return,
3894        hoist_guards,
3895        precise,
3896    ))
3897}
3898
3899/// Slots in the per-thread native call arena: deep self-recursion windows
3900/// callee frames upward through this one buffer (16 MiB); the call stencil's
3901/// arena-limit check deopts before any overflow.
3902const ARENA_SLOTS: usize = 1 << 21;
3903
3904struct ChainFn {
3905    chain: CompiledChain,
3906    limit_slot: usize,
3907    depth: std::sync::Arc<std::sync::atomic::AtomicI64>,
3908    ret: NativeRet,
3909    /// frame_size − 3: what the call stencil's bound math expects in the
3910    /// table's regcount slot (mode A: the plain register count).
3911    published_regc: i64,
3912    precise: Option<PreciseInfo>,
3913    stats: std::sync::Arc<RuntimeStats>,
3914}
3915
3916impl NativeFn for ChainFn {
3917    fn call(&self, args: &[i64], pins: &[i64], depth: usize) -> NativeOutcome {
3918        // One arena per thread: the outermost native call owns it for the
3919        // whole (possibly deeply recursive) run. Kind gates prove no slot is
3920        // read before it is written, so stale contents are unobservable.
3921        thread_local! {
3922            static ARENA: std::cell::RefCell<Vec<i64>> = const { std::cell::RefCell::new(Vec::new()) };
3923        }
3924        self.stats.entries.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3925        ARENA.with(|f| {
3926            let mut frame = f.borrow_mut();
3927            if frame.len() < ARENA_SLOTS {
3928                frame.resize(ARENA_SLOTS, 0);
3929            }
3930            frame[..args.len()].copy_from_slice(args);
3931            if let Some(p) = &self.precise {
3932                for (k, chunk) in pins.chunks(3).enumerate() {
3933                    let base = p.param_pin_scatter[k] as usize;
3934                    frame[base..base + 3].copy_from_slice(chunk);
3935                }
3936            }
3937            let arena_end = unsafe { frame.as_ptr().add(ARENA_SLOTS) } as i64;
3938            frame[self.limit_slot] = arena_end;
3939            self.depth.store(depth as i64, std::sync::atomic::Ordering::Relaxed);
3940            match self.chain.run_with_frame(&mut frame) {
3941                ChainOutcome::Return(v) => {
3942                    self.stats
3943                        .completions
3944                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3945                    let out = if matches!(self.ret, NativeRet::ListByHandle) {
3946                        match alloc_registry_detach(v) {
3947                            // A fresh, registry-owned list: re-box here.
3948                            Some(vec) => NativeOutcome::ReturnValue(
3949                                logicaffeine_compile::vm::Value::int_list(vec),
3950                            ),
3951                            // A param passthrough: the VM matches the
3952                            // handle against its pin arguments.
3953                            None => NativeOutcome::Return(v),
3954                        }
3955                    } else {
3956                        NativeOutcome::Return(v)
3957                    };
3958                    alloc_registry_drain();
3959                    out
3960                }
3961                ChainOutcome::Deopt(raw) => {
3962                    if std::env::var_os("LOGOS_JIT_TRACE").is_some() {
3963                        let arena_end = unsafe { frame.as_ptr().add(ARENA_SLOTS) } as i64;
3964                        eprintln!(
3965                            "jit-trace: fn deopt raw={raw:#x} depth={depth} limit_slot={} \
3966                             frame[limit]={:#x} arena_end={:#x} delta={}",
3967                            self.limit_slot,
3968                            frame[self.limit_slot],
3969                            arena_end,
3970                            arena_end - frame[self.limit_slot],
3971                        );
3972                    }
3973                    let out = if raw & 3 == 3 {
3974                        self.stats
3975                            .deopt_ats
3976                            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3977                        let pc = ((raw & 0xFFFF_FFFF) >> 2) as usize;
3978                        let frame_count = ((raw >> 32) - depth as i64 + 1) as usize;
3979                        self.materialize(pc, frame_count, &frame, pins)
3980                    } else {
3981                        self.stats
3982                            .deopts
3983                            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3984                        NativeOutcome::Deopt
3985                    };
3986                    alloc_registry_drain();
3987                    out
3988                }
3989            }
3990        })
3991    }
3992    fn ret(&self) -> NativeRet {
3993        self.ret
3994    }
3995    fn entry_ptr(&self) -> i64 {
3996        self.chain.base() as i64
3997    }
3998    fn published_regc(&self) -> i64 {
3999        self.published_regc
4000    }
4001}
4002
4003impl ChainFn {
4004    /// Precise deopt: walk the active plant chain (every frame's window
4005    /// plant is −1 unless a call is in flight) and package each native
4006    /// frame with its pause-point re-box kinds — the innermost pauses at
4007    /// the faulting pc, every ancestor at its own Call op.
4008    fn materialize(
4009        &self,
4010        pc: usize,
4011        frame_count: usize,
4012        frame: &[i64],
4013        pins: &[i64],
4014    ) -> NativeOutcome {
4015        let p = self
4016            .precise
4017            .as_ref()
4018            .expect("precise deopt status from a mode-A chain");
4019        let rc = p.register_count;
4020        // Exactly `frame_count` frames are live (the depth rode the status
4021        // value): a call-entry deopt leaves the innermost frame's plant
4022        // freshly written with a callee that never materialized, so the
4023        // plant chain alone cannot terminate the walk.
4024        let mut raw: Vec<(usize, i64, usize, u16)> = Vec::with_capacity(frame_count);
4025        let mut base = 0usize;
4026        for k in 0..frame_count {
4027            let window = frame[base + rc + 2];
4028            raw.push((
4029                base,
4030                window,
4031                frame[base + rc + 3] as usize,
4032                frame[base + rc + 4] as u16,
4033            ));
4034            if k + 1 < frame_count {
4035                assert!(
4036                    window > 0,
4037                    "corrupt plant chain: window {window} at depth {k} (pc {pc})"
4038                );
4039                base += window as usize;
4040            }
4041        }
4042        let last = raw.len() - 1;
4043        let mut frames = Vec::with_capacity(raw.len());
4044        for (k, &(b, _, my_resume, _)) in raw.iter().enumerate() {
4045            let pause_pc = if k == last { pc } else { my_resume - 1 };
4046            let mut kinds = p
4047                .kinds_by_pc
4048                .get(&pause_pc)
4049                .unwrap_or_else(|| panic!("missing kind capture for pc {pause_pc}"))
4050                .clone();
4051            // Native-owned lists survive the deopt: detach fresh ones into
4052            // real values; param passthroughs rewrite to their argument.
4053            let mut resolved: Vec<(u16, logicaffeine_compile::vm::Value)> = Vec::new();
4054            if let Some(owned) = p.owned_by_pc.get(&pause_pc) {
4055                for &(reg, vslot) in owned {
4056                    let handle = frame[b + vslot as usize];
4057                    if let Some(vec) = alloc_registry_detach(handle) {
4058                        kinds[reg as usize] = RegBox::Resolved;
4059                        resolved.push((reg, logicaffeine_compile::vm::Value::int_list(vec)));
4060                    } else {
4061                        // A param's vec: match the boundary pin handles.
4062                        let mut hit = false;
4063                        for (j, chunk) in pins.chunks(3).enumerate() {
4064                            if chunk.first() == Some(&handle) {
4065                                kinds[reg as usize] = RegBox::ListParam(j as u8);
4066                                hit = true;
4067                                break;
4068                            }
4069                        }
4070                        if !hit {
4071                            kinds[reg as usize] = RegBox::Dead;
4072                        }
4073                    }
4074                }
4075            }
4076            let (offset, return_pc, return_reg) = if k == 0 {
4077                (0, 0, 0)
4078            } else {
4079                let prev = raw[k - 1];
4080                (prev.1 as usize, prev.2, prev.3)
4081            };
4082            frames.push(NativeFrame {
4083                offset,
4084                return_pc,
4085                return_reg,
4086                regs: frame[b..b + rc].to_vec(),
4087                kinds,
4088                resolved,
4089            });
4090        }
4091        NativeOutcome::DeoptAt { resume_pc: pc, frames }
4092    }
4093}
4094
4095pub static REGION_RUNS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
4096pub static REGION_DEOPTS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
4097
4098struct RegionChain {
4099    chain: CompiledChain,
4100    frame_size: usize,
4101    /// Set when the region CALLS functions: arena headroom for callee
4102    /// windows, the limit slot to plant, and the live depth cell.
4103    call_support: Option<(usize, u16, std::sync::Arc<std::sync::atomic::AtomicI64>)>,
4104    guard: Vec<(u16, SlotKind)>,
4105    free: Vec<u16>,
4106    writes: Vec<(u16, SlotKind)>,
4107    arrays: Vec<ArrayPin>,
4108    region_return: Option<logicaffeine_compile::vm::RegionReturn>,
4109    hoist_guards: Vec<HoistGuard>,
4110    /// PRECISE region deopt: when `Some`, the chain was compiled with per-op
4111    /// `deopt_codes`, so a side exit stores `(resume_pc << 2) | 3` (low 32 bits)
4112    /// in the status cell — decode it to a precise `DeoptAt` and look the
4113    /// per-register re-box kinds up here by resume pc. `None` ⇒ the classic
4114    /// plain-replay region.
4115    precise_kinds: Option<std::collections::HashMap<usize, Vec<Option<SlotKind>>>>,
4116}
4117
4118impl RegionFn for RegionChain {
4119    fn guard_set(&self) -> &[(u16, SlotKind)] {
4120        &self.guard
4121    }
4122    fn free_set(&self) -> &[u16] {
4123        &self.free
4124    }
4125    fn write_set(&self) -> &[(u16, SlotKind)] {
4126        &self.writes
4127    }
4128    fn array_set(&self) -> &[ArrayPin] {
4129        &self.arrays
4130    }
4131    fn region_return(&self) -> Option<logicaffeine_compile::vm::RegionReturn> {
4132        self.region_return
4133    }
4134    fn hoist_guards(&self) -> &[HoistGuard] {
4135        &self.hoist_guards
4136    }
4137    fn frame_size(&self) -> usize {
4138        self.frame_size
4139    }
4140    fn arena_slots(&self) -> usize {
4141        self.call_support.as_ref().map(|(a, _, _)| *a).unwrap_or(0)
4142    }
4143    fn precise_kinds(&self, resume_pc: usize) -> Option<&[Option<SlotKind>]> {
4144        self.precise_kinds
4145            .as_ref()
4146            .and_then(|m| m.get(&resume_pc))
4147            .map(|v| v.as_slice())
4148    }
4149    fn run(&self, frame: &mut [i64], depth: usize) -> RegionOutcome {
4150        if let Some((_, limit_slot, depth_cell)) = &self.call_support {
4151            let arena_end = unsafe { frame.as_ptr().add(frame.len()) } as i64;
4152            frame[*limit_slot as usize] = arena_end;
4153            depth_cell.store(depth as i64, std::sync::atomic::Ordering::Relaxed);
4154        }
4155        match self.chain.run_with_frame(frame) {
4156            ChainOutcome::Return(_) => {
4157                REGION_RUNS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4158                RegionOutcome::Completed
4159            }
4160            ChainOutcome::Deopt(raw) => {
4161                REGION_DEOPTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4162                // Precise chains tag every side exit with `(resume_pc << 2) | 3`
4163                // in the low 32 bits; a plain chain's terminal stores a value
4164                // whose low 2 bits are not 3. Resume precisely when tagged.
4165                if self.precise_kinds.is_some() && (raw & 0b11) == 0b11 {
4166                    let resume_pc = ((raw & 0xFFFF_FFFF) >> 2) as usize;
4167                    RegionOutcome::DeoptAt { resume_pc }
4168                } else {
4169                    RegionOutcome::Deopt
4170                }
4171            }
4172        }
4173    }
4174}
4175
4176/// Runtime boundary counters shared by every ChainFn a tier produces.
4177/// Compilation is not execution: these count what the native code actually
4178/// DID — entries across the VM boundary, completed returns, and side exits.
4179#[derive(Default)]
4180struct RuntimeStats {
4181    entries: std::sync::atomic::AtomicU64,
4182    completions: std::sync::atomic::AtomicU64,
4183    deopts: std::sync::atomic::AtomicU64,
4184    deopt_ats: std::sync::atomic::AtomicU64,
4185}
4186
4187/// The forge-backed native tier, with compile observability.
4188#[derive(Default)]
4189pub struct ForgeTier {
4190    compiles: AtomicU32,
4191    successes: AtomicU32,
4192    region_compiles: AtomicU32,
4193    region_successes: AtomicU32,
4194    /// Region compiles whose chain came from the CONTIGUOUS register-allocating
4195    /// backend (`compile_region_regalloc`) rather than a per-piece stencil tier
4196    /// — the observable that WS-G's backend actually fired on a region.
4197    regalloc_regions: AtomicU32,
4198    /// PRECISE region compiles whose chain came from the contiguous register-
4199    /// allocating backend (`compile_region_regalloc_precise`) rather than the
4200    /// per-piece precise stencil tier (`compile_straightline_coded`). A precise
4201    /// region is the in-place-mutation + reallocating-push shape (the fannkuch /
4202    /// graph_bfs worklist). Nonzero proves the Wave 21 precise-region regalloc
4203    /// path fired — distinct from `regalloc_regions`, which counts a program's
4204    /// non-precise loops too.
4205    regalloc_precise_regions: AtomicU32,
4206    /// FUNCTION compiles whose chain came from the contiguous register-allocating
4207    /// backend (`compile_function_regalloc`) — the observable that the recursion
4208    /// cluster (self-calls) now goes through the regalloc backend instead of the
4209    /// per-piece stencil tier.
4210    regalloc_functions: AtomicU32,
4211    /// Fused pinned-argument self-calls (`CallSelfCopy`) emitted across all
4212    /// function compiles — the observable that Lever A fired.
4213    pinned_self_calls: AtomicU32,
4214    runtime: std::sync::Arc<RuntimeStats>,
4215}
4216
4217impl ForgeTier {
4218    /// A fresh tier with zeroed counters.
4219    pub fn new() -> Self {
4220        ForgeTier::default()
4221    }
4222
4223    /// (attempts, successes) for FUNCTION compiles.
4224    pub fn function_counts(&self) -> (u32, u32) {
4225        (self.compiles.load(Ordering::SeqCst), self.successes.load(Ordering::SeqCst))
4226    }
4227
4228    /// The number of fused pinned-argument self-calls (`CallSelfCopy`)
4229    /// emitted across every function compile — Lever A's observable. A
4230    /// nonzero value proves a scalar self-call took the fused ABI rather
4231    /// than per-argument frame `Move` staging.
4232    pub fn pinned_self_call_count(&self) -> u32 {
4233        self.pinned_self_calls.load(Ordering::SeqCst)
4234    }
4235
4236    /// (attempts, successes) for Main-loop REGION compiles.
4237    pub fn region_counts(&self) -> (u32, u32) {
4238        (
4239            self.region_compiles.load(Ordering::SeqCst),
4240            self.region_successes.load(Ordering::SeqCst),
4241        )
4242    }
4243
4244    /// How many region compiles used the CONTIGUOUS register-allocating backend
4245    /// (WS-G `compile_region_regalloc`) rather than a per-piece stencil tier.
4246    /// Nonzero proves the regalloc backend fired on a real program's hot loop.
4247    pub fn regalloc_region_count(&self) -> u32 {
4248        self.regalloc_regions.load(Ordering::SeqCst)
4249    }
4250
4251    /// How many PRECISE region compiles used the contiguous register-allocating
4252    /// backend (`compile_region_regalloc_precise`) rather than the per-piece
4253    /// precise stencil tier. Nonzero proves the in-place-mutation + reallocating-
4254    /// push shape (fannkuch / graph_bfs worklist) tiered through the regalloc
4255    /// backend — Wave 21's lever.
4256    pub fn regalloc_precise_region_count(&self) -> u32 {
4257        self.regalloc_precise_regions.load(Ordering::SeqCst)
4258    }
4259
4260    /// How many FUNCTION compiles used the contiguous register-allocating backend
4261    /// (`compile_function_regalloc`) rather than the per-piece stencil tier.
4262    /// Nonzero proves a recursive (self-calling) function tiered through the
4263    /// regalloc backend — the recursion cluster's lever.
4264    pub fn regalloc_function_count(&self) -> u32 {
4265        self.regalloc_functions.load(Ordering::SeqCst)
4266    }
4267
4268    /// Runtime FUNCTION-boundary truth: (entries, completions, plain
4269    /// deopts, precise deopts). Entries count VM→native crossings — native
4270    /// self-recursion inside one entry is invisible here, which is exactly
4271    /// the point: a hot recursive function shows FEW entries.
4272    pub fn runtime_stats(&self) -> (u64, u64, u64, u64) {
4273        (
4274            self.runtime.entries.load(Ordering::SeqCst),
4275            self.runtime.completions.load(Ordering::SeqCst),
4276            self.runtime.deopts.load(Ordering::SeqCst),
4277            self.runtime.deopt_ats.load(Ordering::SeqCst),
4278        )
4279    }
4280}
4281
4282/// Loop-nesting depth of each micro op, from its enclosing back-edges (a
4283/// jump/branch whose target is at or before it opens a loop over
4284/// `[target, here]`). Depth is how many such loops contain an op — the
4285/// frequency proxy for region pin selection.
4286fn loop_depths(ops: &[MicroOp]) -> Vec<u32> {
4287    let n = ops.len();
4288    let mut depth = vec![0u32; n];
4289    for (i, op) in ops.iter().enumerate() {
4290        let target = match *op {
4291            MicroOp::Jump { target }
4292            | MicroOp::JumpIfFalse { target, .. }
4293            | MicroOp::JumpIfTrue { target, .. }
4294            | MicroOp::Branch { target, .. }
4295            | MicroOp::BranchF { target, .. } => Some(target),
4296            _ => None,
4297        };
4298        if let Some(t) = target {
4299            if t <= i && t < n {
4300                for d in depth.iter_mut().take(i + 1).skip(t) {
4301                    *d += 1;
4302                }
4303            }
4304        }
4305    }
4306    depth
4307}
4308
4309/// Linear-scan pin selection. Each slot's profit sums its per-op deltas —
4310/// variant-family uses save a memory access (positive), mem-form uses force
4311/// a frame spill (negative).
4312///
4313/// `region` switches the FREQUENCY MODEL. A FUNCTION body is itself the hot
4314/// loop (recursion is a Call, not a back-edge), so every op counts equally
4315/// (the historical flat model — preserved exactly). A REGION's depth-0 ops
4316/// run once per entry while its INNER loops dominate, so each op is weighted
4317/// by `BASE^loop_depth` — a slot wins only when its register-resident
4318/// variant uses outweigh its spill costs AT THE FREQUENCY THEY EXECUTE.
4319/// (Without this, a counter feeding an inner-loop array INDEX — mem-form,
4320/// spills every iteration — looks neutral statically but is a net loss; see
4321/// histogram vs sieve.) Conversion scratches and beyond-register slots
4322/// never pin; the four most profitable net-positive slots win.
4323///
4324/// The slots a micro-op READS, by value (for read-counting). Call/CallSelf are
4325/// handled by the caller (copy-prop bails on any region containing one), so
4326/// their frame-window arg reads need no accounting here.
4327fn micro_read_slots(op: &MicroOp) -> Vec<u16> {
4328    use MicroOp::*;
4329    match *op {
4330        LoadConst { .. } | Jump { .. } | NewList { .. } => vec![],
4331        Move { src, .. } | DivPow2 { lhs: src, .. } | MagicDivU { lhs: src, .. } | NotInt { src, .. } | NotBool { src, .. }
4332        | IntToFloat { src, .. } | SqrtF { src, .. } | Return { src } | ListTriple { handle_slot: src, .. } => vec![src],
4333        JumpIfFalse { cond, .. } | JumpIfTrue { cond, .. } => vec![cond],
4334        Add { lhs, rhs, .. } | Sub { lhs, rhs, .. } | Mul { lhs, rhs, .. } | Div { lhs, rhs, .. }
4335        | Mod { lhs, rhs, .. } | Lt { lhs, rhs, .. } | Gt { lhs, rhs, .. } | Eq { lhs, rhs, .. }
4336        | LtEq { lhs, rhs, .. } | GtEq { lhs, rhs, .. } | Neq { lhs, rhs, .. } | BitAnd { lhs, rhs, .. }
4337        | BitOr { lhs, rhs, .. } | BitXor { lhs, rhs, .. } | Shl { lhs, rhs, .. } | Shr { lhs, rhs, .. }
4338        | Branch { lhs, rhs, .. } | AddF { lhs, rhs, .. } | SubF { lhs, rhs, .. } | MulF { lhs, rhs, .. }
4339        | DivF { lhs, rhs, .. } | LtF { lhs, rhs, .. } | GtF { lhs, rhs, .. } | LtEqF { lhs, rhs, .. }
4340        | GtEqF { lhs, rhs, .. } | EqF { lhs, rhs, .. } | NeqF { lhs, rhs, .. }
4341        | BranchF { lhs, rhs, .. } => vec![lhs, rhs],
4342        MapGet { key, map_slot, .. } | MapHas { key, map_slot, .. } => vec![key, map_slot],
4343        MapSet { src, key, map_slot, .. } => vec![src, key, map_slot],
4344        ArrLoad { idx, ptr_slot, len_slot, .. } => vec![idx, ptr_slot, len_slot],
4345        ArrLoadAffine { a, op, b, ptr_slot, len_slot, .. } => {
4346            if op == logicaffeine_forge::jit::AffOp::None {
4347                vec![a, ptr_slot, len_slot]
4348            } else {
4349                vec![a, b, ptr_slot, len_slot]
4350            }
4351        }
4352        ArrLoad2F { i, j, ptr_slot, len_slot, .. } => vec![i, j, ptr_slot, len_slot],
4353        ArrLoad2 { i, j, ptr_a, len_a, ptr_b, len_b, .. } => vec![i, j, ptr_a, len_a, ptr_b, len_b],
4354        ArrStore { src, idx, ptr_slot, len_slot, .. } => vec![src, idx, ptr_slot, len_slot],
4355        ArrRMW { idx, operand, ptr_slot, len_slot, .. } => vec![idx, operand, ptr_slot, len_slot],
4356        ArrCondSwap { idx1, idx2, ptr_slot, len_slot, .. } => vec![idx1, idx2, ptr_slot, len_slot],
4357        ArrSwap { idx1, idx2, ptr_slot, len_slot, .. } => vec![idx1, idx2, ptr_slot, len_slot],
4358        FmaF { a, b, c, .. } => vec![a, b, c],
4359        ArrPush { src, vec_slot, ptr_slot, len_slot, .. } => vec![src, vec_slot, ptr_slot, len_slot],
4360        ListClear { vec_slot, ptr_slot, len_slot, .. } => vec![vec_slot, ptr_slot, len_slot],
4361        StrAppend { text_handle_slot, src, .. } => match src {
4362            logicaffeine_forge::jit::StrSrc::Byte(s) => vec![text_handle_slot, s],
4363            logicaffeine_forge::jit::StrSrc::Const { .. } => vec![text_handle_slot],
4364        },
4365        MemMem {
4366            h_ptr_slot,
4367            h_len_slot,
4368            n_ptr_slot,
4369            n_len_slot,
4370            needle_len_slot,
4371            i_slot,
4372            count_slot,
4373            ..
4374        } => vec![
4375            h_ptr_slot,
4376            h_len_slot,
4377            n_ptr_slot,
4378            n_len_slot,
4379            needle_len_slot,
4380            i_slot,
4381            count_slot,
4382        ],
4383        Call { .. } | CallSelf { .. } | CallSelfCopy { .. } => vec![],
4384    }
4385}
4386
4387/// Mutable references to a micro-op's READ operands (for in-place copy
4388/// substitution). Mirrors [`micro_read_slots`].
4389fn micro_reads_mut(op: &mut MicroOp) -> Vec<&mut u16> {
4390    use MicroOp::*;
4391    match op {
4392        LoadConst { .. } | Jump { .. } | NewList { .. } | Call { .. } | CallSelf { .. }
4393        | CallSelfCopy { .. } => vec![],
4394        Move { src, .. } | DivPow2 { lhs: src, .. } | MagicDivU { lhs: src, .. } | NotInt { src, .. } | NotBool { src, .. }
4395        | IntToFloat { src, .. } | SqrtF { src, .. } | Return { src } | ListTriple { handle_slot: src, .. } => vec![src],
4396        JumpIfFalse { cond, .. } | JumpIfTrue { cond, .. } => vec![cond],
4397        Add { lhs, rhs, .. } | Sub { lhs, rhs, .. } | Mul { lhs, rhs, .. } | Div { lhs, rhs, .. }
4398        | Mod { lhs, rhs, .. } | Lt { lhs, rhs, .. } | Gt { lhs, rhs, .. } | Eq { lhs, rhs, .. }
4399        | LtEq { lhs, rhs, .. } | GtEq { lhs, rhs, .. } | Neq { lhs, rhs, .. } | BitAnd { lhs, rhs, .. }
4400        | BitOr { lhs, rhs, .. } | BitXor { lhs, rhs, .. } | Shl { lhs, rhs, .. } | Shr { lhs, rhs, .. }
4401        | Branch { lhs, rhs, .. } | AddF { lhs, rhs, .. } | SubF { lhs, rhs, .. } | MulF { lhs, rhs, .. }
4402        | DivF { lhs, rhs, .. } | LtF { lhs, rhs, .. } | GtF { lhs, rhs, .. } | LtEqF { lhs, rhs, .. }
4403        | GtEqF { lhs, rhs, .. } | EqF { lhs, rhs, .. } | NeqF { lhs, rhs, .. }
4404        | BranchF { lhs, rhs, .. } => vec![lhs, rhs],
4405        MapGet { key, map_slot, .. } | MapHas { key, map_slot, .. } => vec![key, map_slot],
4406        MapSet { src, key, map_slot, .. } => vec![src, key, map_slot],
4407        ArrLoad { idx, ptr_slot, len_slot, .. } => vec![idx, ptr_slot, len_slot],
4408        ArrLoadAffine { a, b, ptr_slot, len_slot, .. } => vec![a, b, ptr_slot, len_slot],
4409        ArrLoad2F { i, j, ptr_slot, len_slot, .. } => vec![i, j, ptr_slot, len_slot],
4410        ArrLoad2 { i, j, ptr_a, len_a, ptr_b, len_b, .. } => vec![i, j, ptr_a, len_a, ptr_b, len_b],
4411        ArrStore { src, idx, ptr_slot, len_slot, .. } => vec![src, idx, ptr_slot, len_slot],
4412        ArrRMW { idx, operand, ptr_slot, len_slot, .. } => vec![idx, operand, ptr_slot, len_slot],
4413        ArrCondSwap { idx1, idx2, ptr_slot, len_slot, .. } => vec![idx1, idx2, ptr_slot, len_slot],
4414        ArrSwap { idx1, idx2, ptr_slot, len_slot, .. } => vec![idx1, idx2, ptr_slot, len_slot],
4415        FmaF { a, b, c, .. } => vec![a, b, c],
4416        ArrPush { src, vec_slot, ptr_slot, len_slot, .. } => vec![src, vec_slot, ptr_slot, len_slot],
4417        ListClear { vec_slot, ptr_slot, len_slot, .. } => vec![vec_slot, ptr_slot, len_slot],
4418        StrAppend { text_handle_slot, src, .. } => match src {
4419            logicaffeine_forge::jit::StrSrc::Byte(s) => vec![text_handle_slot, s],
4420            logicaffeine_forge::jit::StrSrc::Const { .. } => vec![text_handle_slot],
4421        },
4422        MemMem {
4423            h_ptr_slot,
4424            h_len_slot,
4425            n_ptr_slot,
4426            n_len_slot,
4427            needle_len_slot,
4428            i_slot,
4429            count_slot,
4430            ..
4431        } => vec![
4432            h_ptr_slot,
4433            h_len_slot,
4434            n_ptr_slot,
4435            n_len_slot,
4436            needle_len_slot,
4437            i_slot,
4438            count_slot,
4439        ],
4440    }
4441}
4442
4443/// The slots a micro-op DEFINES (writes).
4444fn micro_defs(op: &MicroOp) -> Vec<u16> {
4445    use MicroOp::*;
4446    match *op {
4447        LoadConst { dst, .. } | Move { dst, .. } | Add { dst, .. } | Sub { dst, .. } | Mul { dst, .. }
4448        | Div { dst, .. } | DivPow2 { dst, .. } | MagicDivU { dst, .. } | Mod { dst, .. } | Lt { dst, .. } | Gt { dst, .. }
4449        | Eq { dst, .. } | LtEq { dst, .. } | GtEq { dst, .. } | Neq { dst, .. } | BitAnd { dst, .. }
4450        | BitOr { dst, .. } | BitXor { dst, .. } | Shl { dst, .. } | Shr { dst, .. } | NotInt { dst, .. }
4451        | NotBool { dst, .. } | AddF { dst, .. } | SubF { dst, .. } | MulF { dst, .. } | DivF { dst, .. }
4452        | LtF { dst, .. } | GtF { dst, .. } | LtEqF { dst, .. } | GtEqF { dst, .. } | EqF { dst, .. }
4453        | NeqF { dst, .. } | IntToFloat { dst, .. } | SqrtF { dst, .. } | ArrLoad { dst, .. }
4454        | ArrLoadAffine { dst, .. }
4455        | ArrLoad2F { dst, .. } | ArrLoad2 { dst, .. } | FmaF { dst, .. } | MapGet { dst, .. } | MapHas { dst, .. }
4456        | Call { dst, .. } | CallSelf { dst, .. } | CallSelfCopy { dst, .. } => vec![dst],
4457        ArrPush { ptr_slot, len_slot, .. } => vec![ptr_slot, len_slot],
4458        ListClear { ptr_slot, len_slot, .. } => vec![ptr_slot, len_slot],
4459        NewList { vec_slot, ptr_slot, len_slot, .. } => vec![vec_slot, ptr_slot, len_slot],
4460        ListTriple { vec_slot, ptr_slot, len_slot, .. } => vec![vec_slot, ptr_slot, len_slot],
4461        MemMem { i_slot, count_slot, .. } => vec![i_slot, count_slot],
4462        Branch { .. } | BranchF { .. } | Jump { .. } | JumpIfFalse { .. } | JumpIfTrue { .. }
4463        | MapSet { .. } | ArrStore { .. } | ArrRMW { .. } | ArrCondSwap { .. } | ArrSwap { .. } | Return { .. }
4464        // The mutable-Text append writes the off-frame VM register cell (through
4465        // the planted `*mut Value`), not a frame slot — it defines NO frame slot.
4466        | StrAppend { .. } => vec![],
4467    }
4468}
4469
4470/// A mutable reference to a micro-op's branch/jump TARGET (a micro index), if any.
4471fn micro_target_mut(op: &mut MicroOp) -> Option<&mut usize> {
4472    use MicroOp::*;
4473    match op {
4474        Jump { target } | Branch { target, .. } | BranchF { target, .. }
4475        | JumpIfFalse { target, .. } | JumpIfTrue { target, .. } => Some(target),
4476        _ => None,
4477    }
4478}
4479
4480/// COPY PROPAGATION + dead-Move elimination over a region's micro stream — kills
4481/// the redundant copy chains the bytecode lowering leaves in hot loops (e.g.
4482/// mandelbrot's `t40←t36; t46←t40; zr←t46`, three Moves to land one value),
4483/// each of which is a wasted stencil dispatch every iteration. SOUNDNESS: it
4484/// only ever (1) rewrites a READ to a value provably equal to it within the
4485/// same basic block (copies are cleared at every jump target and across every
4486/// branch/jump), and (2) removes a `Move` whose destination is an UN-NAMED
4487/// scratch temp (never written back to a VM register — `named[d]` is false) and
4488/// has zero remaining reads. Named slots (loop-carried / observable) are never
4489/// touched. The caller restricts this to NON-PRECISE, CALL-FREE regions, so the
4490/// only index-bearing references that survive removal are jump targets, which
4491/// are remapped here; `deopt` is replay-from-head (no per-micro resume).
4492fn copy_propagate(
4493    micro: &mut Vec<MicroOp>,
4494    named: &[bool],
4495    register_count: u16,
4496    pinned: &std::collections::HashSet<u16>,
4497) {
4498    if micro.iter().any(|op| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })) {
4499        return;
4500    }
4501    // A slot is REMOVABLE only if it is an un-named, un-PINNED scratch temp: its
4502    // value is never written back to a VM register (not named) and it is not
4503    // held live in a register by the pin allocator. This lets the pass run AFTER
4504    // pin selection without disturbing the chosen pins (named/pinned defining ops
4505    // and their reads are left intact).
4506    let removable = |slot: u16| -> bool {
4507        slot < register_count
4508            && !named.get(slot as usize).copied().unwrap_or(true)
4509            && !pinned.contains(&slot)
4510    };
4511    let n = micro.len();
4512    let mut leader = vec![false; n];
4513    for op in micro.iter() {
4514        if let Some(t) = micro_target_const(op) {
4515            if t < n {
4516                leader[t] = true;
4517            }
4518        }
4519    }
4520    // Phase 1: forward block-local value propagation. `root[d] = s` means slot d
4521    // currently holds an unmodified copy of slot s; reads of d are rewritten to
4522    // s. `const_holder[v] = slot` tracks the live slot holding integer-bits
4523    // constant `v`, so a re-`LoadConst` of the same value aliases the existing
4524    // holder (CONSTANT CSE — hot loops re-load the same small constants every
4525    // iteration). Both maps clear at block boundaries and invalidate when a
4526    // tracked slot is redefined.
4527    let mut root: std::collections::HashMap<u16, u16> = std::collections::HashMap::new();
4528    let mut const_holder: std::collections::HashMap<i64, u16> = std::collections::HashMap::new();
4529    // `value_holder[(tag, a, b)] = slot` — the live slot already holding the
4530    // result of that pure op over those operands. A redundant recomputation
4531    // (e.g. `v + 1` evaluated for both the load index and the store index of an
4532    // array RMW) aliases the existing holder, which (a) cuts a stencil dispatch
4533    // and (b) makes the two indices the SAME slot, letting `fuse_array_rmw`
4534    // collapse the load/store. Local value numbering: cleared at block
4535    // boundaries, invalidated the instant any operand or the holder is redefined.
4536    let mut value_holder: std::collections::HashMap<(u8, u16, u16), u16> =
4537        std::collections::HashMap::new();
4538    for i in 0..n {
4539        if leader[i] {
4540            root.clear();
4541            const_holder.clear();
4542            value_holder.clear();
4543        }
4544        for r in micro_reads_mut(&mut micro[i]) {
4545            if let Some(&s) = root.get(r) {
4546                *r = s;
4547            }
4548        }
4549        // Value key + prior holder, captured from the operands AS SUBSTITUTED
4550        // above and BEFORE this op's own def invalidates the table.
4551        let value_key = micro_pure_value_key(&micro[i]);
4552        let cse_hit = value_key.and_then(|(k, _)| value_holder.get(&k).copied());
4553        let defs = micro_defs(&micro[i]);
4554        for &d in &defs {
4555            root.retain(|k, v| *k != d && *v != d);
4556            const_holder.retain(|_, s| *s != d);
4557            value_holder.retain(|k, s| k.1 != d && k.2 != d && *s != d);
4558        }
4559        match micro[i] {
4560            MicroOp::Move { dst, src } if dst != src => {
4561                let sroot = root.get(&src).copied().unwrap_or(src);
4562                if sroot != dst {
4563                    root.insert(dst, sroot);
4564                }
4565            }
4566            MicroOp::LoadConst { dst, value } => match const_holder.get(&value) {
4567                Some(&d2) if d2 != dst => {
4568                    root.insert(dst, d2);
4569                }
4570                Some(_) => {}
4571                None => {
4572                    const_holder.insert(value, dst);
4573                }
4574            },
4575            _ => {}
4576        }
4577        if let Some((k, dst)) = value_key {
4578            match cse_hit {
4579                Some(h) if h != dst => {
4580                    root.insert(dst, h);
4581                }
4582                _ => {
4583                    value_holder.insert(k, dst);
4584                }
4585            }
4586        }
4587        // NB: copies/CSE are NOT cleared after a branch. A branch defines no
4588        // slot, so its fall-through (a single-predecessor block) validly inherits
4589        // them; the only multi-predecessor blocks are JUMP TARGETS, which the
4590        // leader-clear at the top of the loop already resets. (Clearing here too
4591        // was overly conservative — it dropped copies that hold across the
4592        // compare-branch into a loop's conditional body, e.g. a sort's swap.)
4593    }
4594    // Phase 2: remove dead scratch Moves / LoadConsts / pure binops (0 remaining
4595    // reads after propagation), remapping jump targets after each removal.
4596    loop {
4597        let mut reads: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
4598        for op in micro.iter() {
4599            for s in micro_read_slots(op) {
4600                *reads.entry(s).or_insert(0) += 1;
4601            }
4602        }
4603        let dead = |dst: u16| removable(dst) && reads.get(&dst).copied().unwrap_or(0) == 0;
4604        let victim = micro.iter().position(|op| match *op {
4605            MicroOp::Move { dst, src } => dst != src && dead(dst),
4606            MicroOp::LoadConst { dst, .. } => dead(dst),
4607            _ => micro_pure_value_key(op).is_some_and(|(_, dst)| dead(dst)),
4608        });
4609        let Some(idx) = victim else { break };
4610        micro.remove(idx);
4611        for op in micro.iter_mut() {
4612            if let Some(t) = micro_target_mut(op) {
4613                if *t > idx {
4614                    *t -= 1;
4615                }
4616            }
4617        }
4618    }
4619    // Phase 3: DEAD-STORE elimination — a pure def whose dst is REDEFINED before
4620    // it is ever read is dead even if the dst is read LATER (register reuse, e.g.
4621    // a sort's recomputed `j+1` index whose slot is reused for the loop-increment
4622    // constant). Global read-counting (phase 2) keeps these; a forward straight-
4623    // line scan removes them. Conservative: stop the scan at any control-flow op
4624    // or jump target (the value could be live on another path → keep). Only
4625    // un-named/un-pinned scratch (the `removable` slots) is eligible.
4626    loop {
4627        let mut victim = None;
4628        'scan: for i in 0..micro.len() {
4629            let d = match micro[i] {
4630                MicroOp::Move { dst, src } if dst != src => dst,
4631                MicroOp::LoadConst { dst, .. } => dst,
4632                ref other => match micro_pure_value_key(other) {
4633                    Some((_, dst)) => dst,
4634                    None => continue,
4635                },
4636            };
4637            if !removable(d) {
4638                continue;
4639            }
4640            // Walk forward: `d` is dead at `i` iff it is REDEFINED before any
4641            // read. We stop at a control-flow SOURCE op (a branch/jump could
4642            // carry `d`'s value to a taken path that reads it) — but NOT at a
4643            // mere jump TARGET: a leader that redefines `d` as its first action
4644            // kills every incoming `d`, so checking it is sound.
4645            for op in micro.iter().skip(i + 1) {
4646                if micro_read_slots(op).contains(&d) {
4647                    break; // read before redef — live; keep.
4648                }
4649                if micro_defs(op).contains(&d) {
4650                    victim = Some(i); // redefined before any read — op `i` is dead.
4651                    break 'scan;
4652                }
4653                if micro_target_const(op).is_some() {
4654                    break; // control transfers away — be conservative; keep.
4655                }
4656            }
4657        }
4658        let Some(idx) = victim else { break };
4659        micro.remove(idx);
4660        for op in micro.iter_mut() {
4661            if let Some(t) = micro_target_mut(op) {
4662                if *t > idx {
4663                    *t -= 1;
4664                }
4665            }
4666        }
4667    }
4668}
4669
4670/// ARRAY READ-MODIFY-WRITE FUSION — collapses the `ArrLoad(t = arr[i]) ;
4671/// <int ALU>(t2 = t OP operand) ; ArrStore(arr[i] = t2)` idiom (histogram's
4672/// `counts[x] += 1`, counting sort, bitset marks) into ONE [`MicroOp::ArrRMW`]:
4673/// one stencil dispatch + one bounds check instead of three, and the element
4674/// never round-trips the frame. The JIT is per-op DISPATCH-bound, so collapsing
4675/// three hot ops into one is a direct win on the indexed-RMW loops that lose
4676/// worst to V8.
4677///
4678/// SOUNDNESS: the load and store hit the SAME pinned 8-byte array and the SAME
4679/// index slot (unchanged between them — the ALU only writes the scratch `t2`),
4680/// so the read cell and the written cell coincide. A single pre-write bounds
4681/// check (`checked = c1 || c2`) is bit-identical to the original pair because
4682/// both tested that same index. The fused op side-exits BEFORE any effect and,
4683/// occupying the ArrLoad's micro slot, replays all three ops from that bytecode
4684/// pc (region deopt is replay-from-head). The loaded value `t` and the result
4685/// `t2` must be single-use, un-named, un-pinned scratch (read only by the ALU /
4686/// the store) — exactly the staleness contract `copy_propagate` already relies
4687/// on, so a dead frame cell is never observably written back. None of the three
4688/// ops may be a jump target (no entry lands mid-idiom). Caller restricts this to
4689/// NON-PRECISE regions (removing micro ops renumbers jump targets — remapped
4690/// here — but would break per-op precise resume codes).
4691fn fuse_array_rmw(
4692    micro: &mut Vec<MicroOp>,
4693    named: &[bool],
4694    register_count: u16,
4695    pinned: &std::collections::HashSet<u16>,
4696) {
4697    if micro.iter().any(|op| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })) {
4698        return;
4699    }
4700    let removable = |slot: u16, reads: &std::collections::HashMap<u16, usize>| -> bool {
4701        slot < register_count
4702            && !named.get(slot as usize).copied().unwrap_or(true)
4703            && !pinned.contains(&slot)
4704            && reads.get(&slot).copied().unwrap_or(0) == 1
4705    };
4706    loop {
4707        let n = micro.len();
4708        // Block leaders: an op any branch/jump lands on. Recomputed each round
4709        // (a fusion renumbers the stream).
4710        let mut is_target = vec![false; n];
4711        for op in micro.iter() {
4712            if let Some(t) = micro_target_const(op) {
4713                if t < n {
4714                    is_target[t] = true;
4715                }
4716            }
4717        }
4718        let mut reads: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
4719        for op in micro.iter() {
4720            for s in micro_read_slots(op) {
4721                *reads.entry(s).or_insert(0) += 1;
4722            }
4723        }
4724        let mut hit: Option<(usize, MicroOp)> = None;
4725        for k in 0..n.saturating_sub(2) {
4726            // The ALU and the store must not be block leaders.
4727            if is_target[k + 1] || is_target[k + 2] {
4728                continue;
4729            }
4730            let MicroOp::ArrLoad { dst: t, idx: i, ptr_slot: p, len_slot: l, byte: false, narrow32: false, checked: c1 } =
4731                micro[k]
4732            else {
4733                continue;
4734            };
4735            // The ALU op: the loaded value `t` is one operand; the OTHER is the
4736            // RMW operand. Sub is non-commutative — fuse only when `t` is on the
4737            // left (`buf[i] - operand`).
4738            let (t2, op, operand) = match micro[k + 1] {
4739                MicroOp::Add { dst, lhs, rhs } if lhs == t => (dst, RmwOp::Add, rhs),
4740                MicroOp::Add { dst, lhs, rhs } if rhs == t => (dst, RmwOp::Add, lhs),
4741                MicroOp::Sub { dst, lhs, rhs } if lhs == t => (dst, RmwOp::Sub, rhs),
4742                MicroOp::Mul { dst, lhs, rhs } if lhs == t => (dst, RmwOp::Mul, rhs),
4743                MicroOp::Mul { dst, lhs, rhs } if rhs == t => (dst, RmwOp::Mul, lhs),
4744                MicroOp::BitAnd { dst, lhs, rhs } if lhs == t => (dst, RmwOp::And, rhs),
4745                MicroOp::BitAnd { dst, lhs, rhs } if rhs == t => (dst, RmwOp::And, lhs),
4746                MicroOp::BitOr { dst, lhs, rhs } if lhs == t => (dst, RmwOp::Or, rhs),
4747                MicroOp::BitOr { dst, lhs, rhs } if rhs == t => (dst, RmwOp::Or, lhs),
4748                MicroOp::BitXor { dst, lhs, rhs } if lhs == t => (dst, RmwOp::Xor, rhs),
4749                MicroOp::BitXor { dst, lhs, rhs } if rhs == t => (dst, RmwOp::Xor, lhs),
4750                // Float RMW (nbody's `v[i] = v[i] + dx*mag`): the array holds
4751                // f64 bits; the fused op reinterprets. Same commutativity rules.
4752                MicroOp::AddF { dst, lhs, rhs } if lhs == t => (dst, RmwOp::AddF, rhs),
4753                MicroOp::AddF { dst, lhs, rhs } if rhs == t => (dst, RmwOp::AddF, lhs),
4754                MicroOp::SubF { dst, lhs, rhs } if lhs == t => (dst, RmwOp::SubF, rhs),
4755                MicroOp::MulF { dst, lhs, rhs } if lhs == t => (dst, RmwOp::MulF, rhs),
4756                MicroOp::MulF { dst, lhs, rhs } if rhs == t => (dst, RmwOp::MulF, lhs),
4757                _ => continue,
4758            };
4759            let MicroOp::ArrStore { src, idx: i2, ptr_slot: p2, len_slot: l2, byte: false, narrow32: false, checked: c2 } =
4760                micro[k + 2]
4761            else {
4762                continue;
4763            };
4764            // Same array + index, and the store consumes exactly the ALU result.
4765            if src != t2 || i2 != i || p2 != p || l2 != l {
4766                continue;
4767            }
4768            // `t` and `t2` are single-use scratch we can erase; `operand` is
4769            // re-read by the fused op (so it must NOT be one of them), and the
4770            // index `i` likewise stays live.
4771            if !removable(t, &reads) || !removable(t2, &reads) || operand == t || operand == t2 {
4772                continue;
4773            }
4774            hit = Some((
4775                k,
4776                MicroOp::ArrRMW { idx: i, operand, ptr_slot: p, len_slot: l, op, checked: c1 || c2 },
4777            ));
4778            break;
4779        }
4780        let Some((k, rmw)) = hit else { break };
4781        if std::env::var_os("LOGOS_FUSE_TRACE").is_some() {
4782            eprintln!("fuse-trace: ARRRMW {rmw:?}");
4783        }
4784        micro[k] = rmw;
4785        micro.remove(k + 2);
4786        micro.remove(k + 1);
4787        // Two ops vanished at k+1, k+2 (neither a jump target). Shift targets
4788        // above them down; a jump to the fused op (k) stays put.
4789        for op in micro.iter_mut() {
4790            if let Some(t) = micro_target_mut(op) {
4791                if *t > k + 2 {
4792                    *t -= 2;
4793                } else if *t > k {
4794                    *t -= 1;
4795                }
4796            }
4797        }
4798    }
4799}
4800
4801/// TWO-BUFFER INTEGER LOAD+BINOP FUSION — collapses `ArrLoad(t1 = a[i]); …;
4802/// ArrLoad(t2 = b[j]); …; {Add,Sub,Mul}(dst = t1 OP t2)` (the matrix-multiply /
4803/// dot-product idiom `c += a[..] * b[..]`) into one [`MicroOp::ArrLoad2`] placed
4804/// at the binop: ONE stencil dispatch + ONE combined bounds gate instead of
4805/// three ops, and the two loaded i64s never round-trip the frame. The JIT is
4806/// per-op DISPATCH-bound, so folding three hot inner-loop ops into one is a
4807/// direct win on the indexed arithmetic loops (matrix_mult) that lose worst to
4808/// V8. The two loads need NOT be adjacent (matmul computes `b`'s index between
4809/// them) — only their values, indices, and buffers must reach the binop
4810/// unchanged. The two buffers may be distinct (`a`, `b`) or the same (a self dot
4811/// product); each must be a pinned 8-byte INT buffer.
4812///
4813/// SOUNDNESS — the fused op evaluates `a[i-1] OP b[j-1]` with the kernel's
4814/// wrapping i64 semantics, bit-identical to the original ArrLoad+ArrLoad+ALU,
4815/// provided the two loaded values are exactly the ones the binop consumed:
4816/// - both loaded scratch values `t1`/`t2` are SINGLE-USE (read only by the
4817///   binop), distinct, un-named, un-pinned — so erasing their frame writes
4818///   loses no observable value (the `copy_propagate` staleness contract);
4819/// - between the EARLIER load and the binop, NOTHING redefines either index
4820///   (`i`/`j`) or either pointer/length slot, and NOTHING writes memory (no
4821///   array store/RMW/swap/push, map set, or list rebuild) — so neither buffer
4822///   nor index can change under the loads being hoisted to the binop site;
4823/// - no block leader (jump target) lands strictly AFTER the earlier load up to
4824///   and including the binop — otherwise an entry would skip a load.
4825/// The combined `checked = c1 || c2` is at least as safe as the pair (an
4826/// unchecked load proven in-bounds is in range by hypothesis, so re-checking it
4827/// never fires). The fused op side-exits BEFORE any effect on out-of-bounds and,
4828/// in a replay-from-head region, the deopt re-runs the whole region from its
4829/// entry — so moving the bounds gates to the binop site changes no observable
4830/// result (the only intervening ops are pure arithmetic on dead scratch).
4831/// Add/Mul commute (`t1`/`t2` in either order); Sub fuses only `a[i] - b[j]`
4832/// (the first load on the left). BYTE (Bool) buffers never fuse — the fused
4833/// stencil reads raw i64s. Caller restricts this to NON-PRECISE regions (it
4834/// renumbers micro indices).
4835fn fuse_array_ld2(
4836    micro: &mut Vec<MicroOp>,
4837    named: &[bool],
4838    register_count: u16,
4839    pinned: &std::collections::HashSet<u16>,
4840) {
4841    if micro.iter().any(|op| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })) {
4842        return;
4843    }
4844    let removable = |slot: u16, reads: &std::collections::HashMap<u16, usize>| -> bool {
4845        slot < register_count
4846            && !named.get(slot as usize).copied().unwrap_or(true)
4847            && !pinned.contains(&slot)
4848            && reads.get(&slot).copied().unwrap_or(0) == 1
4849    };
4850    // An op that writes anywhere in memory (any pinned array / map / list) —
4851    // we cannot statically tell which buffer it aliases, so any such op between
4852    // a load and the binop blocks the hoist conservatively.
4853    let writes_memory = |op: &MicroOp| -> bool {
4854        matches!(
4855            op,
4856            MicroOp::ArrStore { .. }
4857                | MicroOp::ArrRMW { .. }
4858                | MicroOp::ArrSwap { .. }
4859                | MicroOp::ArrCondSwap { .. }
4860                | MicroOp::ArrPush { .. }
4861                | MicroOp::MapSet { .. }
4862                | MicroOp::NewList { .. }
4863                | MicroOp::ListClear { .. }
4864                | MicroOp::ListTriple { .. }
4865        )
4866    };
4867    loop {
4868        let n = micro.len();
4869        let mut is_target = vec![false; n];
4870        for op in micro.iter() {
4871            if let Some(t) = micro_target_const(op) {
4872                if t < n {
4873                    is_target[t] = true;
4874                }
4875            }
4876        }
4877        let mut reads: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
4878        for op in micro.iter() {
4879            for s in micro_read_slots(op) {
4880                *reads.entry(s).or_insert(0) += 1;
4881            }
4882        }
4883        // Anchor on the binop; locate the two ArrLoads that define its operands.
4884        let mut hit: Option<(usize, usize, usize, MicroOp)> = None;
4885        'anchor: for m in 0..n {
4886            let (binop_kind, dst, lhs, rhs) = match micro[m] {
4887                MicroOp::Add { dst, lhs, rhs } => (IOp::Add, dst, lhs, rhs),
4888                MicroOp::Sub { dst, lhs, rhs } => (IOp::Sub, dst, lhs, rhs),
4889                MicroOp::Mul { dst, lhs, rhs } => (IOp::Mul, dst, lhs, rhs),
4890                _ => continue,
4891            };
4892            if lhs == rhs || dst == lhs || dst == rhs {
4893                continue;
4894            }
4895            // The most-recent ArrLoad defining a slot strictly before m, if its
4896            // value provably still holds at m (single-use scratch, and the load
4897            // is not itself re-defined in between — guaranteed by single-use of
4898            // its dst plus the no-rebind interval check below).
4899            let find_load = |slot: u16| -> Option<usize> {
4900                (0..m).rev().find(|&k| micro_defs(&micro[k]).contains(&slot))
4901            };
4902            let (Some(ka), Some(kb)) = (find_load(lhs), find_load(rhs)) else { continue };
4903            if ka == kb {
4904                continue;
4905            }
4906            let MicroOp::ArrLoad { dst: _, idx: ia, ptr_slot: pa, len_slot: lena, byte: false, narrow32: false, checked: ca } =
4907                micro[ka]
4908            else {
4909                continue;
4910            };
4911            let MicroOp::ArrLoad { dst: _, idx: ib, ptr_slot: pb, len_slot: lenb, byte: false, narrow32: false, checked: cb } =
4912                micro[kb]
4913            else {
4914                continue;
4915            };
4916            // For Sub the order is fixed: lhs must be `a[ia]` (the load at ka).
4917            // Add/Mul commute. We always store the operand that came from ka as
4918            // `a`/`i` and from kb as `b`/`j` so `eval` reproduces lhs OP rhs.
4919            // (Sub: lhs == a[ia], rhs == b[ib] ⇒ a - b. Correct by construction.)
4920            // Both scratch loads must be single-use removable; dst already
4921            // checked distinct from both above.
4922            if !removable(lhs, &reads) || !removable(rhs, &reads) {
4923                continue;
4924            }
4925            // Interval soundness. The fused op sits at the binop and reads each
4926            // index + pointer/length from the frame at THAT point, so each
4927            // load's index/buffer must reach the binop unchanged FROM ITS OWN
4928            // LOAD onward (the index is naturally produced just before the load
4929            // — that write is fine). Concretely:
4930            //  - NO jump target in `[lo, m]` (the loads are removed and the fused
4931            //    op moves to the binop, so any entry there would skip a load);
4932            //  - `ia`,`pa`,`lena` not redefined in `(ka, m)`; `ib`,`pb`,`lenb`
4933            //    not redefined in `(kb, m)`;
4934            //  - NO memory write in `[lo, m)` (could mutate either buffer).
4935            let lo = ka.min(kb);
4936            let guard_a: [u16; 3] = [ia, pa, lena];
4937            let guard_b: [u16; 3] = [ib, pb, lenb];
4938            for p in lo..m {
4939                if p < n && is_target[p] {
4940                    continue 'anchor;
4941                }
4942                if p == ka || p == kb {
4943                    continue;
4944                }
4945                if writes_memory(&micro[p]) {
4946                    continue 'anchor;
4947                }
4948                let defs = micro_defs(&micro[p]);
4949                if p > ka && defs.iter().any(|d| guard_a.contains(d)) {
4950                    continue 'anchor;
4951                }
4952                if p > kb && defs.iter().any(|d| guard_b.contains(d)) {
4953                    continue 'anchor;
4954                }
4955            }
4956            // A jump target landing exactly on the binop would skip both loads.
4957            if m < n && is_target[m] {
4958                continue 'anchor;
4959            }
4960            hit = Some((
4961                ka,
4962                kb,
4963                m,
4964                MicroOp::ArrLoad2 {
4965                    dst,
4966                    i: ia,
4967                    j: ib,
4968                    ptr_a: pa,
4969                    len_a: lena,
4970                    ptr_b: pb,
4971                    len_b: lenb,
4972                    op: binop_kind,
4973                    checked: ca || cb,
4974                },
4975            ));
4976            break;
4977        }
4978        let Some((ka, kb, m, ld2)) = hit else { break };
4979        if std::env::var_os("LOGOS_FUSE_TRACE").is_some() {
4980            eprintln!("fuse-trace: ARRLD2 {ld2:?}");
4981        }
4982        // Place the fused op at the binop, then remove the two loads (higher
4983        // index first so the lower index stays valid).
4984        micro[m] = ld2;
4985        let (hi, low) = if ka > kb { (ka, kb) } else { (kb, ka) };
4986        micro.remove(hi);
4987        micro.remove(low);
4988        // Two ops vanished at `low` and `hi` (neither a jump target — the
4989        // interval check forbids targets after the earlier load). Shift later
4990        // targets down by however many removed ops precede them. The fused op
4991        // at `m` is itself shifted by the removals below it.
4992        for op in micro.iter_mut() {
4993            if let Some(t) = micro_target_mut(op) {
4994                let shift = (*t > low) as usize + (*t > hi) as usize;
4995                *t -= shift;
4996            }
4997        }
4998    }
4999}
5000
5001/// AFFINE-INDEX LOAD FUSION — folds the index-arithmetic chain that builds a
5002/// read-only array index INTO the load, so `<binop>(t = a OP b); Add(t2 = t +
5003/// Kc); ArrLoad(arr[t2])` (the `w - wi + 1`, `i*n + j + 1` shape) — or the
5004/// shorter `Add(t = a + Kc); ArrLoad(arr[t])` (`w + 1`) and the bare
5005/// `<binop>(t = a OP b); ArrLoad(arr[t])` (`i*n + j`) — collapse into one
5006/// [`MicroOp::ArrLoadAffine`] stencil that computes the 1-based index and loads
5007/// in a single dispatch. The JIT is per-op DISPATCH-bound, so removing the 1-2
5008/// index-arithmetic ops per access is a direct win on the indexed inner loops
5009/// (knapsack `prev[w - wi + 1]`, the heap-sift read region, graph adjacency
5010/// reads) that lose worst to V8.
5011///
5012/// SOUNDNESS — the fused stencil recomputes `idx = (frame[a] OP frame[b])
5013/// .wrapping_add(const_offset)` (or `frame[a] + const_offset` for the no-`b`
5014/// shape) with the kernel's EXACT i64 wrapping semantics, bit-identical to the
5015/// chain it replaces, PROVIDED the surviving operands reach the load unchanged:
5016/// - the load's index slot is SINGLE-USE scratch (read only by this load), so
5017///   erasing the index-producing op loses no observable value;
5018/// - the trailing-const `Add`'s product `t` (the two-slot binop's dst) is also
5019///   single-use scratch, so the inner binop is removable too;
5020/// - the constant offset is a LoadConst VALUE whose source slot is NOT redefined
5021///   between its load and the index `Add` (the LoadConst itself is KEPT — it is
5022///   usually reused — only its value is folded);
5023/// - between each removed op and the load NOTHING redefines a surviving operand
5024///   (`a`, `b`) and NO jump target lands in the removed span (an entry there
5025///   would skip a removed op).
5026/// The fused op SIDE-EXITS before any effect on out-of-bounds, identical to the
5027/// plain [`MicroOp::ArrLoad`] it replaces (replay-from-head recomputes the same
5028/// index). BYTE (Bool) buffers never fuse (the affine stencils read raw i64s).
5029/// Runs AFTER `fuse_array_ld2` so the matmul `a[..] * b[..]` two-buffer loads
5030/// keep fusing into `ArrLoad2` (which consumes the raw `ArrLoad`s) — this pass
5031/// only claims the plain read-only `ArrLoad`s that `ArrLoad2`/swap fusion left
5032/// behind. Caller restricts to NON-PRECISE regions (it renumbers micro indices).
5033/// `LOGOS_AFFINE=0` is the kill-switch.
5034fn fuse_index_affine(
5035    micro: &mut Vec<MicroOp>,
5036    named: &[bool],
5037    register_count: u16,
5038    pinned: &std::collections::HashSet<u16>,
5039) {
5040    use logicaffeine_forge::jit::AffOp;
5041    if micro.iter().any(|op| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })) {
5042        return;
5043    }
5044    // A scratch temp removable iff it is in-range, un-named, un-pinned, and
5045    // read EXACTLY once (its only reader is the op we erase or replace).
5046    let single_use_scratch = |slot: u16, reads: &std::collections::HashMap<u16, usize>| -> bool {
5047        (slot as usize) < register_count as usize
5048            && !named.get(slot as usize).copied().unwrap_or(true)
5049            && !pinned.contains(&slot)
5050            && reads.get(&slot).copied().unwrap_or(0) == 1
5051    };
5052    // The integer binop family that can build an affine index.
5053    let as_iop = |op: &MicroOp| -> Option<(IOp, u16, u16, u16)> {
5054        match *op {
5055            MicroOp::Add { dst, lhs, rhs } => Some((IOp::Add, dst, lhs, rhs)),
5056            MicroOp::Sub { dst, lhs, rhs } => Some((IOp::Sub, dst, lhs, rhs)),
5057            MicroOp::Mul { dst, lhs, rhs } => Some((IOp::Mul, dst, lhs, rhs)),
5058            _ => None,
5059        }
5060    };
5061    let iop_to_aff = |op: IOp| match op {
5062        IOp::Add => AffOp::Add,
5063        IOp::Sub => AffOp::Sub,
5064        IOp::Mul => AffOp::Mul,
5065    };
5066    loop {
5067        let n = micro.len();
5068        let mut is_target = vec![false; n];
5069        for op in micro.iter() {
5070            if let Some(t) = micro_target_const(op) {
5071                if t < n {
5072                    is_target[t] = true;
5073                }
5074            }
5075        }
5076        let mut reads: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
5077        for op in micro.iter() {
5078            for s in micro_read_slots(op) {
5079                *reads.entry(s).or_insert(0) += 1;
5080            }
5081        }
5082        // The reaching constant value of a slot at position `m`, if it is a
5083        // LoadConst not redefined in `(def, m)`. Returns (value, def_index).
5084        let reaching_const = |slot: u16, m: usize| -> Option<(i64, usize)> {
5085            let k = (0..m).rev().find(|&p| micro_defs(&micro[p]).contains(&slot))?;
5086            if let MicroOp::LoadConst { value, .. } = micro[k] {
5087                Some((value, k))
5088            } else {
5089                None
5090            }
5091        };
5092        // The reaching def index of a slot strictly before `m`.
5093        let reaching_def = |slot: u16, m: usize| -> Option<usize> {
5094            (0..m).rev().find(|&p| micro_defs(&micro[p]).contains(&slot))
5095        };
5096        // No jump target lands in [lo, m]; no surviving operand redefined in the
5097        // open spans after each removed op up to the load.
5098        let mut hit: Option<(Vec<usize>, usize, MicroOp)> = None;
5099        'anchor: for m in 0..n {
5100            let MicroOp::ArrLoad { dst, idx, ptr_slot, len_slot, byte: false, narrow32: false, checked } = micro[m]
5101            else {
5102                continue;
5103            };
5104            // The index must be single-use scratch defined by an integer binop.
5105            if !single_use_scratch(idx, &reads) {
5106                continue;
5107            }
5108            let Some(kidx) = reaching_def(idx, m) else { continue };
5109            let Some((idx_op, _idst, ilhs, irhs)) = as_iop(&micro[kidx]) else { continue };
5110
5111            // Decompose into (a, op, b, const_offset) + the chain ops to remove.
5112            // Three shapes, tried in order of specificity.
5113            let mut removed: Vec<usize> = Vec::new();
5114            let (a, aff_op, b, coff): (u16, AffOp, u16, i64);
5115
5116            // Does either operand of the index binop come from an inner two-slot
5117            // binop whose dst is single-use (the `t` of `t + Kc`)? If the OTHER
5118            // operand is a reaching const, this is the trailing-const shape.
5119            let inner_of = |slot: u16| -> Option<(usize, IOp, u16, u16)> {
5120                if !single_use_scratch(slot, &reads) {
5121                    return None;
5122                }
5123                let k = reaching_def(slot, kidx)?;
5124                let (op, d, l, r) = as_iop(&micro[k])?;
5125                Some((k, op, l, r)).filter(|_| d == slot)
5126            };
5127
5128            if idx_op == IOp::Add {
5129                // `Add(idx = LHS + RHS)`: classify each side.
5130                let lconst = reaching_const(ilhs, kidx);
5131                let rconst = reaching_const(irhs, kidx);
5132                let linner = inner_of(ilhs);
5133                let rinner = inner_of(irhs);
5134                if let (Some((kc, _)), Some((kin, iop, l, r))) = (rconst, linner) {
5135                    // `(l OP r) + Kc` — Kc on the right.
5136                    let _ = kc;
5137                    a = l; b = r; aff_op = iop_to_aff(iop); coff = rconst.unwrap().0;
5138                    removed.push(kin);
5139                    removed.push(kidx);
5140                } else if let (Some((kc, _)), Some((kin, iop, l, r))) = (lconst, rinner) {
5141                    // `Kc + (l OP r)` — Kc on the left.
5142                    let _ = kc;
5143                    a = l; b = r; aff_op = iop_to_aff(iop); coff = lconst.unwrap().0;
5144                    removed.push(kin);
5145                    removed.push(kidx);
5146                } else if let Some((_, _)) = rconst {
5147                    // `a + Kc` (the `w + 1` shape): single slot + const.
5148                    a = ilhs; b = ilhs; aff_op = AffOp::None; coff = rconst.unwrap().0;
5149                    removed.push(kidx);
5150                } else if let Some((_, _)) = lconst {
5151                    a = irhs; b = irhs; aff_op = AffOp::None; coff = lconst.unwrap().0;
5152                    removed.push(kidx);
5153                } else {
5154                    // `a + b` (no const): a bare two-slot add.
5155                    a = ilhs; b = irhs; aff_op = AffOp::Add; coff = 0;
5156                    removed.push(kidx);
5157                }
5158            } else {
5159                // `Sub`/`Mul(idx = a OP b)` with no trailing const (the const,
5160                // when present, is always the OUTER `+ Kc` Add handled above; a
5161                // bare Sub/Mul index folds with const_offset = 0).
5162                a = ilhs; b = irhs; aff_op = iop_to_aff(idx_op); coff = 0;
5163                removed.push(kidx);
5164            }
5165
5166            // Interval soundness over the union of removed ops and the load.
5167            let lo = *removed.iter().min().unwrap();
5168            let survivors: [u16; 2] = [a, b];
5169            for p in lo..m {
5170                if p < n && is_target[p] {
5171                    continue 'anchor;
5172                }
5173                if removed.contains(&p) {
5174                    continue;
5175                }
5176                let defs = micro_defs(&micro[p]);
5177                if defs.iter().any(|d| survivors.contains(d)) {
5178                    continue 'anchor;
5179                }
5180            }
5181            // A jump target landing exactly on the load would skip the removed
5182            // index ops.
5183            if m < n && is_target[m] {
5184                continue 'anchor;
5185            }
5186            removed.sort_unstable();
5187            hit = Some((
5188                removed,
5189                m,
5190                MicroOp::ArrLoadAffine {
5191                    dst,
5192                    a,
5193                    op: aff_op,
5194                    b,
5195                    const_offset: coff,
5196                    ptr_slot,
5197                    len_slot,
5198                    checked,
5199                },
5200            ));
5201            break;
5202        }
5203        let Some((removed, m, affine)) = hit else { break };
5204        if std::env::var_os("LOGOS_FUSE_TRACE").is_some() {
5205            eprintln!("fuse-trace: AFFINE {affine:?}");
5206        }
5207        // Place the fused op at the load, then remove the index-chain ops
5208        // (highest index first so lower indices stay valid). None of the removed
5209        // ops is a jump target (the interval check forbids targets in [lo, m]).
5210        micro[m] = affine;
5211        for &r in removed.iter().rev() {
5212            micro.remove(r);
5213        }
5214        for op in micro.iter_mut() {
5215            if let Some(t) = micro_target_mut(op) {
5216                let shift = removed.iter().filter(|&&r| r < *t).count();
5217                *t -= shift;
5218            }
5219        }
5220    }
5221}
5222
5223/// FLOAT MULTIPLY-ADD FUSION — collapses `MulF(t = a*b); AddF(d = t + c)` (the
5224/// product `t` single-use, AddF commutative so `t` may be either operand) into
5225/// one [`MicroOp::FmaF`] computing `(a*b) + c`. The JIT is per-op DISPATCH-bound,
5226/// and the float arithmetic chains that dominate the float cluster (nbody's
5227/// `dz*dz + sum`, dot products) are a stream of two-input AddF/MulF; merging a
5228/// product into its consuming add removes a stencil dispatch per occurrence.
5229///
5230/// SOUNDNESS: FmaF rounds the product and the add SEPARATELY (`(a*b)+c`, two
5231/// roundings — NOT a hardware single-rounding FMA), bit-identical to MulF+AddF.
5232/// It is mem-form (all operands read from the frame), so it never disturbs the
5233/// threaded XMM pins. To keep it a SPILL-FREE win on its INPUTS, fuse only when
5234/// the surviving inputs (`a`, `b`, the addend `c`) are FRAME-RESIDENT (not
5235/// pinned) — a pinned input would be spilled BEFORE the mem-form op, possibly
5236/// costing more than the saved dispatch. The RESULT `d` MAY be pinned (Lever
5237/// 1a): the pinned compiler's mem-form path reloads a pinned `d` from the frame
5238/// after the FmaF, exactly as the unfused register-form `AddF` would settle it,
5239/// so a pinned `d` adds no input spill (the nbody distance-sum's `dz*dz +
5240/// partial`, whose `d` is the float-pinned running sum). The product `t` must be
5241/// single-use, un-named, un-pinned scratch (its only reader, the AddF, is
5242/// removed). Caller restricts to NON-PRECISE regions (renumbering).
5243fn fuse_fma(
5244    micro: &mut Vec<MicroOp>,
5245    named: &[bool],
5246    register_count: u16,
5247    pinned: &std::collections::HashSet<u16>,
5248) {
5249    if micro.iter().any(|op| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })) {
5250        return;
5251    }
5252    let frame_resident = |slot: u16| -> bool { !pinned.contains(&slot) };
5253    loop {
5254        let n = micro.len();
5255        let mut is_target = vec![false; n];
5256        for op in micro.iter() {
5257            if let Some(t) = micro_target_const(op) {
5258                if t < n {
5259                    is_target[t] = true;
5260                }
5261            }
5262        }
5263        let mut reads: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
5264        for op in micro.iter() {
5265            for s in micro_read_slots(op) {
5266                *reads.entry(s).or_insert(0) += 1;
5267            }
5268        }
5269        let mut hit: Option<(usize, MicroOp)> = None;
5270        for k in 0..n.saturating_sub(1) {
5271            if is_target[k + 1] {
5272                continue;
5273            }
5274            let MicroOp::MulF { dst: t, lhs: a, rhs: b } = micro[k] else { continue };
5275            // The AddF consumes the product `t` (either operand) plus the addend.
5276            let (d, c) = match micro[k + 1] {
5277                MicroOp::AddF { dst, lhs, rhs } if lhs == t => (dst, rhs),
5278                MicroOp::AddF { dst, lhs, rhs } if rhs == t => (dst, lhs),
5279                _ => continue,
5280            };
5281            // `t` is single-use, un-named, un-pinned scratch we can erase.
5282            let t_dead = (t as usize) < register_count as usize
5283                && !named.get(t as usize).copied().unwrap_or(true)
5284                && !pinned.contains(&t)
5285                && reads.get(&t).copied().unwrap_or(0) == 1;
5286            // INPUT-spill-free (Lever 1a): require only the surviving INPUTS
5287            // (`a`, `b`, the addend `c`) frame-resident. `FmaF` is mem-form, so a
5288            // pinned RESULT `d` is spilled/reloaded by the pinned compiler's
5289            // mem-form path exactly as the unfused `AddF`'s register-form write
5290            // would settle it — fusing a pinned `d` adds no INPUT spill, removing
5291            // the `MulF` dispatch (the nbody distance-sum's `dz*dz + partial`,
5292            // whose `d` is the float-pinned sum). The addend `c` must NOT be
5293            // pinned: in the `s = s + a*b` accumulator shape `c == d == s`, a
5294            // pinned `c` would be spilled BEFORE the FmaF (a real extra spill),
5295            // costing more pieces than the register-form `AddF` it replaces.
5296            if !t_dead || !frame_resident(a) || !frame_resident(b) || !frame_resident(c) {
5297                continue;
5298            }
5299            hit = Some((k, MicroOp::FmaF { dst: d, a, b, c }));
5300            break;
5301        }
5302        let Some((k, fma)) = hit else { break };
5303        if std::env::var_os("LOGOS_FUSE_TRACE").is_some() {
5304            eprintln!("fuse-trace: FMAF {fma:?}");
5305        }
5306        micro[k] = fma;
5307        micro.remove(k + 1);
5308        for op in micro.iter_mut() {
5309            if let Some(t) = micro_target_mut(op) {
5310                if *t > k {
5311                    *t -= 1;
5312                }
5313            }
5314        }
5315    }
5316}
5317
5318/// CONDITIONAL-SWAP FUSION — collapses the sort inner-loop idiom
5319/// `ArrLoad(a, i1); [pure index ops]; ArrLoad(b, i2); Branch(cmp, a, b, skip);
5320/// ArrStore(i1, b); ArrStore(i2, a)` into one [`MicroOp::ArrCondSwap`] — the
5321/// biggest per-iteration dispatch sink for bubble/insertion/quick sort. Unlocked
5322/// by the precise-region-live-out keystone: the loaded values flow through
5323/// loop-local `Let a`/`Let b`, which are now dropped from write-back, so
5324/// copy_propagate erases their Moves and the loaded temps become single-use.
5325///
5326/// SOUNDNESS: `ArrCondSwap` re-reads `buf[i1]`/`buf[i2]` and swaps atomically
5327/// (both writes or neither) — bit-identical to the load/compare/cross-store it
5328/// replaces because NOTHING writes the array between the original loads and the
5329/// stores (only the pure index ops and the branch sit there). One pre-write
5330/// bounds check covers both indices. The loaded `a`, `b` must be single-use
5331/// scratch (read only by the branch + their cross-store — exactly two reads
5332/// each — un-named, un-pinned), the branch must target precisely past both
5333/// stores, the comparison must be an ordering (Gt/Lt/GtEq/LtEq), and up to two
5334/// PURE ops (a const + the `i+1` add) may sit between the loads. No fused op may
5335/// be a jump target. Non-precise regions only.
5336fn fuse_cond_swap(
5337    micro: &mut Vec<MicroOp>,
5338    named: &[bool],
5339    register_count: u16,
5340    pinned: &std::collections::HashSet<u16>,
5341) {
5342    if micro.iter().any(|op| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })) {
5343        return;
5344    }
5345    let dead_scratch = |slot: u16, reads: &std::collections::HashMap<u16, usize>| -> bool {
5346        (slot as usize) < register_count as usize
5347            && !named.get(slot as usize).copied().unwrap_or(true)
5348            && !pinned.contains(&slot)
5349            && reads.get(&slot).copied().unwrap_or(0) == 2 // the branch + its cross-store
5350    };
5351    loop {
5352        let n = micro.len();
5353        let mut is_target = vec![false; n];
5354        for op in micro.iter() {
5355            if let Some(t) = micro_target_const(op) {
5356                if t < n {
5357                    is_target[t] = true;
5358                }
5359            }
5360        }
5361        let mut reads: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
5362        for op in micro.iter() {
5363            for s in micro_read_slots(op) {
5364                *reads.entry(s).or_insert(0) += 1;
5365            }
5366        }
5367        let mut hit: Option<(usize, usize, MicroOp)> = None; // (k, gap, condswap)
5368        // The compare's SECOND index can be a multi-op affine computation
5369        // (`2*root+2` = LoadConst + Mul + Add, or `i*n + j + 1`) sitting between
5370        // the two loads. The `between_ok` purity guard below — not this window —
5371        // is what proves the gap ops cannot perturb the swap (pure, and they
5372        // redefine none of the first loaded value / first index / pointer /
5373        // length); the window only bounds the linear search, so it must be wide
5374        // enough to span a realistic index expression.
5375        const MAXGAP: usize = 5;
5376        'outer: for k in 0..n {
5377            let MicroOp::ArrLoad { dst: a, idx: i1, ptr_slot: p, len_slot: l, byte: false, narrow32: false, checked: c1 } =
5378                micro[k]
5379            else {
5380                continue;
5381            };
5382            for gap in 0..=MAXGAP {
5383                let lb = k + 1 + gap;
5384                if lb + 3 >= n {
5385                    break;
5386                }
5387                let between_ok = (k + 1..lb).all(|x| {
5388                    let pure = micro_pure_value_key(&micro[x]).is_some()
5389                        || matches!(micro[x], MicroOp::LoadConst { .. });
5390                    let defs = micro_defs(&micro[x]);
5391                    pure && !defs.contains(&a) && !defs.contains(&i1) && !defs.contains(&p)
5392                        && !defs.contains(&l)
5393                });
5394                if !between_ok {
5395                    continue;
5396                }
5397                let MicroOp::ArrLoad { dst: b, idx: i2, ptr_slot: p2, len_slot: l2, byte: false, narrow32: false, checked: c2 } =
5398                    micro[lb]
5399                else {
5400                    continue;
5401                };
5402                if p2 != p || l2 != l {
5403                    continue;
5404                }
5405                let MicroOp::Branch { cmp, lhs, rhs, target } = micro[lb + 1] else { continue };
5406                if !matches!(cmp, Cmp::Lt | Cmp::Gt | Cmp::LtEq | Cmp::GtEq) || lhs != a || rhs != b {
5407                    continue;
5408                }
5409                let MicroOp::ArrStore { src: s1, idx: si1, ptr_slot: sp1, len_slot: sl1, byte: false, narrow32: false, checked: c3 } =
5410                    micro[lb + 2]
5411                else {
5412                    continue;
5413                };
5414                let MicroOp::ArrStore { src: s2, idx: si2, ptr_slot: sp2, len_slot: sl2, byte: false, narrow32: false, checked: c4 } =
5415                    micro[lb + 3]
5416                else {
5417                    continue;
5418                };
5419                if si1 != i1 || si2 != i2 || s1 != b || s2 != a || sp1 != p || sp2 != p || sl1 != l || sl2 != l {
5420                    continue;
5421                }
5422                if target != lb + 4 {
5423                    continue;
5424                }
5425                if (k..=lb + 3).any(|x| is_target.get(x).copied().unwrap_or(false)) {
5426                    continue;
5427                }
5428                if !dead_scratch(a, &reads) || !dead_scratch(b, &reads) {
5429                    continue;
5430                }
5431                hit = Some((
5432                    k,
5433                    gap,
5434                    MicroOp::ArrCondSwap {
5435                        idx1: i1,
5436                        idx2: i2,
5437                        ptr_slot: p,
5438                        len_slot: l,
5439                        cmp,
5440                        checked: c1 || c2 || c3 || c4,
5441                    },
5442                ));
5443                break 'outer;
5444            }
5445        }
5446        let Some((k, gap, swap)) = hit else { break };
5447        if std::env::var_os("LOGOS_FUSE_TRACE").is_some() {
5448            eprintln!("fuse-trace: CONDSWAP {swap:?}");
5449        }
5450        // Span k ..= k+4+gap. Keep the between ops, turn the b-load slot into the
5451        // swap, drop a-load + branch + both stores (4 removed). First op after the
5452        // span (old k+gap+5) lands at k+gap+1.
5453        micro[k + gap + 1] = swap;
5454        micro.drain(k + gap + 2..=k + gap + 4);
5455        micro.remove(k);
5456        for op in micro.iter_mut() {
5457            if let Some(t) = micro_target_mut(op) {
5458                if *t >= k + gap + 5 {
5459                    *t -= 4;
5460                }
5461            }
5462        }
5463    }
5464}
5465
5466/// UNCONDITIONAL-SWAP FUSION — collapses the `Let tmp = arr[i]; arr[i] = arr[j];
5467/// arr[j] = tmp` exchange (quicksort/heap_sort/mergesort) — micro
5468/// `ArrLoad(a, i1); ArrLoad(b, i2); ArrStore(i1, b); ArrStore(i2, a)` — into one
5469/// [`MicroOp::ArrSwap`] (4 ops → 1). The conditionality (`if arr[j] <= pivot`)
5470/// is a SEPARATE branch before the loads and stays.
5471///
5472/// SOUNDNESS: `ArrSwap` re-reads `buf[i1]`/`buf[i2]` and swaps atomically —
5473/// bit-identical because nothing writes the array between the loads and stores.
5474/// `a`, `b` must be LOCALLY single-use (each read only by its cross-store before
5475/// its next redefinition — a slot may be REUSED later, so global read-counting
5476/// is too coarse here), un-named, un-pinned. No control flow between the four
5477/// ops; none a jump target. Non-precise regions only.
5478fn fuse_swap(
5479    micro: &mut Vec<MicroOp>,
5480    named: &[bool],
5481    register_count: u16,
5482    pinned: &std::collections::HashSet<u16>,
5483) {
5484    if micro.iter().any(|op| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })) {
5485        return;
5486    }
5487    let erasable = |slot: u16| -> bool {
5488        (slot as usize) < register_count as usize
5489            && !named.get(slot as usize).copied().unwrap_or(true)
5490            && !pinned.contains(&slot)
5491    };
5492    loop {
5493        let n = micro.len();
5494        let mut is_target = vec![false; n];
5495        for op in micro.iter() {
5496            if let Some(t) = micro_target_const(op) {
5497                if t < n {
5498                    is_target[t] = true;
5499                }
5500            }
5501        }
5502        // `slot` (defined at def_i) is read ONLY at `want` before its next redef.
5503        let local_single_use = |slot: u16, def_i: usize, want: usize| -> bool {
5504            let mut seen = false;
5505            for j in def_i + 1..micro.len() {
5506                if micro_read_slots(&micro[j]).contains(&slot) {
5507                    if j != want {
5508                        return false;
5509                    }
5510                    seen = true;
5511                }
5512                if micro_defs(&micro[j]).contains(&slot) {
5513                    break; // next redefinition — a separate live range
5514                }
5515            }
5516            seen
5517        };
5518        let mut hit: Option<(usize, MicroOp)> = None;
5519        for k in 0..n.saturating_sub(3) {
5520            let MicroOp::ArrLoad { dst: a, idx: i1, ptr_slot: p, len_slot: l, byte: false, narrow32: false, checked: c1 } =
5521                micro[k]
5522            else {
5523                continue;
5524            };
5525            let MicroOp::ArrLoad { dst: b, idx: i2, ptr_slot: p2, len_slot: l2, byte: false, narrow32: false, checked: c2 } =
5526                micro[k + 1]
5527            else {
5528                continue;
5529            };
5530            if p2 != p || l2 != l || a == b {
5531                continue;
5532            }
5533            let MicroOp::ArrStore { src: s1, idx: si1, ptr_slot: sp1, len_slot: sl1, byte: false, narrow32: false, checked: c3 } =
5534                micro[k + 2]
5535            else {
5536                continue;
5537            };
5538            let MicroOp::ArrStore { src: s2, idx: si2, ptr_slot: sp2, len_slot: sl2, byte: false, narrow32: false, checked: c4 } =
5539                micro[k + 3]
5540            else {
5541                continue;
5542            };
5543            // Cross-store swap to the SAME array+indices: buf[i1]=b, buf[i2]=a.
5544            if si1 != i1 || si2 != i2 || s1 != b || s2 != a || sp1 != p || sp2 != p || sl1 != l || sl2 != l {
5545                continue;
5546            }
5547            if is_target[k + 1] || is_target[k + 2] || is_target[k + 3] {
5548                continue;
5549            }
5550            // `a` consumed only by the store at k+3, `b` only by the store at k+2.
5551            if !erasable(a) || !erasable(b) || !local_single_use(a, k, k + 3) || !local_single_use(b, k + 1, k + 2) {
5552                continue;
5553            }
5554            hit = Some((
5555                k,
5556                MicroOp::ArrSwap {
5557                    idx1: i1,
5558                    idx2: i2,
5559                    ptr_slot: p,
5560                    len_slot: l,
5561                    checked: c1 || c2 || c3 || c4,
5562                },
5563            ));
5564            break;
5565        }
5566        let Some((k, swap)) = hit else { break };
5567        if std::env::var_os("LOGOS_FUSE_TRACE").is_some() {
5568            eprintln!("fuse-trace: ARRSWAP {swap:?}");
5569        }
5570        micro[k] = swap;
5571        micro.drain(k + 1..=k + 3); // remove the 2nd load + both stores
5572        for op in micro.iter_mut() {
5573            if let Some(t) = micro_target_mut(op) {
5574                if *t >= k + 4 {
5575                    *t -= 3;
5576                }
5577            }
5578        }
5579    }
5580}
5581
5582
5583/// A canonical value key for a PURE, side-effect-free, deterministic micro-op —
5584/// the basis for local value numbering (CSE) in [`copy_propagate`]. Returns
5585/// `((tag, a, b), dst)`: a unique op tag, the two operand slots (commutative ops
5586/// sort them so `v + 1` and `1 + v` share a key), and the result slot. Returns
5587/// None for anything that loads memory, calls, side-exits (Div/Mod, whose
5588/// duplicate could diverge on a zero divisor), branches, or carries hidden
5589/// state — those must never be deduplicated. Every operand position is a real
5590/// frame slot (never an immediate), so the invalidation in `copy_propagate` can
5591/// drop a key the moment any slot it names is redefined.
5592fn micro_pure_value_key(op: &MicroOp) -> Option<((u8, u16, u16), u16)> {
5593    use MicroOp::*;
5594    let comm = |t: u8, a: u16, b: u16, d: u16| Some(((t, a.min(b), a.max(b)), d));
5595    let ord = |t: u8, a: u16, b: u16, d: u16| Some(((t, a, b), d));
5596    match *op {
5597        Add { dst, lhs, rhs } => comm(1, lhs, rhs, dst),
5598        Mul { dst, lhs, rhs } => comm(2, lhs, rhs, dst),
5599        BitAnd { dst, lhs, rhs } => comm(3, lhs, rhs, dst),
5600        BitOr { dst, lhs, rhs } => comm(4, lhs, rhs, dst),
5601        BitXor { dst, lhs, rhs } => comm(5, lhs, rhs, dst),
5602        Eq { dst, lhs, rhs } => comm(6, lhs, rhs, dst),
5603        Neq { dst, lhs, rhs } => comm(7, lhs, rhs, dst),
5604        Sub { dst, lhs, rhs } => ord(8, lhs, rhs, dst),
5605        Shl { dst, lhs, rhs } => ord(9, lhs, rhs, dst),
5606        Shr { dst, lhs, rhs } => ord(10, lhs, rhs, dst),
5607        Lt { dst, lhs, rhs } => ord(11, lhs, rhs, dst),
5608        Gt { dst, lhs, rhs } => ord(12, lhs, rhs, dst),
5609        LtEq { dst, lhs, rhs } => ord(13, lhs, rhs, dst),
5610        GtEq { dst, lhs, rhs } => ord(14, lhs, rhs, dst),
5611        NotInt { dst, src } => ord(15, src, src, dst),
5612        NotBool { dst, src } => ord(16, src, src, dst),
5613        _ => None,
5614    }
5615}
5616
5617/// The branch/jump TARGET of a micro-op (a micro index), if any — by value.
5618fn micro_target_const(op: &MicroOp) -> Option<usize> {
5619    use MicroOp::*;
5620    match *op {
5621        Jump { target } | Branch { target, .. } | BranchF { target, .. }
5622        | JumpIfFalse { target, .. } | JumpIfTrue { target, .. } => Some(target),
5623        _ => None,
5624    }
5625}
5626
5627/// Returns TWO disjoint pin sets: `(gp_pins, float_pins)`. A slot that ever
5628/// holds an f64 VALUE (used by any float-arithmetic / float-compare op) is a
5629/// FLOAT slot — it can only ride an XMM register (f0..f3), so it goes into
5630/// `float_pins` and is NEVER put in the GP set, which threads i64s through
5631/// r0..r3. The two budgets are independent (the threaded ABI passes both
5632/// r0..r3 and f0..f3), so each is ranked and capped at four on its own.
5633fn select_pins(ops: &[MicroOp], register_count: u16, region: bool) -> (Vec<u16>, Vec<u16>) {
5634    const BASE: i64 = 16;
5635    const CAP: u32 = 4;
5636    let depth = if region { loop_depths(ops) } else { Vec::new() };
5637    let weight = |i: usize| -> i64 {
5638        if region {
5639            BASE.pow(depth[i].min(CAP))
5640        } else {
5641            1
5642        }
5643    };
5644    // Every REAL call saves/restores each live pinned register (SysV makes
5645    // them caller-saved): call-heavy bodies (fib) lose to pinning while
5646    // loop-heavy bodies (gcd, nqueens' solver) win — charge each slot a
5647    // per-call penalty, frequency-weighted in regions (a call inside an
5648    // inner loop is far costlier than one at top level).
5649    let call_penalty: i64 = ops
5650        .iter()
5651        .enumerate()
5652        .filter(|(_, op)| matches!(op, MicroOp::Call { .. } | MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. }))
5653        .map(|(i, _)| 3 * weight(i))
5654        .sum();
5655    // FLOAT-slot set: every slot that ever holds an f64 VALUE. The float
5656    // arithmetic ops (AddF/SubF/MulF/DivF) carry floats in all three slots;
5657    // the float comparisons/branch carry floats only in the OPERANDS (their
5658    // dst is the Int 0/1 result); IntToFloat produces a float in dst from an
5659    // Int src; SqrtF and the fused float load produce/consume floats. A float
5660    // slot can only live in an XMM register, NEVER a GP one (the GP stencils
5661    // store raw i64 bits and the float arith reads `fN` directly), so EVERY
5662    // float slot is excluded from the GP budget below.
5663    //
5664    // A float slot is XMM-PINNABLE only when every op that touches it as a
5665    // float is XMM-aware: the V_FBINOP arith threads it through `fN`, and the
5666    // mem-form float ops (DivF, the float compares/branch, IntToFloat, SqrtF,
5667    // the fused load) spill/reload it around their frame-form stencils. The
5668    // register-form ops that resolve operands through the GP location only —
5669    // `LoadConst`, `Move`, and the function `Return` value — have NO XMM form,
5670    // so a float slot they touch must stay FRAME-resident (blocked from the
5671    // float-pin budget); the float arith then reads it from the frame (floc 0)
5672    // correctly. (`main_float_loop`: the `0.25` constant slot is a LoadConst
5673    // dst AND an AddF operand — float-valued but pin-blocked.)
5674    let mut float_slots: std::collections::HashSet<u16> = std::collections::HashSet::new();
5675    let mut float_blocked: std::collections::HashSet<u16> = std::collections::HashSet::new();
5676    let mut mark_float = |slot: u16, set: &mut std::collections::HashSet<u16>| {
5677        if slot < register_count {
5678            set.insert(slot);
5679        }
5680    };
5681    for op in ops {
5682        match *op {
5683            MicroOp::AddF { dst, lhs, rhs }
5684            | MicroOp::SubF { dst, lhs, rhs }
5685            | MicroOp::MulF { dst, lhs, rhs }
5686            | MicroOp::DivF { dst, lhs, rhs } => {
5687                for s in [dst, lhs, rhs] {
5688                    mark_float(s, &mut float_slots);
5689                }
5690            }
5691            MicroOp::LtF { lhs, rhs, .. }
5692            | MicroOp::GtF { lhs, rhs, .. }
5693            | MicroOp::LtEqF { lhs, rhs, .. }
5694            | MicroOp::GtEqF { lhs, rhs, .. }
5695            | MicroOp::EqF { lhs, rhs, .. }
5696            | MicroOp::NeqF { lhs, rhs, .. }
5697            | MicroOp::BranchF { lhs, rhs, .. } => {
5698                for s in [lhs, rhs] {
5699                    mark_float(s, &mut float_slots);
5700                }
5701            }
5702            MicroOp::IntToFloat { dst, .. } => mark_float(dst, &mut float_slots),
5703            MicroOp::SqrtF { dst, src } => {
5704                for s in [dst, src] {
5705                    mark_float(s, &mut float_slots);
5706                }
5707            }
5708            MicroOp::ArrLoad2F { dst, .. } => mark_float(dst, &mut float_slots),
5709            // A float value loaded from / stored to an array stays FRAME-RESIDENT
5710            // (block it from the XMM pin budget): the array access itself is
5711            // mem-form, so a pinned value would round-trip the frame anyway, and
5712            // the float-pin + mem-form-array interaction is not yet correct
5713            // (spectral_norm SIGSEGV). The array load/store still works mem-form.
5714            MicroOp::ArrLoad { dst, .. } => mark_float(dst, &mut float_blocked),
5715            MicroOp::ArrStore { src, .. } => mark_float(src, &mut float_blocked),
5716            // Register-form ops with no XMM lowering: a float slot they touch
5717            // is pin-blocked (it must read/write through the frame). `Move` is
5718            // NOT here — it threads XMM pins through V_FMOV (f→f, or frame↔f when
5719            // one end is unpinned), so a loop-carried float reassigned via `Set
5720            // acc to next` keeps its register (mandelbrot's `Set zr to zr2`).
5721            MicroOp::LoadConst { dst, .. } => mark_float(dst, &mut float_blocked),
5722            MicroOp::Return { src } => mark_float(src, &mut float_blocked),
5723            _ => {}
5724        }
5725    }
5726    // INTEGER-role slots: any slot read/written as an i64 by a non-float op —
5727    // int arithmetic, a branch/jump condition, an array index/pointer/length/
5728    // handle, an `IntToFloat` SOURCE, a map key/handle. A slot that is BOTH
5729    // float-valued (above) AND integer-role is a TYPE-REUSED scratch cell, and
5730    // must stay FRAME-resident (never float-pinned): the int stencil writes raw
5731    // i64 bits to its frame cell while a float pin would keep an f64 in an XMM
5732    // register, and the mem-form spill of the float pin clobbers the integer
5733    // the int op wrote. (spectral_norm SIGSEGV: a `MulF` result slot was reused
5734    // as an `ArrStore` index; the float-pin spill wrote f64 bits over the
5735    // integer index, so the store indexed through a garbage pointer.) Marking
5736    // the integer role into `float_blocked` blocks the pin; a pure-integer slot
5737    // is never in `float_slots`, so this is a no-op for it — only genuinely
5738    // mixed slots are forced frame-resident.
5739    for op in ops {
5740        match *op {
5741            MicroOp::Add { dst, lhs, rhs }
5742            | MicroOp::Sub { dst, lhs, rhs }
5743            | MicroOp::Mul { dst, lhs, rhs }
5744            | MicroOp::BitAnd { dst, lhs, rhs }
5745            | MicroOp::BitOr { dst, lhs, rhs }
5746            | MicroOp::BitXor { dst, lhs, rhs }
5747            | MicroOp::Shl { dst, lhs, rhs }
5748            | MicroOp::Shr { dst, lhs, rhs }
5749            | MicroOp::Lt { dst, lhs, rhs }
5750            | MicroOp::Gt { dst, lhs, rhs }
5751            | MicroOp::LtEq { dst, lhs, rhs }
5752            | MicroOp::GtEq { dst, lhs, rhs }
5753            | MicroOp::Eq { dst, lhs, rhs }
5754            | MicroOp::Neq { dst, lhs, rhs }
5755            | MicroOp::Div { dst, lhs, rhs }
5756            | MicroOp::Mod { dst, lhs, rhs } => {
5757                for s in [dst, lhs, rhs] {
5758                    mark_float(s, &mut float_blocked);
5759                }
5760            }
5761            MicroOp::DivPow2 { dst, lhs, .. } | MicroOp::MagicDivU { dst, lhs, .. } => {
5762                for s in [dst, lhs] {
5763                    mark_float(s, &mut float_blocked);
5764                }
5765            }
5766            MicroOp::NotInt { dst, src } | MicroOp::NotBool { dst, src } => {
5767                for s in [dst, src] {
5768                    mark_float(s, &mut float_blocked);
5769                }
5770            }
5771            MicroOp::Branch { lhs, rhs, .. } => {
5772                for s in [lhs, rhs] {
5773                    mark_float(s, &mut float_blocked);
5774                }
5775            }
5776            MicroOp::JumpIfFalse { cond, .. } | MicroOp::JumpIfTrue { cond, .. } => {
5777                mark_float(cond, &mut float_blocked);
5778            }
5779            MicroOp::IntToFloat { src, .. } => mark_float(src, &mut float_blocked),
5780            MicroOp::ArrLoad { idx, ptr_slot, len_slot, .. } => {
5781                for s in [idx, ptr_slot, len_slot] {
5782                    mark_float(s, &mut float_blocked);
5783                }
5784            }
5785            MicroOp::ArrStore { idx, ptr_slot, len_slot, .. } => {
5786                for s in [idx, ptr_slot, len_slot] {
5787                    mark_float(s, &mut float_blocked);
5788                }
5789            }
5790            MicroOp::ArrRMW { idx, operand, ptr_slot, len_slot, .. } => {
5791                for s in [idx, operand, ptr_slot, len_slot] {
5792                    mark_float(s, &mut float_blocked);
5793                }
5794            }
5795            MicroOp::ArrCondSwap { idx1, idx2, ptr_slot, len_slot, .. } => {
5796                for s in [idx1, idx2, ptr_slot, len_slot] {
5797                    mark_float(s, &mut float_blocked);
5798                }
5799            }
5800            MicroOp::ArrSwap { idx1, idx2, ptr_slot, len_slot, .. } => {
5801                for s in [idx1, idx2, ptr_slot, len_slot] {
5802                    mark_float(s, &mut float_blocked);
5803                }
5804            }
5805            MicroOp::ArrLoad2F { i: ix, j: jx, ptr_slot, len_slot, .. } => {
5806                for s in [ix, jx, ptr_slot, len_slot] {
5807                    mark_float(s, &mut float_blocked);
5808                }
5809            }
5810            // The fused int two-load result is an i64; every operand and slot
5811            // is integer-role (block them from the float-pin budget, exactly
5812            // like the mem-form array ops).
5813            MicroOp::ArrLoad2 { dst, i: ix, j: jx, ptr_a, len_a, ptr_b, len_b, .. } => {
5814                for s in [dst, ix, jx, ptr_a, len_a, ptr_b, len_b] {
5815                    mark_float(s, &mut float_blocked);
5816                }
5817            }
5818            MicroOp::ArrPush { vec_slot, ptr_slot, len_slot, .. } => {
5819                for s in [vec_slot, ptr_slot, len_slot] {
5820                    mark_float(s, &mut float_blocked);
5821                }
5822            }
5823            MicroOp::NewList { vec_slot, ptr_slot, len_slot, .. } => {
5824                for s in [vec_slot, ptr_slot, len_slot] {
5825                    mark_float(s, &mut float_blocked);
5826                }
5827            }
5828            MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
5829                for s in [handle_slot, vec_slot, ptr_slot, len_slot] {
5830                    mark_float(s, &mut float_blocked);
5831                }
5832            }
5833            MicroOp::MapGet { key, map_slot, .. } => {
5834                for s in [key, map_slot] {
5835                    mark_float(s, &mut float_blocked);
5836                }
5837            }
5838            MicroOp::MapSet { key, map_slot, .. } => {
5839                for s in [key, map_slot] {
5840                    mark_float(s, &mut float_blocked);
5841                }
5842            }
5843            MicroOp::MapHas { dst, key, map_slot, .. } => {
5844                for s in [dst, key, map_slot] {
5845                    mark_float(s, &mut float_blocked);
5846                }
5847            }
5848            _ => {}
5849        }
5850    }
5851    let mut profit: std::collections::HashMap<u16, i64> = std::collections::HashMap::new();
5852    let bump = |slot: u16, delta: i64, profit: &mut std::collections::HashMap<u16, i64>| {
5853        if slot < register_count {
5854            *profit.entry(slot).or_insert(0) += delta;
5855        }
5856    };
5857    for (i, op) in ops.iter().enumerate() {
5858        // Variant uses save a memory access (+); mem-form uses force a
5859        // spill (−). Each scaled by execution frequency (loop depth).
5860        let v = 2 * weight(i);
5861        let mv = 1 * weight(i);
5862        let m = -4 * weight(i);
5863        match *op {
5864            MicroOp::Add { dst, lhs, rhs }
5865            | MicroOp::Sub { dst, lhs, rhs }
5866            | MicroOp::Mul { dst, lhs, rhs }
5867            | MicroOp::BitAnd { dst, lhs, rhs }
5868            | MicroOp::BitOr { dst, lhs, rhs }
5869            | MicroOp::BitXor { dst, lhs, rhs }
5870            | MicroOp::Shl { dst, lhs, rhs }
5871            | MicroOp::Shr { dst, lhs, rhs }
5872            | MicroOp::Lt { dst, lhs, rhs }
5873            | MicroOp::Gt { dst, lhs, rhs }
5874            | MicroOp::LtEq { dst, lhs, rhs }
5875            | MicroOp::GtEq { dst, lhs, rhs }
5876            | MicroOp::Eq { dst, lhs, rhs }
5877            | MicroOp::Neq { dst, lhs, rhs } => {
5878                for s in [dst, lhs, rhs] {
5879                    bump(s, v, &mut profit);
5880                }
5881            }
5882            MicroOp::Branch { lhs, rhs, .. } => {
5883                for s in [lhs, rhs] {
5884                    bump(s, v, &mut profit);
5885                }
5886            }
5887            MicroOp::Move { dst, src } => {
5888                for s in [dst, src] {
5889                    bump(s, mv, &mut profit);
5890                }
5891            }
5892            MicroOp::LoadConst { dst, .. } => bump(dst, mv, &mut profit),
5893            MicroOp::Return { src } => bump(src, mv, &mut profit),
5894            MicroOp::JumpIfFalse { cond, .. } | MicroOp::JumpIfTrue { cond, .. } => {
5895                bump(cond, -3 * weight(i), &mut profit);
5896            }
5897            MicroOp::Div { dst, lhs, rhs } | MicroOp::Mod { dst, lhs, rhs } => {
5898                for s in [dst, lhs, rhs] {
5899                    bump(s, m, &mut profit);
5900                }
5901            }
5902            // DivPow2 is a single mem-form stencil (reads lhs, writes dst).
5903            // MagicDivU bails the pinned/function tier (no stencil), but its
5904            // operands still profit from residency on the regalloc path.
5905            MicroOp::DivPow2 { dst, lhs, .. } | MicroOp::MagicDivU { dst, lhs, .. } => {
5906                for s in [dst, lhs] {
5907                    bump(s, m, &mut profit);
5908                }
5909            }
5910            // Float add/sub/mul are register-threaded now (V_FBINOP) → variant.
5911            MicroOp::AddF { dst, lhs, rhs }
5912            | MicroOp::SubF { dst, lhs, rhs }
5913            | MicroOp::MulF { dst, lhs, rhs } => {
5914                for s in [dst, lhs, rhs] {
5915                    bump(s, v, &mut profit);
5916                }
5917            }
5918            // DivF is register-threaded now (V_DIVF) → variant. The float
5919            // comparisons have no XMM variant yet → mem-form.
5920            MicroOp::DivF { dst, lhs, rhs } => {
5921                for s in [dst, lhs, rhs] {
5922                    bump(s, v, &mut profit);
5923                }
5924            }
5925            MicroOp::LtF { dst, lhs, rhs }
5926            | MicroOp::GtF { dst, lhs, rhs }
5927            | MicroOp::LtEqF { dst, lhs, rhs }
5928            | MicroOp::GtEqF { dst, lhs, rhs }
5929            | MicroOp::EqF { dst, lhs, rhs }
5930            | MicroOp::NeqF { dst, lhs, rhs } => {
5931                for s in [dst, lhs, rhs] {
5932                    bump(s, m, &mut profit);
5933                }
5934            }
5935            // BranchF is register-threaded now (V_BRANCHF) → variant.
5936            MicroOp::BranchF { lhs, rhs, .. } => {
5937                for s in [lhs, rhs] {
5938                    bump(s, v, &mut profit);
5939                }
5940            }
5941            // SqrtF is register-threaded now (V_SQRTF) → variant.
5942            MicroOp::SqrtF { dst, src } => {
5943                for s in [dst, src] {
5944                    bump(s, v, &mut profit);
5945                }
5946            }
5947            // IntToFloat is register-threaded now (V_I2F) → variant.
5948            MicroOp::IntToFloat { dst, src } => {
5949                for s in [dst, src] {
5950                    bump(s, v, &mut profit);
5951                }
5952            }
5953            MicroOp::NotInt { dst, src } | MicroOp::NotBool { dst, src } => {
5954                for s in [dst, src] {
5955                    bump(s, m, &mut profit);
5956                }
5957            }
5958            MicroOp::ArrLoad { dst, idx, ptr_slot, len_slot, .. } => {
5959                for s in [dst, idx, ptr_slot, len_slot] {
5960                    bump(s, m, &mut profit);
5961                }
5962            }
5963            MicroOp::ArrLoadAffine { dst, a, b, ptr_slot, len_slot, .. } => {
5964                for s in [dst, a, b, ptr_slot, len_slot] {
5965                    bump(s, m, &mut profit);
5966                }
5967            }
5968            MicroOp::ArrLoad2F { dst, i: ix, j: jx, ptr_slot, len_slot, .. } => {
5969                for s in [dst, ix, jx, ptr_slot, len_slot] {
5970                    bump(s, m, &mut profit);
5971                }
5972            }
5973            MicroOp::ArrLoad2 { dst, i: ix, j: jx, ptr_a, len_a, ptr_b, len_b, .. } => {
5974                for s in [dst, ix, jx, ptr_a, len_a, ptr_b, len_b] {
5975                    bump(s, m, &mut profit);
5976                }
5977            }
5978            MicroOp::ArrStore { src, idx, ptr_slot, len_slot, .. } => {
5979                for s in [src, idx, ptr_slot, len_slot] {
5980                    bump(s, m, &mut profit);
5981                }
5982            }
5983            MicroOp::ArrRMW { idx, operand, ptr_slot, len_slot, .. } => {
5984                for s in [idx, operand, ptr_slot, len_slot] {
5985                    bump(s, m, &mut profit);
5986                }
5987            }
5988            MicroOp::ArrCondSwap { idx1, idx2, ptr_slot, len_slot, .. } => {
5989                for s in [idx1, idx2, ptr_slot, len_slot] {
5990                    bump(s, m, &mut profit);
5991                }
5992            }
5993            MicroOp::ArrSwap { idx1, idx2, ptr_slot, len_slot, .. } => {
5994                for s in [idx1, idx2, ptr_slot, len_slot] {
5995                    bump(s, m, &mut profit);
5996                }
5997            }
5998            // FmaF is introduced AFTER pin selection, so this arm never executes
5999            // at runtime — it exists only to keep the match exhaustive. Treat it
6000            // as a mem-form op for completeness.
6001            MicroOp::FmaF { dst, a, b, c } => {
6002                for s in [dst, a, b, c] {
6003                    bump(s, m, &mut profit);
6004                }
6005            }
6006            MicroOp::ArrPush { src, vec_slot, ptr_slot, len_slot, .. } => {
6007                for s in [src, vec_slot, ptr_slot, len_slot] {
6008                    bump(s, m, &mut profit);
6009                }
6010            }
6011            MicroOp::ListClear { vec_slot, ptr_slot, len_slot, .. } => {
6012                for s in [vec_slot, ptr_slot, len_slot] {
6013                    bump(s, m, &mut profit);
6014                }
6015            }
6016            // The str-append's byte source is read across a SysV call (mem-form);
6017            // the handle slot is forced frame-resident, so count both as mem-form.
6018            MicroOp::StrAppend { text_handle_slot, src, .. } => {
6019                bump(text_handle_slot, m, &mut profit);
6020                if let logicaffeine_forge::jit::StrSrc::Byte(s) = src {
6021                    bump(s, m, &mut profit);
6022                }
6023            }
6024            MicroOp::Call { dst, .. } | MicroOp::CallSelf { dst, .. } => {
6025                bump(dst, m, &mut profit)
6026            }
6027            MicroOp::CallSelfCopy { dst, src_start, arg_count, .. } => {
6028                bump(dst, m, &mut profit);
6029                for s in src_start..src_start + arg_count {
6030                    bump(s, m, &mut profit);
6031                }
6032            }
6033            MicroOp::MapGet { dst, key, map_slot, .. } => {
6034                for s in [dst, key, map_slot] {
6035                    bump(s, m, &mut profit);
6036                }
6037            }
6038            MicroOp::MapSet { src, key, map_slot, .. } => {
6039                for s in [src, key, map_slot] {
6040                    bump(s, m, &mut profit);
6041                }
6042            }
6043            MicroOp::MapHas { dst, key, map_slot, .. } => {
6044                for s in [dst, key, map_slot] {
6045                    bump(s, m, &mut profit);
6046                }
6047            }
6048            MicroOp::NewList { vec_slot, ptr_slot, len_slot, .. } => {
6049                for s in [vec_slot, ptr_slot, len_slot] {
6050                    bump(s, m, &mut profit);
6051                }
6052            }
6053            MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
6054                for s in [handle_slot, vec_slot, ptr_slot, len_slot] {
6055                    bump(s, m, &mut profit);
6056                }
6057            }
6058            // A MemMem region runs UNPINNED (it reads/writes every slot through
6059            // the frame in the helper); count every touched slot as mem-form so
6060            // none would be selected even if the no-pin gate were ever lifted.
6061            MicroOp::MemMem {
6062                h_ptr_slot,
6063                h_len_slot,
6064                n_ptr_slot,
6065                n_len_slot,
6066                needle_len_slot,
6067                i_slot,
6068                count_slot,
6069                ..
6070            } => {
6071                for s in [
6072                    h_ptr_slot,
6073                    h_len_slot,
6074                    n_ptr_slot,
6075                    n_len_slot,
6076                    needle_len_slot,
6077                    i_slot,
6078                    count_slot,
6079                ] {
6080                    bump(s, m, &mut profit);
6081                }
6082            }
6083            MicroOp::Jump { .. } => {}
6084        }
6085    }
6086    // Threshold: flat model wants any net win (>2, historical); the region
6087    // model wants at least one depth-1 net benefit (>BASE) so cold depth-0
6088    // slots never pin.
6089    let threshold = if region { BASE } else { 2 };
6090    let ranked: Vec<(u16, i64)> = {
6091        let mut r: Vec<(u16, i64)> = profit
6092            .into_iter()
6093            .map(|(s, p)| (s, p - call_penalty))
6094            .filter(|&(_, p)| p > threshold)
6095            .collect();
6096        r.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
6097        r
6098    };
6099    // Split by budget: a float slot can only ride an XMM register, an int/bool
6100    // slot only a GP register; each budget independently takes its four best. A
6101    // float slot that is pin-blocked (touched by a register-form op with no XMM
6102    // lowering) gets NO pin — it stays frame-resident — but is still kept out
6103    // of the GP budget (its value is f64 bits the GP stencils would mishandle).
6104    let mut gp_pins: Vec<u16> = Vec::new();
6105    let mut float_pins: Vec<u16> = Vec::new();
6106    // A float pin lives in a caller-saved XMM register, threaded through the
6107    // stencil chain by the register-form float ops (AddF/SubF/MulF). It is only
6108    // sound in a REGION when EVERY op on its live range provably leaves the
6109    // other live XMM pins alone behind the chain's back — otherwise a pin is
6110    // clobbered and memory corrupts (spectral_norm SIGSEGV'd at tier-up: its
6111    // inner loop kept the float product in a pinned XMM across both the `aVal`
6112    // CALL, when un-inlined, and the inlined `1.0/denom` DivF + IntToFloat).
6113    // Rather than blocklist the clobbering ops — which silently misses any new
6114    // one — WHITELIST the ops proven NOT to disturb a live float pin:
6115    // register-form float arith (AddF/SubF/MulF and the fused float load), the
6116    // array load/store stencils, the mem-form float stencils that spill/reload
6117    // only the slot THEY touch (DivF/SqrtF/IntToFloat/BranchF and — Lever 2a —
6118    // the float ORDERING compares LtF/LtEqF/GtF/GtEqF), and the pure integer/
6119    // pointer/control ops (no XMM at all). ANYTHING else blocks: a call or
6120    // helper-call (Call/CallSelf/Map*/NewList/ListTriple/ArrPush — all wipe the
6121    // caller-saved xmm0–15). Float EQUALITY (EqF/NeqF) is NOT whitelisted here —
6122    // it stays on its (epsilon-correct) mem-form path but a region carrying it
6123    // still drops its float pins, a pre-existing limitation outside Lever 2a's
6124    // ordering-only scope. A MicroOp variant added later is unmatched and so
6125    // blocks by default: sound until it is classified. GP pins and the function
6126    // tier are unaffected.
6127    let xmm_pin_safe = |op: &MicroOp| {
6128        matches!(
6129            op,
6130            MicroOp::LoadConst { .. }
6131                | MicroOp::Move { .. }
6132                | MicroOp::Add { .. }
6133                | MicroOp::Sub { .. }
6134                | MicroOp::Mul { .. }
6135                | MicroOp::Div { .. }
6136                | MicroOp::DivPow2 { .. }
6137                | MicroOp::MagicDivU { .. }
6138                | MicroOp::Mod { .. }
6139                | MicroOp::Lt { .. }
6140                | MicroOp::Gt { .. }
6141                | MicroOp::Eq { .. }
6142                | MicroOp::LtEq { .. }
6143                | MicroOp::GtEq { .. }
6144                | MicroOp::Neq { .. }
6145                | MicroOp::Branch { .. }
6146                | MicroOp::BitAnd { .. }
6147                | MicroOp::BitOr { .. }
6148                | MicroOp::BitXor { .. }
6149                | MicroOp::Shl { .. }
6150                | MicroOp::Shr { .. }
6151                | MicroOp::NotInt { .. }
6152                | MicroOp::NotBool { .. }
6153                | MicroOp::AddF { .. }
6154                | MicroOp::SubF { .. }
6155                | MicroOp::MulF { .. }
6156                | MicroOp::DivF { .. }
6157                | MicroOp::SqrtF { .. }
6158                | MicroOp::IntToFloat { .. }
6159                | MicroOp::BranchF { .. }
6160                | MicroOp::LtF { .. }
6161                | MicroOp::LtEqF { .. }
6162                | MicroOp::GtF { .. }
6163                | MicroOp::GtEqF { .. }
6164                | MicroOp::ArrLoad { .. }
6165                | MicroOp::ArrLoad2F { .. }
6166                | MicroOp::ArrLoad2 { .. }
6167                | MicroOp::ArrStore { .. }
6168                | MicroOp::ArrRMW { .. }
6169                | MicroOp::ArrCondSwap { .. }
6170                | MicroOp::ArrSwap { .. }
6171                | MicroOp::FmaF { .. }
6172                | MicroOp::Jump { .. }
6173                | MicroOp::JumpIfFalse { .. }
6174                | MicroOp::JumpIfTrue { .. }
6175                | MicroOp::Return { .. }
6176        )
6177    };
6178    // LEVER 3 (fusion-aware pin selection): a float slot that is the RESULT `d`
6179    // of an `AddF` which `fuse_fma` will collapse into an `FmaF` — its product
6180    // operand is the single-use, un-named result of an immediately-preceding
6181    // `MulF` — is KEPT OUT of the float-pin budget. `fuse_fma` runs after pin
6182    // selection; a mem-form `FmaF` writing a PINNED `d` must reload that pin from
6183    // the frame afterwards (one extra piece), which exactly cancels the dispatch
6184    // the fusion saved. Leaving `d` frame-resident lets the `FmaF` write the
6185    // frame directly with NO reload — a true 2-pieces-to-1 win (the nbody
6186    // distance-sum's `dz*dz + partial`, whose single-use partial-sum was
6187    // wastefully pinned). Frame traffic on this temp is L1-free next to the
6188    // saved dispatch (the engine is per-op DISPATCH-bound). Excluding a slot
6189    // that turns out NOT to fuse is at worst neutral (one un-pinned float temp).
6190    // The product `t` is read by the AddF being removed; if `t` were pinned the
6191    // fusion would not fire, so it is left to its own pin eligibility.
6192    let mut fma_result_temps: std::collections::HashSet<u16> = std::collections::HashSet::new();
6193    {
6194        let mut read_counts: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
6195        for op in ops {
6196            for s in micro_read_slots(op) {
6197                *read_counts.entry(s).or_insert(0) += 1;
6198            }
6199        }
6200        for k in 0..ops.len().saturating_sub(1) {
6201            let MicroOp::MulF { dst: t, .. } = ops[k] else { continue };
6202            // The addend `c` is the AddF operand that is NOT the product `t`.
6203            let (d, c) = match ops[k + 1] {
6204                MicroOp::AddF { dst, lhs, rhs } if lhs == t => (dst, rhs),
6205                MicroOp::AddF { dst, lhs, rhs } if rhs == t => (dst, lhs),
6206                _ => continue,
6207            };
6208            // The ACCUMULATOR shape `s = s + a*b` (`c == d`) is NOT a Lever-3
6209            // candidate: there `d` is a loop-carried float that must stay pinned,
6210            // and Lever 1a deliberately refuses to fuse it (a pinned addend `c`
6211            // would have to be spilled). Only a result `d` DISTINCT from its
6212            // addend is the genuine throwaway partial-sum the FmaF absorbs.
6213            if d == c {
6214                continue;
6215            }
6216            // `t` is single-use scratch (only the AddF reads it) — `fuse_fma`'s
6217            // removability precondition for the product (named `t`s are rejected
6218            // there too, but excluding their `d` here is at worst neutral: one
6219            // un-pinned float temp, free next to the per-op dispatch).
6220            let t_dead = (t as usize) < register_count as usize
6221                && read_counts.get(&t).copied().unwrap_or(0) == 1;
6222            if t_dead {
6223                fma_result_temps.insert(d);
6224            }
6225        }
6226    }
6227    // XMM float pinning is enabled for Main-loop REGIONS whose ops are all
6228    // XMM-aware. Array regions are now admitted: the earlier spectral_norm
6229    // SIGSEGV was a TYPE-REUSED slot (a `MulF` result reused as an `ArrStore`
6230    // index) being float-pinned — fixed above by blocking every integer-role
6231    // slot from the float-pin budget, so a mem-form array op never spills an
6232    // f64 over an integer index. FUNCTIONS still keep their floats frame-
6233    // resident (the mode-B call boundary round-trips them anyway).
6234    let block_float_pins = !region || !ops.iter().all(xmm_pin_safe);
6235    for (slot, _) in ranked {
6236        if float_slots.contains(&slot) {
6237            if !block_float_pins
6238                && !float_blocked.contains(&slot)
6239                && !fma_result_temps.contains(&slot)
6240                && float_pins.len() < 6
6241            {
6242                float_pins.push(slot);
6243            }
6244        } else if gp_pins.len() < 4 {
6245            gp_pins.push(slot);
6246        }
6247    }
6248    (gp_pins, float_pins)
6249}
6250
6251impl NativeTier for ForgeTier {
6252    fn compile_function(
6253        &self,
6254        code: &[Op],
6255        entry_pc: usize,
6256        constants: &[Constant],
6257        param_count: u16,
6258        register_count: u16,
6259        self_fi: u16,
6260        param_kinds: &[Option<ParamKind>],
6261        ret_kind: Option<SlotKind>,
6262        ctx: &NativeCtx,
6263        callees: &[CalleeSig],
6264    ) -> Option<Box<dyn NativeFn>> {
6265        self.compiles.fetch_add(1, Ordering::SeqCst);
6266        // The frame layout (and therefore the limit slot the Call micros
6267        // bake) depends on the mode — compute it before adapting, with
6268        // EXACTLY the adapter's pin accounting (params + allocation sites
6269        // + list-returning self-call results); the adapter asserts the
6270        // agreement.
6271        let n_list_params = param_kinds
6272            .iter()
6273            .filter(|k| matches!(k, Some(ParamKind::List(_))))
6274            .count();
6275        let has_sites = code.iter().any(|op| matches!(op, Op::NewEmptyList { .. }));
6276        let mode_b = n_list_params > 0 || has_sites;
6277        let fn_returns_lists = ret_kind.is_none() && mode_b;
6278        let list_param_regs: Vec<u16> = param_kinds
6279            .iter()
6280            .enumerate()
6281            .filter_map(|(i, k)| {
6282                matches!(k, Some(ParamKind::List(_))).then_some(i as u16)
6283            })
6284            .collect();
6285        let npins = discover_list_regs(
6286            code,
6287            &list_param_regs,
6288            fn_returns_lists,
6289            self_fi,
6290            register_count as usize + 2,
6291        )
6292        .iter()
6293        .filter(|&&b| b)
6294        .count();
6295        let frame_size = if mode_b {
6296            register_count as usize + 6 + 3 * npins
6297        } else {
6298            register_count as usize + 3
6299        };
6300        // Self-recursion advances one full frame per level and stages the
6301        // callee window BEFORE the depth check runs — the arena must hold
6302        // MAX_CALL_DEPTH + 1 frames so staging writes can never cross the
6303        // buffer even on the deopt path.
6304        if frame_size * (logicaffeine_compile::semantics::MAX_CALL_DEPTH + 1) > ARENA_SLOTS {
6305            return None;
6306        }
6307        let call_ctx = CallCtx {
6308            table_addr: ctx.table.slot_addr(self_fi as usize),
6309            depth_addr: std::sync::Arc::as_ptr(&ctx.depth) as i64,
6310            status_addr: std::sync::Arc::as_ptr(&ctx.status) as i64,
6311            limit_slot: (frame_size - 1) as u16,
6312            depth_limit: logicaffeine_compile::semantics::MAX_CALL_DEPTH as i64,
6313            self_fi,
6314            table: ctx.table.as_ref(),
6315        };
6316        let adapted = adapt_function(
6317            code,
6318            entry_pc,
6319            constants,
6320            param_count,
6321            register_count,
6322            self_fi,
6323            param_kinds,
6324            ret_kind,
6325            &call_ctx,
6326            callees,
6327        )?;
6328        debug_assert_eq!(adapted.frame_size, frame_size);
6329        // LINEAR SCAN (EXODIA 3.1), mode A only: mode B's precise deopt
6330        // reads the frame mid-run, which the pinning contract forbids.
6331        // Profit = variant-family uses − spill/reload churn at memory-form
6332        // ops − the prologue reload; the four best positive slots pin.
6333        // The value-form float `>`/`>=` compares (`GtF`/`GtEqF`) now have a
6334        // mem-form lowering (Lever 2a: swap operands, reuse the LtF/LeF stencil —
6335        // IEEE-exact, NaN-false), so they spill/reload their pinned operands like
6336        // any mem-form op and no longer force the whole function unpinned.
6337        // `ListClear` is still excluded: the mem-form emitter cannot lower it
6338        // without leaving a pinned ptr/len register stale (it would miscompile —
6339        // observed on knapsack).
6340        let pinnable = !adapted.micro.iter().any(|op| {
6341            matches!(op, MicroOp::ListClear { .. })
6342        });
6343        // WS-G WAVE 12: the CONTIGUOUS regalloc FUNCTION backend. For a
6344        // non-precise mode-A function whose every op (the self-calls included)
6345        // is in the backend's supported subset, emit ONE register-allocated
6346        // x86-64 function with a real SysV self-call — 4-6x faster than the
6347        // per-piece tier on the recursion cluster. Returns None on any
6348        // unsupported op (a cross-function Call, list/map/byte ops, …), in which
6349        // case we fall through to the pinned per-piece tier, byte-identical to
6350        // today. The self-entry patch is INTERNAL (an entry cell), so the
6351        // stencil patch path below is skipped for this chain. `LOGOS_REGALLOC=0`
6352        // is the kill-switch.
6353        #[cfg(target_arch = "x86_64")]
6354        let regalloc_enabled = logicaffeine_forge::regalloc::regalloc_enabled()
6355            && !std::env::var("LOGOS_JIT_REGALLOC").is_ok_and(|v| v == "0");
6356        #[cfg(target_arch = "x86_64")]
6357        let regalloc_chain: Option<CompiledChain> = if !regalloc_enabled {
6358            None
6359        } else if adapted.precise.is_none() {
6360            // MODE A (scalar, classic replay deopt): the original contiguous
6361            // FUNCTION backend.
6362            logicaffeine_forge::regalloc::compile_function_regalloc(
6363                &adapted.micro,
6364                Some(ctx.status.clone()),
6365                call_ctx.depth_addr,
6366            )
6367        } else if std::env::var("LOGOS_REGALLOC_PRECISE").as_deref() != Ok("0") {
6368            // MODE B (list-param, in-place array mutation): the PRECISE
6369            // contiguous backend — a side exit materializes the native frame and
6370            // resumes AT the faulting op (no replay-from-head double-apply). Only
6371            // when the adapter built the per-op deopt-code table. `None` on any
6372            // unsupported op (e.g. a list-RETURN allocation/Map shape) → fall back
6373            // to the per-piece precise stencil tier, byte-identical to today.
6374            // `LOGOS_REGALLOC_PRECISE=0` is the targeted kill-switch.
6375            adapted.deopt_codes.as_deref().and_then(|codes| {
6376                logicaffeine_forge::regalloc::compile_function_regalloc_precise(
6377                    &adapted.micro,
6378                    Some(ctx.status.clone()),
6379                    call_ctx.depth_addr,
6380                    codes,
6381                )
6382            })
6383        } else {
6384            None
6385        };
6386        #[cfg(not(target_arch = "x86_64"))]
6387        let regalloc_chain: Option<CompiledChain> = None;
6388
6389        let chain = if let Some(c) = regalloc_chain {
6390            self.regalloc_functions.fetch_add(1, Ordering::SeqCst);
6391            // A `CallSelfCopy` in the stream is a FUSED contiguous-arg self-call
6392            // (Lever A's fusion, decided by the adapter) — the regalloc backend
6393            // stages the contiguous arg block in one go just like the stencil
6394            // tier did, so it remains the same observable. Count it (the
6395            // kill-switch path emits per-`Move` `CallSelf` instead, leaving this
6396            // at zero).
6397            let fused = adapted
6398                .micro
6399                .iter()
6400                .filter(|op| matches!(op, MicroOp::CallSelfCopy { .. }))
6401                .count() as u32;
6402            if fused != 0 {
6403                self.pinned_self_calls.fetch_add(fused, Ordering::SeqCst);
6404            }
6405            c
6406        } else {
6407            let (gp_pins, float_pins): (Vec<u16>, Vec<u16>) = if pinnable
6408                && adapted.precise.is_none()
6409                && !std::env::var("LOGOS_JIT_REGALLOC").is_ok_and(|v| v == "0")
6410            {
6411                select_pins(&adapted.micro, register_count, false)
6412            } else {
6413                (Vec::new(), Vec::new())
6414            };
6415            let chain = if gp_pins.is_empty() && float_pins.is_empty() {
6416                compile_straightline_coded(
6417                    &adapted.micro,
6418                    Some(ctx.status.clone()),
6419                    adapted.deopt_codes.as_deref(),
6420                    call_ctx.depth_addr,
6421                )
6422                .ok()?
6423            } else {
6424                compile_straightline_pinned_float(
6425                    &adapted.micro,
6426                    &gp_pins,
6427                    &float_pins,
6428                    Some(ctx.status.clone()),
6429                )
6430                .ok()?
6431            };
6432            // Self-entry patch: write this chain's base into every direct
6433            // self-call site. Targets without post-seal patching (MAP_JIT)
6434            // fail here — fall back to bytecode rather than run unpatched.
6435            if chain.has_patch_marks() && chain.patch_marked(chain.base()).is_err() {
6436                return None;
6437            }
6438            let fused = adapted
6439                .micro
6440                .iter()
6441                .filter(|op| matches!(op, MicroOp::CallSelfCopy { .. }))
6442                .count() as u32;
6443            if fused != 0 {
6444                self.pinned_self_calls.fetch_add(fused, Ordering::SeqCst);
6445            }
6446            chain
6447        };
6448        self.successes.fetch_add(1, Ordering::SeqCst);
6449        Some(Box::new(ChainFn {
6450            chain,
6451            limit_slot: call_ctx.limit_slot as usize,
6452            depth: ctx.depth.clone(),
6453            ret: adapted.ret,
6454            published_regc: (frame_size - 3) as i64,
6455            precise: adapted.precise,
6456            stats: self.runtime.clone(),
6457        }))
6458    }
6459
6460    fn compile_region(
6461        &self,
6462        code: &[Op],
6463        head_pc: usize,
6464        exit_pc: usize,
6465        constants: &[Constant],
6466        register_count: u16,
6467        named: &[bool],
6468        observed: &[ObservedKind],
6469        ctx: &NativeCtx,
6470        callees: &[logicaffeine_compile::vm::CalleeSig],
6471    ) -> Option<Box<dyn RegionFn>> {
6472        self.region_compiles.fetch_add(1, Ordering::SeqCst);
6473        let dbg = std::env::var_os("LOGOS_RDIAG").is_some();
6474        let Some((mut micro, frame_size, guard, free, writes, arrays, region_return, hoist_guards, precise)) =
6475            adapt_region(
6476            code,
6477            head_pc,
6478            exit_pc,
6479            constants,
6480            register_count,
6481            named,
6482            observed,
6483            ctx,
6484            callees,
6485        ) else {
6486            if dbg { eprintln!("RDIAG head_pc={head_pc}: adapt_region -> None"); }
6487            return None;
6488        };
6489        let has_calls = code.iter().any(|op| matches!(op, Op::Call { .. }));
6490        // A calling region's chain must check the SHARED status cell — the
6491        // call stencil writes its deopt marker there. A PRECISE region needs it
6492        // too: every side exit stores its encoded resume pc through it.
6493        let status =
6494            (has_calls || precise.is_some()).then(|| ctx.status.clone());
6495        // REGION REGISTER ALLOCATION (EXODIA 3.1, sprint 13's deferred half).
6496        // Pin the hottest INTEGER slots that are not in the write-set or the
6497        // array-handle set: the write-set must stay frame-resident for the
6498        // VM's success copy-back, and dead-at-exit slots (loop counters,
6499        // read-only bounds, scratch) need only the prologue reload — so NO
6500        // exit-spill machinery is required, and a mid-loop side-exit keeps
6501        // the discard-and-replay contract untouched (the VM replays from the
6502        // region head on its OWN registers; the native frame's scalars are
6503        // never read back). The value-form float `>`/`>=` compares (`GtF`/
6504        // `GtEqF`) now have a mem-form lowering (Lever 2a: swap operands, reuse
6505        // the LtF/LeF stencil — IEEE-exact, NaN-false), so a region carrying a
6506        // value-form float `>`/`>=` keeps its XMM pins instead of dropping all
6507        // of them. (A float `>` USED AS A BRANCH already fuses into the register-
6508        // form `BranchF`; only the VALUE form produces a surviving `GtF`/`GtEqF`.)
6509        //
6510        // `ListClear` is still excluded: the mem-form emitter has no arm for it
6511        // (it resets a list's vec/ptr/len through a helper, which would leave a
6512        // pinned ptr/len register stale), so a pinned region containing one
6513        // would miscompile (observed: knapsack's DP-row reuse). Such a region
6514        // stays unpinned and runs the correct unpinned path.
6515        let pinnable = !micro.iter().any(|op| {
6516            // A `MemMem` region reads/writes every operand through the frame in
6517            // its helper, so it must run UNPINNED (no register-resident slot the
6518            // helper would miss); `ListClear`'s mem-form emitter has no pinned arm.
6519            matches!(op, MicroOp::ListClear { .. } | MicroOp::MemMem { .. })
6520        });
6521        let (gp_pins, float_pins): (Vec<u16>, Vec<u16>) = if !pinnable
6522            // Precise regions materialize the frame's scalars on a side exit, so
6523            // every scalar must stay frame-resident — no GP register pinning
6524            // (mirrors the function tier's `adapted.precise.is_none()` gate).
6525            || precise.is_some()
6526            || std::env::var("LOGOS_JIT_REGALLOC").is_ok_and(|v| v == "0")
6527        {
6528            (Vec::new(), Vec::new())
6529        } else {
6530            // Phase 2 (write-set pinning): the WRITE-SET is now eligible —
6531            // the pinned compiler's success-exit epilogue spills each pinned
6532            // slot back to its frame cell, so the VM's write-back still sees
6533            // loop-carried values. Only ARRAY handle registers stay excluded
6534            // (they are pinned through the dedicated ptr/len triple, not GP
6535            // register threading).
6536            let array_regs: std::collections::HashSet<u16> =
6537                arrays.iter().map(|a| a.reg).collect();
6538            let (gp, fp) = select_pins(&micro, register_count, true);
6539            let mut gp: Vec<u16> =
6540                gp.into_iter().filter(|r| !array_regs.contains(r)).collect();
6541            let fp: Vec<u16> = fp.into_iter().filter(|r| !array_regs.contains(r)).collect();
6542            // REGISTER-FORM array access: hand the hottest INT array whose base
6543            // pointer is STABLE (never pushed-to in this region → no realloc) a
6544            // free GP register, so its word ArrLoad/ArrStore read the pointer
6545            // from a register instead of re-loading the frame cell every access.
6546            // Parked default-OFF: measured ~0% (the interpreter is per-op
6547            // DISPATCH-bound, not frame-traffic-bound — re-reading the array
6548            // pointer from its L1-hot frame cell is free next to the stencil
6549            // dispatch). `LOGOS_ARRPTR=1` re-enables for experiments.
6550            if gp.len() < 4 && std::env::var("LOGOS_ARRPTR").is_ok() {
6551                let pushed: std::collections::HashSet<u16> = micro
6552                    .iter()
6553                    .filter_map(|op| match op {
6554                        MicroOp::ArrPush { vec_slot, .. } => Some(*vec_slot),
6555                        _ => None,
6556                    })
6557                    .collect();
6558                let mut best: Option<(u16, usize)> = None;
6559                for a in &arrays {
6560                    if a.elem != PinElem::Int || pushed.contains(&a.vec_slot) {
6561                        continue;
6562                    }
6563                    let cnt = micro
6564                        .iter()
6565                        .filter(|op| match op {
6566                            MicroOp::ArrLoad { ptr_slot, byte: false, .. }
6567                            | MicroOp::ArrStore { ptr_slot, byte: false, .. } => {
6568                                *ptr_slot == a.ptr_slot
6569                            }
6570                            _ => false,
6571                        })
6572                        .count();
6573                    if cnt > 0 && best.map_or(true, |(_, b)| cnt > b) {
6574                        best = Some((a.ptr_slot, cnt));
6575                    }
6576                }
6577                if let Some((ptr_slot, _)) = best {
6578                    gp.push(ptr_slot);
6579                }
6580            }
6581            (gp, fp)
6582        };
6583        // COPY-PROPAGATION + CONSTANT CSE — kills the redundant scratch-Move
6584        // chains and re-loaded constants the bytecode lowering leaves in hot
6585        // loops (the per-op DISPATCH is the floor, so fewer ops = faster). Run
6586        // AFTER pin selection, protecting the chosen pins, so it never perturbs
6587        // the greedy allocator (the reason the earlier pre-selection version had
6588        // to be array-free-gated) — now it applies in array regions too. Sound
6589        // only for replay-from-head (non-precise) regions: removing micro ops
6590        // renumbers jump targets (remapped) but would break per-op precise
6591        // resume codes. `LOGOS_COPYPROP=0` is the kill-switch.
6592        if precise.is_none() && std::env::var("LOGOS_COPYPROP").as_deref() != Ok("0") {
6593            let pinset: std::collections::HashSet<u16> =
6594                gp_pins.iter().chain(float_pins.iter()).copied().collect();
6595            copy_propagate(&mut micro, named, register_count, &pinset);
6596        }
6597        // WS-G PRE-FUSION SNAPSHOT (approach a). The array-fusion passes below
6598        // (`ArrRMW`/`ArrLoad2`/`ArrLoadAffine`/`ArrCondSwap`/`ArrSwap`) exist to
6599        // cut PIECE count in the per-piece stencil tier. The CONTIGUOUS regalloc
6600        // backend has no per-piece overhead and handles RAW `ArrLoad`/`ArrStore`
6601        // directly, so it must see the UN-fused stream. Snapshot the cleaned
6602        // (copy-propagated) micro here, BEFORE the fusions, and try regalloc on
6603        // it at the dispatch below; only if regalloc declines do the fusions and
6604        // the per-piece stencil tier take over the (mutated) `micro`. The
6605        // snapshot is taken only for a non-precise region (the only kind
6606        // regalloc compiles); precise regions never reach it.
6607        #[cfg(target_arch = "x86_64")]
6608        let prefusion_micro: Option<Vec<MicroOp>> =
6609            (precise.is_none() && logicaffeine_forge::regalloc::regalloc_enabled())
6610                .then(|| micro.clone());
6611        // ARRAY READ-MODIFY-WRITE FUSION: after copy-prop/CSE has cleaned the
6612        // stream and deduped the operand constant, collapse `arr[i] = arr[i] OP
6613        // operand` (ArrLoad + int ALU + ArrStore) into one ArrRMW stencil — the
6614        // dispatch-reduction lever for the indexed-RMW loops (histogram,
6615        // counting sort). Non-precise only (it renumbers micro indices).
6616        // `LOGOS_RMW=0` is the kill-switch.
6617        if precise.is_none() && std::env::var("LOGOS_RMW").as_deref() != Ok("0") {
6618            let pinset: std::collections::HashSet<u16> =
6619                gp_pins.iter().chain(float_pins.iter()).copied().collect();
6620            fuse_array_rmw(&mut micro, named, register_count, &pinset);
6621        }
6622        // TWO-BUFFER INTEGER LOAD+BINOP FUSION: collapse `a[i] OP b[j]`
6623        // (ArrLoad + ArrLoad + int Add/Sub/Mul) into one ArrLoad2 stencil — the
6624        // dispatch-reduction lever for the integer dot-product / matrix-multiply
6625        // inner loop, where two distinct pinned buffers feed a binop. Runs after
6626        // RMW (the c-accumulator's load+add+mod+store is not a clean RMW, so the
6627        // two passes never contend for the same ops). Non-precise only (it
6628        // renumbers micro indices). `LOGOS_LD2=0` is the kill-switch.
6629        if precise.is_none() && std::env::var("LOGOS_LD2").as_deref() != Ok("0") {
6630            let pinset: std::collections::HashSet<u16> =
6631                gp_pins.iter().chain(float_pins.iter()).copied().collect();
6632            fuse_array_ld2(&mut micro, named, register_count, &pinset);
6633        }
6634        // FLOAT MULTIPLY-ADD FUSION: merge `a*b; +c` (MulF feeding a single-use
6635        // AddF) into one FmaF stencil — the dispatch-reduction lever for the
6636        // float arithmetic chains in the float cluster (nbody). Only fuses
6637        // frame-resident operands (spill-free). `LOGOS_FMA=0` is the kill-switch.
6638        if precise.is_none() && std::env::var("LOGOS_FMA").as_deref() != Ok("0") {
6639            let pinset: std::collections::HashSet<u16> =
6640                gp_pins.iter().chain(float_pins.iter()).copied().collect();
6641            fuse_fma(&mut micro, named, register_count, &pinset);
6642        }
6643        // CONDITIONAL-SWAP FUSION: collapse the sort inner-loop compare-and-swap
6644        // (ArrLoad×2 + Branch + ArrStore×2 → 1) — now fires thanks to the
6645        // precise-region-live-out keystone freeing the loop-local a/b.
6646        // `LOGOS_CONDSWAP=0` kills it.
6647        if std::env::var("LOGOS_DUMP_MICRO").ok().and_then(|v| v.parse::<usize>().ok()) == Some(head_pc) {
6648            eprintln!("=== MICRO head_pc={head_pc} ({} ops) ===", micro.len());
6649            for (i, m) in micro.iter().enumerate() {
6650                eprintln!("  [{i}] {m:?}");
6651            }
6652        }
6653        if precise.is_none() && std::env::var("LOGOS_CONDSWAP").as_deref() != Ok("0") {
6654            let pinset: std::collections::HashSet<u16> =
6655                gp_pins.iter().chain(float_pins.iter()).copied().collect();
6656            fuse_cond_swap(&mut micro, named, register_count, &pinset);
6657        }
6658        // UNCONDITIONAL-SWAP FUSION: collapse the `tmp` exchange (quick/heap/merge
6659        // sort: load,load,store,store → 1). `LOGOS_SWAP=0` kills it.
6660        if precise.is_none() && std::env::var("LOGOS_SWAP").as_deref() != Ok("0") {
6661            let pinset: std::collections::HashSet<u16> =
6662                gp_pins.iter().chain(float_pins.iter()).copied().collect();
6663            fuse_swap(&mut micro, named, register_count, &pinset);
6664        }
6665        // AFFINE-INDEX LOAD FUSION: fold the index arithmetic of a read-only
6666        // array load (`prev[w - wi + 1]`, the heap-sift reads, graph adjacency
6667        // reads) into the load stencil. Runs LAST among the array fusions so the
6668        // two-buffer (`ArrLoad2`), RMW, and swap fusions claim their raw loads
6669        // first; this pass only takes the plain `ArrLoad`s they leave behind.
6670        // `LOGOS_AFFINE=0` is the kill-switch.
6671        if precise.is_none() && std::env::var("LOGOS_AFFINE").as_deref() != Ok("0") {
6672            let pinset: std::collections::HashSet<u16> =
6673                gp_pins.iter().chain(float_pins.iter()).copied().collect();
6674            fuse_index_affine(&mut micro, named, register_count, &pinset);
6675        }
6676        if dbg {
6677            eprintln!(
6678                "RDIAG head_pc={head_pc}: adapt OK frame={frame_size} pinnable={pinnable} gp_pins={gp_pins:?} float_pins={float_pins:?} arrays={}",
6679                arrays.len()
6680            );
6681        }
6682        // WS-G CONTIGUOUS REGALLOC BACKEND (LOGOS_REGALLOC default ON).
6683        // For a non-precise integer region whose every op is in the contiguous
6684        // backend's supported subset, emit ONE register-allocated function (no
6685        // per-piece stencil boundaries). Returns None on any unsupported op →
6686        // we fall through to the existing per-piece tiers, so behavior with the
6687        // flag off — or on an unsupported region — is byte-identical to today.
6688        //
6689        // It is tried on the PRE-FUSION snapshot (approach a): the backend has
6690        // no per-piece cost, so it wants the RAW `ArrLoad`/`ArrStore` stream,
6691        // not the fused `ArrRMW`/`ArrLoad2`/… the array fusions produced for the
6692        // stencil tier. If regalloc declines (an unsupported op — float arrays,
6693        // byte loads, calls, globals, list ops, …) we fall through to the
6694        // already-fused `micro` and the per-piece tier, byte-identical to today.
6695        #[cfg(target_arch = "x86_64")]
6696        let regalloc_chain: Option<CompiledChain> = prefusion_micro
6697            .as_ref()
6698            .and_then(|m| logicaffeine_forge::regalloc::compile_region_regalloc(m, status.clone()));
6699        #[cfg(not(target_arch = "x86_64"))]
6700        let regalloc_chain: Option<CompiledChain> = None;
6701
6702        let chain = if let Some(c) = regalloc_chain {
6703            self.regalloc_regions.fetch_add(1, Ordering::SeqCst);
6704            if dbg {
6705                eprintln!("RDIAG head_pc={head_pc}: CONTIGUOUS REGALLOC backend (arrays={})", arrays.len());
6706            }
6707            c
6708        } else if let Some(p) = precise.as_ref() {
6709            // PRECISE region (the fannkuch / graph_bfs worklist shape: an
6710            // in-place `SetIndex` beside a reallocating `ArrPush`). The depth
6711            // cell value rides the resume tag's high 32 bits; the VM masks it out
6712            // of the region resume (only the low-32 resume code is read back).
6713            let depth_addr = std::sync::Arc::as_ptr(&ctx.depth) as i64;
6714            // WS-G WAVE 21: try the CONTIGUOUS regalloc backend FIRST, with the
6715            // per-op precise deopt codes. It register-allocates the region's
6716            // scalars (the per-piece precise stencil tier kept every scalar
6717            // frame-resident and paid the 4-6× per-piece dispatch overhead) while
6718            // flushing every resident-written scalar to its frame slot at each
6719            // precise side exit, so the VM's resume reads correct values. No
6720            // fusions / copy-prop ran (all gated `precise.is_none()`), so `micro`
6721            // is the raw lowered stream `p.deopt_codes` is parallel to. On any
6722            // unsupported op the backend declines and the per-piece precise tier
6723            // takes over — byte-identical to before this wave.
6724            #[cfg(target_arch = "x86_64")]
6725            let precise_regalloc: Option<CompiledChain> = (logicaffeine_forge::regalloc::regalloc_enabled()
6726                && std::env::var("LOGOS_REGALLOC_PRECISE").as_deref() != Ok("0"))
6727            .then(|| {
6728                logicaffeine_forge::regalloc::compile_region_regalloc_precise(
6729                    &micro,
6730                    status.clone(),
6731                    depth_addr,
6732                    &p.deopt_codes,
6733                )
6734            })
6735            .flatten();
6736            #[cfg(not(target_arch = "x86_64"))]
6737            let precise_regalloc: Option<CompiledChain> = None;
6738            if let Some(c) = precise_regalloc {
6739                self.regalloc_regions.fetch_add(1, Ordering::SeqCst);
6740                self.regalloc_precise_regions.fetch_add(1, Ordering::SeqCst);
6741                if dbg {
6742                    eprintln!("RDIAG head_pc={head_pc}: CONTIGUOUS REGALLOC backend (PRECISE, arrays={})", arrays.len());
6743                }
6744                c
6745            } else {
6746                match compile_straightline_coded(&micro, status, Some(&p.deopt_codes), depth_addr) {
6747                    Ok(c) => c,
6748                    Err(e) => {
6749                        if dbg { eprintln!("RDIAG head_pc={head_pc}: compile_straightline_coded(precise) -> Err({e:?})"); }
6750                        return None;
6751                    }
6752                }
6753            }
6754        } else if gp_pins.is_empty() && float_pins.is_empty() {
6755            match compile_straightline_with(&micro, status) {
6756                Ok(c) => c,
6757                Err(e) => {
6758                    if dbg { eprintln!("RDIAG head_pc={head_pc}: compile_straightline_with -> Err({e:?})"); }
6759                    return None;
6760                }
6761            }
6762        } else {
6763            match compile_straightline_pinned_float(&micro, &gp_pins, &float_pins, status) {
6764                Ok(c) => c,
6765                Err(e) => {
6766                    if dbg { eprintln!("RDIAG head_pc={head_pc}: compile_straightline_pinned_float -> Err({e:?})"); }
6767                    return None;
6768                }
6769            }
6770        };
6771        self.region_successes.fetch_add(1, Ordering::SeqCst);
6772        let call_support = has_calls.then(|| {
6773            (ARENA_SLOTS, (frame_size - 1) as u16, ctx.depth.clone())
6774        });
6775        Some(Box::new(RegionChain {
6776            chain,
6777            frame_size,
6778            call_support,
6779            guard,
6780            free,
6781            writes,
6782            arrays,
6783            region_return,
6784            hoist_guards,
6785            precise_kinds: precise.map(|p| p.kinds_by_pc),
6786        }))
6787    }
6788}
6789
6790/// Install the forge tier as the process-wide native tier. Idempotent —
6791/// the first call wins; later calls return the already-installed tier.
6792///
6793/// Call once at binary startup (CLI, server). The live VM constructors in
6794/// `logicaffeine-compile` pick it up for every program they run.
6795///
6796/// `LOGOS_NATIVE_TIER=0` skips installation entirely — the diagnostic
6797/// kill-switch that isolates pure-bytecode wall time from tiered runs.
6798/// Install the diagnostic SIGSEGV/SIGBUS tracer (no-op unless `LOGOS_SEGV_TRAP`
6799/// is set). Used to localize faults inside JIT'd code while root-causing.
6800pub fn segv_trace_install() {
6801    logicaffeine_forge::segv_trace::install();
6802}
6803
6804pub fn install() -> &'static ForgeTier {
6805    static TIER: std::sync::OnceLock<ForgeTier> = std::sync::OnceLock::new();
6806    let tier = TIER.get_or_init(ForgeTier::new);
6807    if !std::env::var("LOGOS_NATIVE_TIER").is_ok_and(|v| v == "0") {
6808        install_native_tier(tier);
6809    }
6810    tier
6811}
6812
6813#[cfg(test)]
6814mod memmem_safety_tests {
6815    use super::*;
6816
6817    /// SECURITY/UB regression (CRITIQUE #6). `logos_rt_memmem` cast its `i64` lengths
6818    /// straight to `usize` for `from_raw_parts`, so a negative `needle_len` or
6819    /// `haystack_len` became a ~2^64 slice length — immediate undefined behavior
6820    /// (`from_raw_parts` requires `len <= isize::MAX`), reachable on program-controlled
6821    /// input. The guard must bail to the recoverable deopt sentinel before constructing
6822    /// any slice. (The pre-fix failure is UB, so it is established by inspection of the
6823    /// `from_raw_parts` calls rather than executed as a crashing test.)
6824    #[test]
6825    fn out_of_range_lengths_bail_safely_without_constructing_an_invalid_slice() {
6826        let haystack = b"abcabc";
6827        let needle = b"bc";
6828        let hp = haystack.as_ptr() as i64;
6829        let np = needle.as_ptr() as i64;
6830        unsafe {
6831            // negative needle_len → ~2^64 slice length pre-fix; must bail.
6832            assert_eq!(
6833                logos_rt_memmem(hp, 6, np, 2, -1, 1, 0),
6834                LOGOS_MEMMEM_DEOPT,
6835                "negative needle_len must bail to deopt, never reach from_raw_parts"
6836            );
6837            assert_eq!(logos_rt_memmem(hp, -1, np, 2, 2, 1, 0), LOGOS_MEMMEM_DEOPT, "negative haystack_len must bail");
6838            assert_eq!(logos_rt_memmem(hp, 6, np, -1, 2, 1, 0), LOGOS_MEMMEM_DEOPT, "negative needle_buf_len must bail");
6839        }
6840    }
6841
6842    /// Regression: valid inputs still search correctly after the guard is added.
6843    /// `"bc"` occurs twice in `"abcabc"` (0-based offsets 1 and 4).
6844    #[test]
6845    fn valid_search_still_counts_matches() {
6846        let haystack = b"abcabc";
6847        let needle = b"bc";
6848        let count =
6849            unsafe { logos_rt_memmem(haystack.as_ptr() as i64, 6, needle.as_ptr() as i64, 2, 2, 1, 0) };
6850        assert_eq!(count, 2, "the guard must not change correct search results");
6851    }
6852}
6853
6854#[cfg(test)]
6855mod select_pins_tests {
6856    use super::*;
6857
6858    /// A slot used as BOTH an f64 (a `MulF` result, float-valued) AND an i64
6859    /// (an `ArrStore` index, integer-valued) is type-reused scratch. It must
6860    /// NEVER be float-pinned: the mem-form `ArrStore` spills the float pin to
6861    /// the frame cell, overwriting the integer index the `Add` wrote, so the
6862    /// store indexes through an f64 bit-pattern → a wild write. This was the
6863    /// spectral_norm SIGSEGV. The integer-role classification in `select_pins`
6864    /// must keep the mixed slot out of the float-pin budget (frame-resident).
6865    #[test]
6866    fn float_result_reused_as_array_index_is_not_float_pinned() {
6867        // register_count = 8 → slots 0..7 are registers; 8/9 stand in for the
6868        // pinned array's ptr/len cells. Slot 5 is the type-reused cell.
6869        let ops = vec![
6870            MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 6 },
6871            MicroOp::MulF { dst: 5, lhs: 2, rhs: 3 }, // slot 5 holds an f64 …
6872            MicroOp::AddF { dst: 4, lhs: 4, rhs: 5 }, // … consumed as a float
6873            MicroOp::Add { dst: 5, lhs: 0, rhs: 1 },  // slot 5 reused as an i64 index
6874            MicroOp::ArrStore {
6875                src: 4,
6876                idx: 5,
6877                ptr_slot: 8,
6878                len_slot: 9,
6879                byte: false,
6880                narrow32: false,
6881                checked: false,
6882            },
6883            MicroOp::Jump { target: 0 },
6884            MicroOp::Return { src: 0 },
6885        ];
6886        let (_gp, fpins) = select_pins(&ops, 8, true);
6887        assert!(
6888            !fpins.contains(&5),
6889            "slot 5 (an f64 result reused as an integer array index) must NOT be \
6890             float-pinned — pinning it spills f64 bits over the integer index; got fpins={fpins:?}"
6891        );
6892    }
6893
6894    /// LEVER 3 — a single-use float partial-sum that is the DESTINATION of an
6895    /// `AddF` consuming an immediately-preceding single-use `MulF` product (the
6896    /// nbody distance-sum's `partial = a*a + (b*b)` shape, `d` distinct from its
6897    /// addend) is the result an FmaF will absorb. It must be KEPT OUT of the
6898    /// float-pin budget so the (later) `fuse_fma` writes the frame directly with
6899    /// NO reload — a 2-pieces-to-1 win — instead of pinning it and paying a reload
6900    /// after the mem-form FmaF.
6901    #[test]
6902    fn fma_result_temp_is_not_float_pinned() {
6903        // partial = aa + b*b, where aa is a frame temp and b*b is single-use.
6904        // slot 4 = partial (the FmaF dst); slot 6 = b*b product (single-use);
6905        // slot 5 = aa (the addend, distinct from 4); slot 7 reads partial after.
6906        let ops = vec![
6907            MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 6 },
6908            MicroOp::MulF { dst: 6, lhs: 2, rhs: 2 }, // b*b → single-use product
6909            MicroOp::AddF { dst: 4, lhs: 5, rhs: 6 }, // partial = aa + (b*b), 4 != 5
6910            MicroOp::SqrtF { dst: 7, src: 4 },        // partial's only reader
6911            MicroOp::Jump { target: 0 },
6912            MicroOp::Return { src: 0 },
6913        ];
6914        let (_gp, fpins) = select_pins(&ops, 8, true);
6915        assert!(
6916            !fpins.contains(&4),
6917            "slot 4 (a single-use FmaF-candidate partial-sum, distinct from its \
6918             addend) must NOT be float-pinned (Lever 3); got fpins={fpins:?}"
6919        );
6920    }
6921
6922    /// LEVER 3 boundary — a LOOP-CARRIED accumulator `s = s + a*b` (`c == d == s`)
6923    /// is NOT an FmaF candidate (Lever 1a refuses to fuse a pinned addend), so
6924    /// Lever 3 must NOT exclude it from the float-pin budget. (The accumulator is
6925    /// read+written by the AddF as a float; the region returns an unrelated int so
6926    /// the `Return`-blocks-float rule does not interfere with the assertion.)
6927    #[test]
6928    fn loop_carried_float_accumulator_not_excluded_by_lever3() {
6929        // s = s + a*b: slot 4 = s (read + written by the AddF), 6 = a*b product.
6930        let ops = vec![
6931            MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 4 },
6932            MicroOp::MulF { dst: 6, lhs: 2, rhs: 3 }, // a*b → product
6933            MicroOp::AddF { dst: 4, lhs: 4, rhs: 6 }, // s = s + product (c == d == 4)
6934            MicroOp::Jump { target: 0 },
6935            MicroOp::Return { src: 0 }, // returns an int, not the accumulator
6936        ];
6937        let (_gp, fpins) = select_pins(&ops, 8, true);
6938        assert!(
6939            fpins.contains(&4),
6940            "slot 4 (a loop-carried float accumulator, c == d) must STILL be \
6941             float-pin-eligible — it is not an FmaF candidate, so Lever 3 must not \
6942             exclude it; got fpins={fpins:?}"
6943        );
6944    }
6945}
6946
6947#[cfg(test)]
6948mod fuse_rmw_tests {
6949    use super::*;
6950    use std::collections::HashSet;
6951
6952    // register_count = 8 (slots 0..7 are VM registers); slots 6/7 stand in for
6953    // the pinned array's ptr/len cells. `named` marks slot 5 (the index) live;
6954    // the scratch temps 3 (loaded value) and 4 (sum) are un-named.
6955    fn named8(live: &[u16]) -> Vec<bool> {
6956        let mut n = vec![false; 8];
6957        for &s in live {
6958            n[s as usize] = true;
6959        }
6960        n
6961    }
6962
6963    /// The canonical histogram idiom `arr[i] = arr[i] + operand` — ArrLoad into
6964    /// a single-use scratch, Add with the operand, ArrStore the result back to
6965    /// the SAME array+index — collapses to one ArrRMW.
6966    #[test]
6967    fn load_add_store_to_same_cell_fuses_to_rmw() {
6968        let mut micro = vec![
6969            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
6970            MicroOp::Add { dst: 4, lhs: 3, rhs: 2 },
6971            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
6972            MicroOp::Return { src: 0 },
6973        ];
6974        fuse_array_rmw(&mut micro, &named8(&[2, 5]), 8, &HashSet::new());
6975        assert_eq!(micro.len(), 2, "the 3-op idiom must collapse to 1 op + Return");
6976        match micro[0] {
6977            MicroOp::ArrRMW { idx, operand, ptr_slot, len_slot, op, checked } => {
6978                assert_eq!((idx, operand, ptr_slot, len_slot), (5, 2, 6, 7));
6979                assert_eq!(op, RmwOp::Add);
6980                assert!(checked);
6981            }
6982            ref other => panic!("expected ArrRMW, got {other:?}"),
6983        }
6984    }
6985
6986    /// Subtraction is non-commutative: it fuses ONLY when the loaded element is
6987    /// the left operand (`buf[i] - operand`), and the operand is the right.
6988    #[test]
6989    fn sub_fuses_only_with_loaded_value_on_the_left() {
6990        let mut micro = vec![
6991            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: false },
6992            MicroOp::Sub { dst: 4, lhs: 3, rhs: 2 },
6993            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: false },
6994            MicroOp::Return { src: 0 },
6995        ];
6996        fuse_array_rmw(&mut micro, &named8(&[2, 5]), 8, &HashSet::new());
6997        match micro[0] {
6998            MicroOp::ArrRMW { op: RmwOp::Sub, operand: 2, checked: false, .. } => {}
6999            ref other => panic!("expected unchecked ArrRMW Sub operand=2, got {other:?}"),
7000        }
7001
7002        // operand - buf[i] is NOT an RMW (the loaded value is on the right).
7003        let mut rev = vec![
7004            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: false },
7005            MicroOp::Sub { dst: 4, lhs: 2, rhs: 3 },
7006            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: false },
7007            MicroOp::Return { src: 0 },
7008        ];
7009        fuse_array_rmw(&mut rev, &named8(&[2, 5]), 8, &HashSet::new());
7010        assert!(
7011            matches!(rev[0], MicroOp::ArrLoad { .. }),
7012            "operand - buf[i] must NOT fuse: {rev:?}"
7013        );
7014    }
7015
7016    /// A bounds-checked load with an unchecked store (or vice versa) fuses to a
7017    /// CHECKED rmw — a single pre-write check covers both (same index).
7018    #[test]
7019    fn mixed_checked_load_unchecked_store_fuses_checked() {
7020        let mut micro = vec![
7021            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7022            MicroOp::BitOr { dst: 4, lhs: 3, rhs: 2 },
7023            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: false },
7024            MicroOp::Return { src: 0 },
7025        ];
7026        fuse_array_rmw(&mut micro, &named8(&[2, 5]), 8, &HashSet::new());
7027        match micro[0] {
7028            MicroOp::ArrRMW { op: RmwOp::Or, checked: true, .. } => {}
7029            ref other => panic!("expected checked ArrRMW Or, got {other:?}"),
7030        }
7031    }
7032
7033    /// The loaded value must be single-use: if `t` is read again (here also
7034    /// stored elsewhere), folding it away would lose that read — no fusion.
7035    #[test]
7036    fn multi_use_loaded_value_blocks_fusion() {
7037        let mut micro = vec![
7038            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7039            MicroOp::Add { dst: 4, lhs: 3, rhs: 2 },
7040            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7041            MicroOp::Move { dst: 1, src: 3 }, // second use of the loaded value
7042            MicroOp::Return { src: 1 },
7043        ];
7044        fuse_array_rmw(&mut micro, &named8(&[1, 2, 5]), 8, &HashSet::new());
7045        assert!(matches!(micro[0], MicroOp::ArrLoad { .. }), "multi-use t must block fusion");
7046    }
7047
7048    /// Load and store to DIFFERENT arrays (or different indices) is not an
7049    /// in-place RMW and must never fuse.
7050    #[test]
7051    fn different_array_or_index_blocks_fusion() {
7052        // different ptr_slot
7053        let mut a = vec![
7054            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7055            MicroOp::Add { dst: 4, lhs: 3, rhs: 2 },
7056            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 1, len_slot: 7, byte: false, narrow32: false, checked: true },
7057            MicroOp::Return { src: 0 },
7058        ];
7059        fuse_array_rmw(&mut a, &named8(&[1, 2, 5]), 8, &HashSet::new());
7060        assert!(matches!(a[0], MicroOp::ArrLoad { .. }), "different array must block fusion");
7061        // different index
7062        let mut b = vec![
7063            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7064            MicroOp::Add { dst: 4, lhs: 3, rhs: 2 },
7065            MicroOp::ArrStore { src: 4, idx: 1, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7066            MicroOp::Return { src: 0 },
7067        ];
7068        fuse_array_rmw(&mut b, &named8(&[1, 2, 5]), 8, &HashSet::new());
7069        assert!(matches!(b[0], MicroOp::ArrLoad { .. }), "different index must block fusion");
7070    }
7071
7072    /// A jump landing ON the store (or the ALU) means another path enters
7073    /// mid-idiom — fusing would skip the load on that entry. No fusion.
7074    #[test]
7075    fn jump_target_inside_idiom_blocks_fusion() {
7076        let mut micro = vec![
7077            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7078            MicroOp::Add { dst: 4, lhs: 3, rhs: 2 },
7079            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7080            MicroOp::JumpIfTrue { cond: 2, target: 2 }, // jumps onto the store
7081            MicroOp::Return { src: 0 },
7082        ];
7083        fuse_array_rmw(&mut micro, &named8(&[2, 5]), 8, &HashSet::new());
7084        assert!(
7085            matches!(micro[0], MicroOp::ArrLoad { .. }),
7086            "a jump target inside the idiom must block fusion"
7087        );
7088    }
7089
7090    /// THE HISTOGRAM SHAPE: the index `v + 1` is computed TWICE (separate
7091    /// slots, as the bytecode lowers `Set item (v+1) of A to (item (v+1) of A)
7092    /// + 1`). `copy_propagate`'s value-numbering must merge the two `v + 1` into
7093    /// one slot so the load and store share an index, and only then does
7094    /// `fuse_array_rmw` collapse the idiom. This pins the CSE→fusion handoff.
7095    #[test]
7096    fn duplicated_index_expr_is_cse_d_then_fused() {
7097        // v=1 (named loop var); c=2 the `1`; 3/5/6 scratch; 8/9 ptr/len.
7098        let mut micro = vec![
7099            MicroOp::LoadConst { dst: 2, value: 1 },
7100            MicroOp::Add { dst: 3, lhs: 1, rhs: 2 }, // idx_a = v + 1
7101            MicroOp::ArrLoad { dst: 4, idx: 3, ptr_slot: 8, len_slot: 9, byte: false, narrow32: false, checked: true },
7102            MicroOp::Add { dst: 5, lhs: 4, rhs: 2 }, // t2 = arr[idx_a] + 1
7103            MicroOp::Add { dst: 6, lhs: 1, rhs: 2 }, // idx_b = v + 1  (recomputed)
7104            MicroOp::ArrStore { src: 5, idx: 6, ptr_slot: 8, len_slot: 9, byte: false, narrow32: false, checked: true },
7105            MicroOp::Return { src: 1 },
7106        ];
7107        let named = named8_n(&[1], 10);
7108        copy_propagate(&mut micro, &named, 10, &HashSet::new());
7109        fuse_array_rmw(&mut micro, &named, 10, &HashSet::new());
7110        // The dead recomputed index is gone; the idiom is one ArrRMW.
7111        let rmw = micro.iter().find(|op| matches!(op, MicroOp::ArrRMW { .. }));
7112        match rmw {
7113            Some(&MicroOp::ArrRMW { idx, operand, op, .. }) => {
7114                assert_eq!(op, RmwOp::Add);
7115                assert_eq!(operand, 2, "operand is the deduped `1`");
7116                // idx is whichever surviving slot holds v+1 (the load's index).
7117                assert!(idx == 3, "load+store must share the CSE'd index slot; got {idx}");
7118            }
7119            _ => panic!("CSE+fusion failed to collapse the duplicated-index idiom: {micro:?}"),
7120        }
7121        assert!(
7122            !micro.iter().any(|op| matches!(op, MicroOp::ArrStore { .. })),
7123            "the store must be folded into the ArrRMW"
7124        );
7125    }
7126
7127    fn named8_n(live: &[u16], n: usize) -> Vec<bool> {
7128        let mut v = vec![false; n];
7129        for &s in live {
7130            v[s as usize] = true;
7131        }
7132        v
7133    }
7134
7135    /// FLOAT RMW (nbody `v[i] = v[i] + dx*mag`): an ArrLoad feeding an AddF whose
7136    /// result stores back to the same cell collapses to a float ArrRMW. The
7137    /// operand (the product) stays live; commutative AddF takes `t` on either
7138    /// side, SubF only on the left.
7139    #[test]
7140    fn float_load_addf_store_fuses_to_float_rmw() {
7141        let mut micro = vec![
7142            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 8, len_slot: 9, byte: false, narrow32: false, checked: false },
7143            MicroOp::AddF { dst: 4, lhs: 3, rhs: 2 }, // v[i] + product
7144            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 8, len_slot: 9, byte: false, narrow32: false, checked: false },
7145            MicroOp::Return { src: 0 },
7146        ];
7147        fuse_array_rmw(&mut micro, &named8(&[2, 5]), 10, &HashSet::new());
7148        match micro[0] {
7149            MicroOp::ArrRMW { op: RmwOp::AddF, operand: 2, idx: 5, .. } => {}
7150            ref other => panic!("expected float ArrRMW AddF operand=2, got {other:?}"),
7151        }
7152
7153        // operand - v[i] must NOT fuse (loaded value on the right of SubF).
7154        let mut rev = vec![
7155            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 8, len_slot: 9, byte: false, narrow32: false, checked: true },
7156            MicroOp::SubF { dst: 4, lhs: 2, rhs: 3 },
7157            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 8, len_slot: 9, byte: false, narrow32: false, checked: true },
7158            MicroOp::Return { src: 0 },
7159        ];
7160        fuse_array_rmw(&mut rev, &named8(&[2, 5]), 10, &HashSet::new());
7161        assert!(matches!(rev[0], MicroOp::ArrLoad { .. }), "operand - v[i] must not fuse: {rev:?}");
7162    }
7163
7164    /// FLOAT MULTIPLY-ADD: `MulF(t = a*b); AddF(d = t + c)` with `t` single-use
7165    /// collapses to one FmaF. Commutative — the product may be either AddF
7166    /// operand; the addend `c` stays live.
7167    #[test]
7168    fn mulf_addf_fuses_to_fma() {
7169        for swap in [false, true] {
7170            let add = if swap {
7171                MicroOp::AddF { dst: 4, lhs: 2, rhs: 3 } // c + t
7172            } else {
7173                MicroOp::AddF { dst: 4, lhs: 3, rhs: 2 } // t + c
7174            };
7175            let mut micro = vec![
7176                MicroOp::MulF { dst: 3, lhs: 0, rhs: 1 }, // t = a*b
7177                add,
7178                MicroOp::Return { src: 4 },
7179            ];
7180            fuse_fma(&mut micro, &named8(&[0, 1, 2, 4]), 8, &HashSet::new());
7181            match micro[0] {
7182                MicroOp::FmaF { dst: 4, a: 0, b: 1, c: 2 } => {}
7183                ref other => panic!("expected FmaF(dst4,a0,b1,c2), got {other:?} (swap={swap})"),
7184            }
7185            assert!(!micro.iter().any(|op| matches!(op, MicroOp::AddF { .. })), "AddF folded away");
7186        }
7187    }
7188
7189    /// A multi-use product must NOT fuse (the second reader would lose it), and a
7190    /// pinned operand blocks fusion (mem-form FmaF would need a spill).
7191    #[test]
7192    fn fma_blocks_multiuse_product_and_pinned_operands() {
7193        // product read twice
7194        let mut multi = vec![
7195            MicroOp::MulF { dst: 3, lhs: 0, rhs: 1 },
7196            MicroOp::AddF { dst: 4, lhs: 3, rhs: 2 },
7197            MicroOp::AddF { dst: 5, lhs: 3, rhs: 2 }, // second use of the product
7198            MicroOp::Return { src: 4 },
7199        ];
7200        fuse_fma(&mut multi, &named8(&[0, 1, 2, 4, 5]), 8, &HashSet::new());
7201        assert!(matches!(multi[0], MicroOp::MulF { .. }), "multi-use product must block FMA");
7202
7203        // a pinned operand (slot 0) blocks the spill-free fusion
7204        let mut pinned_in = vec![
7205            MicroOp::MulF { dst: 3, lhs: 0, rhs: 1 },
7206            MicroOp::AddF { dst: 4, lhs: 3, rhs: 2 },
7207            MicroOp::Return { src: 4 },
7208        ];
7209        let pins: HashSet<u16> = [0u16].into_iter().collect();
7210        fuse_fma(&mut pinned_in, &named8(&[0, 1, 2, 4]), 8, &pins);
7211        assert!(matches!(pinned_in[0], MicroOp::MulF { .. }), "pinned operand must block FMA");
7212    }
7213
7214    /// LEVER 1a — a pinned DESTINATION `d` (the accumulator/result XMM pin) must
7215    /// NOT block fusion: `FmaF` is mem-form, so a pinned `d` is spilled/reloaded
7216    /// by `compile_straightline_pinned_float`'s mem-form path exactly as it would
7217    /// be by the unfused `AddF`'s register-form result — fusing adds no extra
7218    /// spill on the INPUTS (which stay frame-resident). The nbody distance-sum's
7219    /// `MulF(t = dz*dz); AddF(d = sum + t)` with `d` float-pinned is the case the
7220    /// guard previously refused. Inputs `a`, `b`, the addend `c` stay frame-resident.
7221    #[test]
7222    fn fma_fuses_with_pinned_destination() {
7223        for swap in [false, true] {
7224            let add = if swap {
7225                MicroOp::AddF { dst: 4, lhs: 2, rhs: 3 } // c + t
7226            } else {
7227                MicroOp::AddF { dst: 4, lhs: 3, rhs: 2 } // t + c
7228            };
7229            let mut micro = vec![
7230                MicroOp::MulF { dst: 3, lhs: 0, rhs: 1 }, // t = a*b (a,b,t frame-resident)
7231                add,                                      // d = t + c, d pinned
7232                MicroOp::Return { src: 4 },
7233            ];
7234            let pins: HashSet<u16> = [4u16].into_iter().collect(); // ONLY the dst is pinned
7235            fuse_fma(&mut micro, &named8(&[0, 1, 2, 4]), 8, &pins);
7236            match micro[0] {
7237                MicroOp::FmaF { dst: 4, a: 0, b: 1, c: 2 } => {}
7238                ref other => panic!(
7239                    "expected FmaF(dst4,a0,b1,c2) with pinned dst, got {other:?} (swap={swap})"
7240                ),
7241            }
7242            assert!(
7243                !micro.iter().any(|op| matches!(op, MicroOp::AddF { .. })),
7244                "AddF must fold into the FmaF even when the dst is pinned"
7245            );
7246        }
7247    }
7248
7249    /// LEVER 1a soundness boundary — a pinned ADDEND `c` (the loop-carried
7250    /// accumulator read on the FmaF's frame-input side, the `s = s + a*b` shape
7251    /// where `c == d == s`) must STILL block: spilling the pinned accumulator
7252    /// around the mem-form FmaF would cost more pieces than the register-form
7253    /// `AddF` it replaces. Only a pinned `d` that is NOT also an input is relaxed.
7254    #[test]
7255    fn fma_pinned_addend_still_blocks() {
7256        // accumulator shape: t = a*b ; s = s + t  (c == d == slot 4, pinned)
7257        let mut micro = vec![
7258            MicroOp::MulF { dst: 3, lhs: 0, rhs: 1 },
7259            MicroOp::AddF { dst: 4, lhs: 4, rhs: 3 }, // s = s + t, s is both c and d
7260            MicroOp::Return { src: 4 },
7261        ];
7262        let pins: HashSet<u16> = [4u16].into_iter().collect();
7263        fuse_fma(&mut micro, &named8(&[0, 1, 4]), 8, &pins);
7264        assert!(
7265            matches!(micro[0], MicroOp::MulF { .. }),
7266            "a pinned addend (loop-carried accumulator) must block FMA"
7267        );
7268    }
7269
7270    /// Jump targets AFTER the idiom are remapped down by the two removed ops.
7271    #[test]
7272    fn fusion_remaps_later_jump_targets() {
7273        let mut micro = vec![
7274            MicroOp::ArrLoad { dst: 3, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7275            MicroOp::Add { dst: 4, lhs: 3, rhs: 2 },
7276            MicroOp::ArrStore { src: 4, idx: 5, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
7277            MicroOp::JumpIfTrue { cond: 2, target: 5 }, // -> the Return
7278            MicroOp::Add { dst: 1, lhs: 1, rhs: 2 },
7279            MicroOp::Return { src: 1 },
7280        ];
7281        fuse_array_rmw(&mut micro, &named8(&[1, 2, 5]), 8, &HashSet::new());
7282        // [ArrRMW, JumpIfTrue->3, Add, Return] — the target slid 5 -> 3.
7283        assert!(matches!(micro[0], MicroOp::ArrRMW { .. }));
7284        match micro[1] {
7285            MicroOp::JumpIfTrue { target, .. } => assert_eq!(target, 3, "target must remap 5->3"),
7286            ref other => panic!("expected JumpIfTrue, got {other:?}"),
7287        }
7288        assert!(matches!(micro[3], MicroOp::Return { .. }));
7289    }
7290}
7291
7292#[cfg(test)]
7293mod fuse_ld2_tests {
7294    use super::*;
7295    use logicaffeine_forge::jit::IOp;
7296    use std::collections::HashSet;
7297
7298    fn named_n(live: &[u16], n: usize) -> Vec<bool> {
7299        let mut v = vec![false; n];
7300        for &s in live {
7301            v[s as usize] = true;
7302        }
7303        v
7304    }
7305
7306    /// THE MATMUL DOT-PRODUCT SHAPE: `ArrLoad(t1 = a[ix]); ArrLoad(t2 = b[jx]);
7307    /// Mul(d = t1 * t2)` — the two elements come from TWO DISTINCT pinned int
7308    /// buffers (a at ptr 10/len 11, b at ptr 12/len 13). The two single-use
7309    /// scratch loads collapse into ONE fused ArrLoad2 (two loads + multiply,
7310    /// three dispatches → one), so the loaded i64s never round-trip the frame.
7311    #[test]
7312    fn two_buffer_load_mul_fuses_to_arrld2() {
7313        let mut micro = vec![
7314            MicroOp::ArrLoad { dst: 3, idx: 1, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7315            MicroOp::ArrLoad { dst: 4, idx: 2, ptr_slot: 12, len_slot: 13, byte: false, narrow32: false, checked: true },
7316            MicroOp::Mul { dst: 5, lhs: 3, rhs: 4 },
7317            MicroOp::Return { src: 5 },
7318        ];
7319        fuse_array_ld2(&mut micro, &named_n(&[1, 2, 5], 14), 14, &HashSet::new());
7320        assert_eq!(micro.len(), 2, "the 3-op dot-product idiom must collapse to 1 op + Return");
7321        match micro[0] {
7322            MicroOp::ArrLoad2 { dst, i, j, ptr_a, len_a, ptr_b, len_b, op, checked } => {
7323                assert_eq!((dst, i, j), (5, 1, 2));
7324                assert_eq!((ptr_a, len_a, ptr_b, len_b), (10, 11, 12, 13));
7325                assert_eq!(op, IOp::Mul);
7326                assert!(checked);
7327            }
7328            ref other => panic!("expected ArrLoad2, got {other:?}"),
7329        }
7330    }
7331
7332    /// Add commutes — the loads may feed the binop in either order, and the
7333    /// fused op records the index order that reproduces `a[i] OP b[j]`.
7334    #[test]
7335    fn add_fuses_in_either_operand_order() {
7336        for swap in [false, true] {
7337            let add = if swap {
7338                MicroOp::Add { dst: 5, lhs: 4, rhs: 3 } // b[j] + a[i]
7339            } else {
7340                MicroOp::Add { dst: 5, lhs: 3, rhs: 4 } // a[i] + b[j]
7341            };
7342            let mut micro = vec![
7343                MicroOp::ArrLoad { dst: 3, idx: 1, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: false },
7344                MicroOp::ArrLoad { dst: 4, idx: 2, ptr_slot: 12, len_slot: 13, byte: false, narrow32: false, checked: false },
7345                add,
7346                MicroOp::Return { src: 5 },
7347            ];
7348            fuse_array_ld2(&mut micro, &named_n(&[1, 2, 5], 14), 14, &HashSet::new());
7349            match micro[0] {
7350                MicroOp::ArrLoad2 { op: IOp::Add, checked: false, .. } => {}
7351                ref other => panic!("expected unchecked ArrLoad2 Add (swap={swap}), got {other:?}"),
7352            }
7353        }
7354    }
7355
7356    /// Subtraction is non-commutative, but the fusion is anchored on the binop
7357    /// and tracks WHICH load feeds the left vs. right operand, so it fuses BOTH
7358    /// orderings correctly by recording the left load as `a`/`i` and the right
7359    /// load as `b`/`j` (the stencil computes `a[i] - b[j]`).
7360    #[test]
7361    fn sub_fuses_in_binop_order() {
7362        // `X[1] - Y[2]`: left load = X (ptr 10), right load = Y (ptr 12).
7363        let mut fwd = vec![
7364            MicroOp::ArrLoad { dst: 3, idx: 1, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7365            MicroOp::ArrLoad { dst: 4, idx: 2, ptr_slot: 12, len_slot: 13, byte: false, narrow32: false, checked: true },
7366            MicroOp::Sub { dst: 5, lhs: 3, rhs: 4 }, // X[1] - Y[2]
7367            MicroOp::Return { src: 5 },
7368        ];
7369        fuse_array_ld2(&mut fwd, &named_n(&[1, 2, 5], 14), 14, &HashSet::new());
7370        match fwd[0] {
7371            MicroOp::ArrLoad2 { op: IOp::Sub, i: 1, j: 2, ptr_a: 10, ptr_b: 12, .. } => {}
7372            ref other => panic!("expected ArrLoad2 Sub (X-Y): i=1 j=2 ptr_a=10 ptr_b=12, got {other:?}"),
7373        }
7374
7375        // `Y[2] - X[1]`: left load = Y (ptr 12), right load = X (ptr 10). The
7376        // fused op must SWAP which buffer is `a`/`b` so it still computes Y - X.
7377        let mut rev = vec![
7378            MicroOp::ArrLoad { dst: 3, idx: 1, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7379            MicroOp::ArrLoad { dst: 4, idx: 2, ptr_slot: 12, len_slot: 13, byte: false, narrow32: false, checked: true },
7380            MicroOp::Sub { dst: 5, lhs: 4, rhs: 3 }, // Y[2] - X[1]
7381            MicroOp::Return { src: 5 },
7382        ];
7383        fuse_array_ld2(&mut rev, &named_n(&[1, 2, 5], 14), 14, &HashSet::new());
7384        match rev[0] {
7385            MicroOp::ArrLoad2 { op: IOp::Sub, i: 2, j: 1, ptr_a: 12, ptr_b: 10, .. } => {}
7386            ref other => panic!("expected ArrLoad2 Sub (Y-X): i=2 j=1 ptr_a=12 ptr_b=10, got {other:?}"),
7387        }
7388    }
7389
7390    /// A byte (Bool) buffer is not an 8-byte int load — the fused int stencil
7391    /// reads raw i64s, so a byte load must never fuse.
7392    #[test]
7393    fn byte_buffer_blocks_fusion() {
7394        let mut micro = vec![
7395            MicroOp::ArrLoad { dst: 3, idx: 1, ptr_slot: 10, len_slot: 11, byte: true, narrow32: false, checked: true },
7396            MicroOp::ArrLoad { dst: 4, idx: 2, ptr_slot: 12, len_slot: 13, byte: false, narrow32: false, checked: true },
7397            MicroOp::Mul { dst: 5, lhs: 3, rhs: 4 },
7398            MicroOp::Return { src: 5 },
7399        ];
7400        fuse_array_ld2(&mut micro, &named_n(&[1, 2, 5], 14), 14, &HashSet::new());
7401        assert!(matches!(micro[0], MicroOp::ArrLoad { byte: true, .. }), "byte load must block fusion");
7402    }
7403
7404    /// A multi-use loaded value must NOT be folded away (a later reader would
7405    /// lose it), and a jump landing on the second load or the binop blocks it.
7406    #[test]
7407    fn multiuse_load_and_inner_jump_target_block_fusion() {
7408        let mut multi = vec![
7409            MicroOp::ArrLoad { dst: 3, idx: 1, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7410            MicroOp::ArrLoad { dst: 4, idx: 2, ptr_slot: 12, len_slot: 13, byte: false, narrow32: false, checked: true },
7411            MicroOp::Mul { dst: 5, lhs: 3, rhs: 4 },
7412            MicroOp::Move { dst: 6, src: 3 }, // second use of a[i]
7413            MicroOp::Return { src: 5 },
7414        ];
7415        fuse_array_ld2(&mut multi, &named_n(&[1, 2, 5, 6], 14), 14, &HashSet::new());
7416        assert!(matches!(multi[0], MicroOp::ArrLoad { .. }), "multi-use load must block fusion");
7417
7418        let mut jt = vec![
7419            MicroOp::ArrLoad { dst: 3, idx: 1, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7420            MicroOp::ArrLoad { dst: 4, idx: 2, ptr_slot: 12, len_slot: 13, byte: false, narrow32: false, checked: true },
7421            MicroOp::Mul { dst: 5, lhs: 3, rhs: 4 },
7422            MicroOp::JumpIfTrue { cond: 0, target: 1 }, // lands on the 2nd load
7423            MicroOp::Return { src: 5 },
7424        ];
7425        fuse_array_ld2(&mut jt, &named_n(&[0, 1, 2, 5], 14), 14, &HashSet::new());
7426        assert!(matches!(jt[0], MicroOp::ArrLoad { .. }), "inner jump target must block fusion");
7427    }
7428}
7429
7430#[cfg(test)]
7431mod fuse_affine_tests {
7432    use super::*;
7433    use logicaffeine_forge::jit::AffOp;
7434    use std::collections::HashSet;
7435
7436    // slots 0..n are registers; named marks the live ones. The const-holding
7437    // slot is left live (it is reused), the index temps are scratch (un-named).
7438    fn named_n(live: &[u16], n: usize) -> Vec<bool> {
7439        let mut v = vec![false; n];
7440        for &s in live {
7441            v[s as usize] = true;
7442        }
7443        v
7444    }
7445
7446    /// THE `w + 1` SHAPE — `LoadConst(kc = 1); Add(idx = w + kc); ArrLoad(arr[idx])`.
7447    /// The index `idx` is single-use scratch, the const slot is reused; folds to
7448    /// one `ArrLoadAffine{op: None, a: w, const_offset: 1}` and the `Add` vanishes
7449    /// while the `LoadConst` survives.
7450    #[test]
7451    fn slot_plus_const_folds_to_affine_none() {
7452        // slot 4 = w (live index var), 8 = kc (live const), 5 = idx (scratch),
7453        // 6 = loaded value, 10/11 = ptr/len.
7454        let mut micro = vec![
7455            MicroOp::LoadConst { dst: 8, value: 1 },
7456            MicroOp::Add { dst: 5, lhs: 4, rhs: 8 },
7457            MicroOp::ArrLoad { dst: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7458            MicroOp::Return { src: 6 },
7459        ];
7460        fuse_index_affine(&mut micro, &named_n(&[4, 8, 6], 12), 12, &HashSet::new());
7461        assert_eq!(micro.len(), 3, "Add folds into the load; LoadConst kept");
7462        match micro[1] {
7463            MicroOp::ArrLoadAffine { dst, a, op, const_offset, ptr_slot, len_slot, checked, .. } => {
7464                assert_eq!((dst, a, op, const_offset), (6, 4, AffOp::None, 1));
7465                assert_eq!((ptr_slot, len_slot, checked), (10, 11, true));
7466            }
7467            ref other => panic!("expected ArrLoadAffine None, got {other:?}"),
7468        }
7469        assert!(matches!(micro[0], MicroOp::LoadConst { dst: 8, value: 1 }), "shared const kept");
7470    }
7471
7472    /// THE `w - wi + 1` SHAPE — `Sub(t = w - wi); LoadConst(kc = 1); Add(idx = t + kc);
7473    /// ArrLoad(arr[idx])`. Both `t` and `idx` are single-use scratch; folds to one
7474    /// `ArrLoadAffine{op: Sub, a: w, b: wi, const_offset: 1}` (the inner Sub AND the
7475    /// trailing Add vanish, the LoadConst survives).
7476    #[test]
7477    fn sub_plus_const_folds_to_affine_sub() {
7478        // 4 = w, 3 = wi, 8 = kc (live), 7 = t (scratch), 5 = idx (scratch).
7479        let mut micro = vec![
7480            MicroOp::Sub { dst: 7, lhs: 4, rhs: 3 },
7481            MicroOp::LoadConst { dst: 8, value: 1 },
7482            MicroOp::Add { dst: 5, lhs: 7, rhs: 8 },
7483            MicroOp::ArrLoad { dst: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: false },
7484            MicroOp::Return { src: 6 },
7485        ];
7486        fuse_index_affine(&mut micro, &named_n(&[4, 3, 8, 6], 12), 12, &HashSet::new());
7487        assert_eq!(micro.len(), 3, "Sub + Add fold into the load; LoadConst kept");
7488        match micro[1] {
7489            MicroOp::ArrLoadAffine { dst, a, op, b, const_offset, checked, .. } => {
7490                assert_eq!((dst, a, op, b, const_offset, checked), (6, 4, AffOp::Sub, 3, 1, false));
7491            }
7492            ref other => panic!("expected ArrLoadAffine Sub, got {other:?}"),
7493        }
7494    }
7495
7496    /// THE `i*n + j + 1` MATMUL READ SHAPE — `Mul(t1 = i*n)` is MULTI-USE (the
7497    /// matmul reuses `i*n`), so it must NOT be removed, but the trailing
7498    /// `Add(t2 = t1 + j); Add(idx = t2 + kc); ArrLoad` still folds: the inner
7499    /// add `t2` is single-use scratch over the slot `t1`, giving
7500    /// `ArrLoadAffine{op: Add, a: t1, b: j, const_offset: 1}`.
7501    #[test]
7502    fn matmul_read_folds_keeping_shared_mul() {
7503        // 6 = i, 1 = n, 14 = j, 8 = kc (live), 18 = t1 = i*n (MULTI-USE: read by
7504        // the inner add AND a later op), 17 = t2 (scratch), 16 = idx (scratch).
7505        let mut micro = vec![
7506            MicroOp::Mul { dst: 18, lhs: 6, rhs: 1 },        // i*n, reused
7507            MicroOp::Add { dst: 17, lhs: 18, rhs: 14 },      // i*n + j  (scratch)
7508            MicroOp::LoadConst { dst: 8, value: 1 },
7509            MicroOp::Add { dst: 16, lhs: 17, rhs: 8 },       // + 1      (scratch)
7510            MicroOp::ArrLoad { dst: 23, idx: 16, ptr_slot: 40, len_slot: 41, byte: false, narrow32: false, checked: true },
7511            MicroOp::Move { dst: 99, src: 18 },              // second use of i*n
7512            MicroOp::Return { src: 23 },
7513        ];
7514        fuse_index_affine(&mut micro, &named_n(&[6, 1, 14, 8, 23, 99], 100), 100, &HashSet::new());
7515        // The two inner Adds collapse into the load; the Mul and LoadConst stay.
7516        match micro.iter().find(|o| matches!(o, MicroOp::ArrLoadAffine { .. })) {
7517            Some(MicroOp::ArrLoadAffine { dst, a, op, b, const_offset, .. }) => {
7518                assert_eq!((*dst, *a, *op, *b, *const_offset), (23, 18, AffOp::Add, 14, 1));
7519            }
7520            other => panic!("expected ArrLoadAffine, got {other:?}"),
7521        }
7522        assert!(micro.iter().any(|o| matches!(o, MicroOp::Mul { dst: 18, .. })), "shared i*n Mul kept");
7523    }
7524
7525    /// THE BARE `a + b` SHAPE (no const) — `Add(idx = a + b); ArrLoad` folds to
7526    /// `ArrLoadAffine{op: Add, const_offset: 0}`.
7527    #[test]
7528    fn bare_two_slot_add_folds_with_zero_offset() {
7529        let mut micro = vec![
7530            MicroOp::Add { dst: 5, lhs: 4, rhs: 3 },
7531            MicroOp::ArrLoad { dst: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7532            MicroOp::Return { src: 6 },
7533        ];
7534        fuse_index_affine(&mut micro, &named_n(&[4, 3, 6], 12), 12, &HashSet::new());
7535        assert_eq!(micro.len(), 2, "the Add folds into the load");
7536        match micro[0] {
7537            MicroOp::ArrLoadAffine { a, op, b, const_offset, .. } => {
7538                assert_eq!((a, op, b, const_offset), (4, AffOp::Add, 3, 0));
7539            }
7540            ref other => panic!("expected ArrLoadAffine Add, got {other:?}"),
7541        }
7542    }
7543
7544    /// A MULTI-USE INDEX (the slot is read by a store too, as matmul's `c[idx]`)
7545    /// must NOT fold — folding would drop the index the store still needs.
7546    #[test]
7547    fn multiuse_index_blocks_fusion() {
7548        let mut micro = vec![
7549            MicroOp::LoadConst { dst: 8, value: 1 },
7550            MicroOp::Add { dst: 5, lhs: 4, rhs: 8 },
7551            MicroOp::ArrLoad { dst: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7552            MicroOp::ArrStore { src: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7553            MicroOp::Return { src: 6 },
7554        ];
7555        fuse_index_affine(&mut micro, &named_n(&[4, 8, 6], 12), 12, &HashSet::new());
7556        assert!(
7557            matches!(micro[2], MicroOp::ArrLoad { .. }),
7558            "an index reused by a store must block the fold: {micro:?}"
7559        );
7560    }
7561
7562    /// A BYTE (Bool) load never folds — the affine stencils read raw i64s.
7563    #[test]
7564    fn byte_load_blocks_fusion() {
7565        let mut micro = vec![
7566            MicroOp::LoadConst { dst: 8, value: 1 },
7567            MicroOp::Add { dst: 5, lhs: 4, rhs: 8 },
7568            MicroOp::ArrLoad { dst: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: true, narrow32: false, checked: true },
7569            MicroOp::Return { src: 6 },
7570        ];
7571        fuse_index_affine(&mut micro, &named_n(&[4, 8, 6], 12), 12, &HashSet::new());
7572        assert!(matches!(micro[2], MicroOp::ArrLoad { byte: true, .. }), "byte load must not fold");
7573    }
7574
7575    /// A PINNED index temp must not fold (the fused op reads it from the frame;
7576    /// folding past a pin would lose the pinned value's settling).
7577    #[test]
7578    fn pinned_index_temp_blocks_fusion() {
7579        let mut micro = vec![
7580            MicroOp::Add { dst: 5, lhs: 4, rhs: 3 },
7581            MicroOp::ArrLoad { dst: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7582            MicroOp::Return { src: 6 },
7583        ];
7584        let mut pins = HashSet::new();
7585        pins.insert(5u16);
7586        fuse_index_affine(&mut micro, &named_n(&[4, 3, 6], 12), 12, &pins);
7587        assert!(matches!(micro[0], MicroOp::Add { .. }), "a pinned index must block the fold");
7588    }
7589
7590    /// A jump landing on the load (or between the index op and the load) must
7591    /// block the fold — an entry there would skip the index computation.
7592    #[test]
7593    fn jump_target_on_load_blocks_fusion() {
7594        let mut micro = vec![
7595            MicroOp::Add { dst: 5, lhs: 4, rhs: 3 },
7596            MicroOp::ArrLoad { dst: 6, idx: 5, ptr_slot: 10, len_slot: 11, byte: false, narrow32: false, checked: true },
7597            MicroOp::JumpIfTrue { cond: 4, target: 1 }, // lands on the load
7598            MicroOp::Return { src: 6 },
7599        ];
7600        fuse_index_affine(&mut micro, &named_n(&[4, 3, 6], 12), 12, &HashSet::new());
7601        assert!(matches!(micro[0], MicroOp::Add { .. }), "jump onto the load blocks the fold");
7602    }
7603
7604    /// COND-SWAP WITH A COMPUTED SECOND INDEX (Priority 2) — the sort inner-loop
7605    /// compare-and-swap idiom `ArrLoad(a, i1); <index arith for i2>; ArrLoad(b, i2);
7606    /// Branch(cmp, a, b); ArrStore(i1, b); ArrStore(i2, a)` must still fuse into one
7607    /// `ArrCondSwap` when `Mul`/`Add`/`LoadConst` (the `2*root+2`-style index
7608    /// computation of the SECOND access) sit BETWEEN the two loads. The
7609    /// between-ops are pure and redefine neither the first loaded value, the
7610    /// first index, nor the pointer/length, so they do not block the fusion.
7611    #[test]
7612    fn cond_swap_tolerates_computed_index_between_loads() {
7613        // i1 = slot 5 (live), root = slot 4 (live). i2 = 2*root + 2 computed
7614        // between the loads (slots 8 = kc2, 7 = 2*root, 6 = i2; all scratch).
7615        // a = slot 9, b = slot 10 (the two loaded values, dead after the swap).
7616        let mut micro = vec![
7617            MicroOp::ArrLoad { dst: 9, idx: 5, ptr_slot: 20, len_slot: 21, byte: false, narrow32: false, checked: true },
7618            MicroOp::LoadConst { dst: 8, value: 2 },
7619            MicroOp::Mul { dst: 7, lhs: 4, rhs: 8 },     // 2*root
7620            MicroOp::Add { dst: 6, lhs: 7, rhs: 8 },     // 2*root + 2  (the i2 index)
7621            MicroOp::ArrLoad { dst: 10, idx: 6, ptr_slot: 20, len_slot: 21, byte: false, narrow32: false, checked: true },
7622            MicroOp::Branch { cmp: Cmp::Lt, lhs: 9, rhs: 10, target: 8 },
7623            MicroOp::ArrStore { src: 10, idx: 5, ptr_slot: 20, len_slot: 21, byte: false, narrow32: false, checked: true },
7624            MicroOp::ArrStore { src: 9, idx: 6, ptr_slot: 20, len_slot: 21, byte: false, narrow32: false, checked: true },
7625            MicroOp::Return { src: 0 },
7626        ];
7627        // gap from load1 to load2 is 3 (LoadConst, Mul, Add) — raise the search
7628        // window by running with the same named/pin config the pipeline uses.
7629        fuse_cond_swap(&mut micro, &named_n(&[4, 5], 22), 22, &HashSet::new());
7630        assert!(
7631            micro.iter().any(|o| matches!(o, MicroOp::ArrCondSwap { idx1: 5, idx2: 6, .. })),
7632            "the compare-and-swap must fuse despite the 2*root+2 index arith between \
7633             the loads: {micro:?}"
7634        );
7635    }
7636
7637    /// EVAL PARITY — `AffOp::eval` reproduces the kernel's wrapping i64 index
7638    /// arithmetic exactly (the differential gate's bit-for-bit contract).
7639    #[test]
7640    fn affop_eval_matches_wrapping_kernel_semantics() {
7641        assert_eq!(AffOp::None.eval(7, 999, 1), 8);
7642        assert_eq!(AffOp::Add.eval(40, 3, 1), 44);
7643        assert_eq!(AffOp::Sub.eval(10, 4, 1), 7);
7644        assert_eq!(AffOp::Mul.eval(6, 7, 1), 43);
7645        // Wrapping at the i64 edges, identical to the kernel.
7646        assert_eq!(AffOp::Add.eval(i64::MAX, 1, 0), i64::MAX.wrapping_add(1));
7647        assert_eq!(AffOp::Mul.eval(i64::MIN, -1, 0), i64::MIN.wrapping_mul(-1));
7648        assert_eq!(AffOp::Sub.eval(i64::MIN, 1, 0), i64::MIN.wrapping_sub(1));
7649    }
7650}
7651
7652#[cfg(test)]
7653mod bug002_hazard_tests {
7654    use super::*;
7655    use logicaffeine_compile::vm::Op;
7656
7657    fn load(dst: u16, idx: u16) -> Op {
7658        Op::IndexUnchecked { dst, collection: 7, index: idx }
7659    }
7660    fn store(idx: u16, value: u16) -> Op {
7661        Op::SetIndex { collection: 7, index: idx, value }
7662    }
7663
7664    #[test]
7665    fn detects_crossbranch_array_value_store() {
7666        // load a[j]->v ; branch ; store a[i] = v  (v survives the branch) -> HAZARD
7667        let ops = vec![
7668            load(13, 11),
7669            Op::Move { dst: 14, src: 13 },
7670            Op::JumpIfFalse { cond: 15, target: 0 },
7671            Op::Index { dst: 16, collection: 7, index: 9 },
7672            store(9, 14),
7673            store(11, 16),
7674        ];
7675        assert!(has_crossbranch_array_value_store_hazard(&ops));
7676    }
7677
7678    #[test]
7679    fn safe_when_load_is_inside_branch() {
7680        // branch ; load a[j]->v ; store a[i] = v  (no branch between load and store) -> SAFE
7681        let ops = vec![
7682            Op::JumpIfFalse { cond: 15, target: 0 },
7683            Op::Index { dst: 16, collection: 7, index: 9 },
7684            load(17, 11),
7685            store(9, 17),
7686            store(11, 16),
7687        ];
7688        assert!(!has_crossbranch_array_value_store_hazard(&ops));
7689    }
7690}