Skip to main content

sv_sim_sim/state/
accessor_impl.rs

1//! Task 9 — the accessor ABI implementation (`sv_get`/`sv_set` `_scalar`/`_wide`)
2//! and `SignalAccess` (ledger ST-013, ST-020, ST-021, ST-022, ST-023).
3//!
4//! Realizes the plan header's "what `SimHandle` points to" design decision:
5//! `SimHandle.0` *is* a `Box<InstanceHandle>::into_raw`-leaked pointer for
6//! this wave (`InstanceHandle::into_sim_handle`/`from_sim_handle` below).
7//! `sv_get_scalar`/`sv_set_scalar` resolve `absolute_word = handle.base
8//! .inline_base + layout.slots[slot.0].offset.word_offset` and read/write
9//! `arena.inline[absolute_word]` directly; `sv_get_wide`/`sv_set_wide` do the
10//! wide-plane analogue against `handle.base.wide_base`.
11//!
12//! `sv_set_scalar`/`sv_set_wide` route through `write::commit_write_at` — the
13//! exact primitive `StateEngine::commit_write` also calls (see
14//! `state::write`'s module doc comment for the Task 9 retrofit that makes
15//! this reachable with no `&mut StateEngine` in scope) — so VC detection is
16//! never bypassed by the ABI entry point and there is exactly one write path,
17//! never a second, ad hoc one.
18//!
19//! Task 10 — accessor discipline, the reference API as real non-JIT impl,
20//! single-`SimHandle` reachability (ledger ST-026, ST-027, ST-029, ST-152).
21//!
22//! **ST-029:** every fn in this module (`sv_get_scalar`/`sv_set_scalar`/
23//! `sv_get_wide`/`sv_set_wide` above, `SignalAccess::read`/`write`/`subscribe`
24//! below) is a plain Rust/`extern "C"` function, callable with nothing more
25//! than an `InstanceHandle`/`StateCellId` in hand — no `ProcessFn`/
26//! `CompiledKernel` invocation is required to reach any of them. This is the
27//! structural property that makes them usable by `$display`/VPI/the debugger
28//! once those crates exist: this crate's reference read/write API *is* the
29//! actual implementation those non-JIT consumers call into, never a second,
30//! parallel one only the compiled kernel path can reach.
31//!
32//! **ST-152:** the whole of STATE's owned state — the arena, the template
33//! registry, the `cells` table, the VC-tracking state (and, once Tasks 11/16
34//! land, the VC dispatch table and the undo log) — lives inside one
35//! `StateEngine` (`state::instance`), reached from the ABI side exclusively
36//! through the `InstanceHandle` a `SimHandle` points at (which itself, per
37//! this plan's header decision, is reachable only via `SimHandle`); no
38//! function in this crate accepts a second, independent "give me the arena"
39//! pointer parameter. See `StateEngine`'s own doc comment (`state::instance`)
40//! for the field-level half of this guarantee.
41
42use std::ffi::c_void;
43
44use sv_sim_abi::{SimHandle, SlotId, WideView};
45
46use super::instance::{InstanceHandle, StateEngine};
47use super::write::{SlotLocation, commit_write_at, read_slot, resolve_mem_row, write_slot};
48use super::{
49    EdgeKind, FourStateValue, ListenerRole, SignalAccess, SlotClass, StateCellId, ValueChangeSink,
50};
51
52impl InstanceHandle {
53    /// `Box::into_raw` leak — the handle's lifetime is the simulation run's,
54    /// matching how `sim_init`/`sim_final` bracket it per ARCHITECTURE §5.
55    pub fn into_sim_handle(self: Box<Self>) -> SimHandle {
56        SimHandle(Box::into_raw(self) as *mut c_void)
57    }
58
59    /// # Safety
60    /// `h` must be a `SimHandle` previously produced by [`Self::into_sim_handle`]
61    /// on an `InstanceHandle` that has not since been passed to a reclaiming
62    /// function (no such function exists this wave — every handle this crate
63    /// mints lives for the simulation run's duration, per ARCHITECTURE §5, so
64    /// this precondition is trivially satisfied by every caller in this
65    /// wave), and no other thread is concurrently reclaiming/freeing it.
66    pub unsafe fn from_sim_handle<'a>(h: SimHandle) -> &'a InstanceHandle {
67        // SAFETY: forwarded to this function's own `# Safety` section above.
68        unsafe { &*(h.0 as *const InstanceHandle) }
69    }
70}
71
72/// Resolves `slot` against `handle`'s template-relative `StateLayout` and
73/// this instance's own base, producing the already-*absolute* `SlotLocation`
74/// every accessor fn below reads/writes through — the one place this
75/// resolution happens, shared by every `sv_get_*`/`sv_set_*` fn (never
76/// re-derived ad hoc at each call site).
77pub(super) fn slot_location_for(handle: &InstanceHandle, slot: SlotId) -> SlotLocation {
78    let offset = handle.layout().slots[slot.0 as usize].offset;
79    let word_offset = match offset.class {
80        SlotClass::Inline => handle.base.inline_base + offset.word_offset,
81        SlotClass::Wide | SlotClass::Memory => handle.base.wide_base + offset.word_offset,
82        // Review fix (Task 14 r2, blocking finding 2): a real elaborated
83        // signal's `SlotOffset` (from `handle.layout()`) is never
84        // `ForceWide` — see `SlotClass::ForceWide`'s own doc comment.
85        SlotClass::ForceWide => unreachable!(
86            "slot_location_for: no SlotDescriptor/SlotOffset the layout builder \
87             produces is ever SlotClass::ForceWide"
88        ),
89    };
90    SlotLocation {
91        class: offset.class,
92        word_offset,
93        bit_width: offset.bit_width,
94        elem_word_count: offset.elem_word_count,
95        row_stride_words: offset.row_stride_words,
96        num_rows: offset.num_rows,
97    }
98}
99
100/// `slot` of `handle`: its cell, bit width, whether it is wide, and current
101/// value (`force`/`release`, `state::force`).
102///
103/// For a windowed alias (a ref port bound to a select) the state is the
104/// *target's*: its cell, width and value, plus the window's offset in it (a
105/// force of the alias is a force of those bits of the target). `Err` when
106/// the target is an element of a memory, which `force` does not reach.
107pub(crate) fn slot_state(
108    handle: SimHandle,
109    slot: SlotId,
110) -> Result<(StateCellId, u32, bool, FourStateValue, u32), &'static str> {
111    // SAFETY: see `sv_get_scalar`.
112    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
113    let idx = slot.0 as usize;
114    let window = if idx >= this.own_slot_count() {
115        this.foreign_window(idx)
116    } else {
117        None
118    };
119    if let Some(w) = &window
120        && w.row.is_some()
121    {
122        // `real_sv_rt_force`/`release` route these to the row path first.
123        return Err("force/release of a ref port bound to an element of an unpacked array");
124    }
125    let (handle, slot) = resolve_foreign_slot(handle, slot);
126    // SAFETY: see `sv_get_scalar`.
127    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
128    let loc = slot_location_for(handle, slot);
129    let cell = handle.cell_for_slot(slot.0 as usize);
130    let value = super::write::read_slot(handle.arena(), loc);
131    Ok((
132        cell,
133        loc.bit_width,
134        loc.class == SlotClass::Wide,
135        value,
136        window.map_or(0, |w| w.lo),
137    ))
138}
139
140/// The row (and the bit offset in it) of the unpacked-array element a foreign
141/// `slot` of `handle` -- a ref port bound to `arr[i]` (LRM 23.3.3.2) --
142/// stands for; `None` for any other slot. A `force`/`release` of such a slot
143/// is one of that row ([`row_state`] follows the slot to its owner).
144pub(crate) fn slot_row_alias(handle: SimHandle, slot: SlotId) -> Option<(u64, u32)> {
145    // SAFETY: see `sv_get_scalar`.
146    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
147    let idx = slot.0 as usize;
148    if idx < this.own_slot_count() {
149        return None;
150    }
151    let w = this.foreign_window(idx)?;
152    w.row.map(|row| (row, w.lo))
153}
154
155/// The state a `force`/`release` of row `row` of memory `slot` needs: the
156/// memory's cell, the row's word offset (its key in the force table), its
157/// bit width and its value now. A foreign slot is followed to its owner.
158pub(crate) fn row_state(
159    handle: SimHandle,
160    slot: SlotId,
161    row: u64,
162) -> (StateCellId, u32, u32, FourStateValue) {
163    let (handle, slot) = resolve_foreign_slot(handle, slot);
164    // SAFETY: see `sv_get_scalar`.
165    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
166    let loc = resolve_mem_row(slot_location_for(handle, slot), row);
167    let cell = handle.cell_for_slot(slot.0 as usize);
168    let value = super::write::read_slot(handle.arena(), loc);
169    (cell, loc.word_offset, loc.bit_width, value)
170}
171
172/// Copies `src`'s current value into `dst` of the same instance without
173/// reporting a value change: the sampled value functions' hidden P/S cells
174/// (`crate::sva::sampled`) are history, not signals -- no listener waits on
175/// them, no waveform shows them, and their updates must not wake a process.
176/// `src` may be a foreign slot (a hierarchically connected signal); `dst`
177/// is always one of the instance's own cells, of the same class and width
178/// as `src` by construction (`hidden_slots` clones the source's shape).
179pub fn copy_slot_quiet(handle: SimHandle, src: SlotId, dst: SlotId) {
180    // SAFETY: see `sv_get_scalar`.
181    let dst_h = unsafe { InstanceHandle::from_sim_handle(handle) };
182    let idx = src.0 as usize;
183    if idx >= dst_h.own_slot_count()
184        && let Some(window) = dst_h.foreign_window(idx)
185    {
186        let value = read_window(dst_h, idx, window);
187        write_slot(dst_h.arena(), slot_location_for(dst_h, dst), &value);
188        return;
189    }
190    let (src_handle, src) = resolve_foreign_slot(handle, src);
191    let src_h = unsafe { InstanceHandle::from_sim_handle(src_handle) };
192    let value = read_slot(src_h.arena(), slot_location_for(src_h, src));
193    write_slot(dst_h.arena(), slot_location_for(dst_h, dst), &value);
194}
195
196/// Runs `f` on the force table of `handle`'s engine (`state::force`).
197pub(crate) fn with_force_table<R>(
198    handle: SimHandle,
199    f: impl FnOnce(&mut super::force::ForceTable) -> R,
200) -> R {
201    // SAFETY: see `sv_get_scalar`.
202    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
203    f(&mut handle.vc().force.borrow_mut())
204}
205
206/// Narrow (`SlotClass::Inline`) scalar read (ST-020): `sv_sim_abi::accessor`'s
207/// FIXED `val` low 64 bits / `unk` high 64 bits layout, read straight out of
208/// the arena's inline plane at `handle`'s own base-applied word offset.
209pub extern "C" fn sv_get_scalar(handle: SimHandle, slot: SlotId) -> u128 {
210    // SAFETY: see `InstanceHandle::from_sim_handle`'s own `# Safety` section
211    // — this fn's only documented callers (compiled-kernel code, or this
212    // crate's own tests) pass back a `SimHandle` this crate itself minted
213    // via `into_sim_handle`.
214    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
215    let loc = slot_location_for(handle, slot);
216    handle.arena().inline()[loc.word_offset as usize]
217}
218
219/// Narrow scalar write (ST-021): writes `value` and reports whether it
220/// changed by routing through [`commit_write_at`] — the same VC-detection
221/// primitive `StateEngine::commit_write` uses — never a second, parallel
222/// write path. The `extern "C"` signature itself returns `()` (the frozen
223/// ABI type alias, `sv_sim_abi::accessor::SvSetScalarFn`); the change report
224/// lands in the same speculative VC buffer every other `commit_write_at`
225/// caller's changes do, observable via `write::last_vc_record_for` in tests.
226pub extern "C" fn sv_set_scalar(handle: SimHandle, slot: SlotId, value: u128) {
227    // SAFETY: see `sv_get_scalar`.
228    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
229    let loc = slot_location_for(handle, slot);
230    let cell = handle.cell_for_slot(slot.0 as usize);
231    let result = commit_write_at(
232        handle.arena(),
233        handle.vc(),
234        loc,
235        cell,
236        FourStateValue::Narrow(value),
237        &mut (),
238    );
239    handle.vc().views.note_change(cell, None, &result);
240}
241
242/// Wide (`SlotClass::Wide`) non-memory read (ST-022): a non-owning
243/// [`WideView`] over `handle`'s own `aval`/`bval` plane words at `slot`'s
244/// base-applied offset — `word_count` words starting at that offset in each
245/// plane, per `sv_sim_abi::accessor`'s documented shape.
246pub extern "C" fn sv_get_wide(handle: SimHandle, slot: SlotId) -> WideView {
247    // SAFETY: see `sv_get_scalar`.
248    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
249    let loc = slot_location_for(handle, slot);
250    let start = loc.word_offset as usize;
251    // Review fix (Task 9 r3, blocking finding 1): seal the arena's wide
252    // planes *before* handing out pointers into their live storage below.
253    // The returned `WideView`'s `aval`/`bval` pointers are retained by the
254    // caller past this call's return (a non-owning *view*, per `WideView`'s
255    // own doc comment, deliberately aliasing the arena's own live storage,
256    // not a copy) -- unlike `sv_get_scalar`/`sv_set_scalar`/`sv_set_wide`,
257    // none of which ever caches a pointer across calls. Without this seal, a
258    // later `StateEngine::allocate_instance` growing `wide_aval`/`wide_bval`
259    // via `Arena::alloc_wide_words` (a plain growable `Vec<u64>`, not
260    // individually boxed per allocation the way `TemplateRegistry` boxes
261    // each `StateLayout`) could reallocate their backing buffer and dangle
262    // this exact pointer pair -- a genuine use-after-free. `seal_wide_plane`
263    // makes every later nonzero-word `alloc_wide_words` call panic instead
264    // (see `Arena::wide_sealed`'s doc comment), which is what actually makes
265    // the *reallocation* half of the SAFETY argument below true.
266    handle.arena().seal_wide_plane();
267    // Review fix (Task 9 r4, blocking finding): this used to read
268    // `handle.arena().wide_aval_mut()[start..].as_mut_ptr()` -- forming a
269    // fresh `&mut [u64]` reborrow of the *whole* wide plane on every call.
270    // Under Stacked Borrows that reborrow is retagged from scratch each
271    // time, which invalidates any pointer a *previous* call like this one
272    // returned the instant *any* later `wide_aval_mut`/`wide_bval_mut` call
273    // happens anywhere against this `Arena` -- the very next `sv_get_wide`
274    // for a different instance/slot (no write required), or the next
275    // wide-class `commit_write_at` write via `write::write_slot`. Confirmed
276    // by an isolated Miri repro of exactly this pattern (both a later
277    // unrelated wide-class write and a second, read-only-shaped
278    // `sv_get_wide` call with zero intervening writes reproduce Miri's
279    // "tag does not exist in the borrow stack" error against the first
280    // pointer). `seal_wide_plane` above only prevents the Vec from
281    // *reallocating*; it does nothing about this separate, far-more-easily
282    // triggered reborrow-invalidation hazard.
283    //
284    // SAFETY: `start..start+elem_word_count` was reserved for this
285    // instance's slot by `allocate_instance`'s bulk wide-plane allocation
286    // and is not touched by any other instance's own bases (Task 7's
287    // ST-067/ST-136 disjoint-base guarantee), so the pointer arithmetic
288    // below stays within the plane's allocated length. `Arena::wide_aval_ptr`/
289    // `wide_bval_ptr` never form a `&mut [u64]`/`&[u64]` over the plane's
290    // buffer (see their own doc comments for the full argument) -- the
291    // pointers they return keep the buffer's original allocation tag for
292    // the Arena's whole life once sealed, so they stay valid to dereference
293    // no matter how many later `wide_aval_mut`/`wide_bval_mut`-based reads
294    // or writes (from `write::write_slot`, `read_slot`, `rollback::restore`,
295    // or another `sv_get_wide` call) happen against this same plane
296    // afterward: those all reborrow a *child* tag on top of the buffer's
297    // root tag, never invalidating the root tag itself (Miri-verified; see
298    // `sv_get_wide_view_stays_valid_across_a_later_sv_get_wide_and_a_later_
299    // wide_class_write` below, the Miri-reachable regression test for this
300    // exact fix). `seal_wide_plane` just above is what keeps the buffer's
301    // *address* (not merely its tag) stable for the rest of this Arena's
302    // life, which this argument also depends on.
303    let aval = unsafe { handle.arena().wide_aval_ptr().add(start) };
304    let bval = unsafe { handle.arena().wide_bval_ptr().add(start) };
305    WideView {
306        aval,
307        bval,
308        word_count: loc.elem_word_count,
309    }
310}
311
312/// Wide non-memory write (ST-023): copies `value`'s `elem_word_count` words
313/// out of the caller-supplied [`WideView`] into an owned [`FourStateValue::Wide`]
314/// and routes through [`commit_write_at`] — the wide-tier sibling of
315/// `sv_set_scalar`, through the identical shared primitive.
316pub extern "C" fn sv_set_wide(handle: SimHandle, slot: SlotId, value: WideView) {
317    // SAFETY: see `sv_get_scalar`.
318    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
319    let loc = slot_location_for(handle, slot);
320    let cell = handle.cell_for_slot(slot.0 as usize);
321    let n = loc.elem_word_count as usize;
322    // SAFETY: per the accessor-ABI contract (`sv_sim_abi::accessor`), a
323    // legitimate `SvSetWideFn` caller supplies a `WideView` whose `aval`/
324    // `bval` point at (at least) `word_count` contiguous, initialized `u64`s
325    // it owns for at least the duration of this call; this fn's own callers
326    // (this crate's tests) uphold that by construction (`n` here comes from
327    // `handle`'s own slot shape, never from `value.word_count`, so a
328    // mismatched `value` would read past/short of its own buffer -- the
329    // caller's obligation, not something this fn can check from a raw
330    // pointer pair alone).
331    let aval: Vec<u64> = unsafe { std::slice::from_raw_parts(value.aval, n) }.to_vec();
332    let bval: Vec<u64> = unsafe { std::slice::from_raw_parts(value.bval, n) }.to_vec();
333    let result = commit_write_at(
334        handle.arena(),
335        handle.vc(),
336        loc,
337        cell,
338        FourStateValue::Wide { aval, bval },
339        &mut (),
340    );
341    handle.vc().views.note_change(cell, None, &result);
342}
343
344/// Task 12 (ST-024, ST-025, ST-114): memory-word accessors. `slot` addresses
345/// the whole `SlotClass::Memory` array the same way `sv_get_wide`/
346/// `sv_set_wide` address a whole `SlotClass::Wide` signal
347/// ([`slot_location_for`] is shared, unchanged); the one extra step is
348/// [`resolve_mem_row`], applied to that base location with the caller's
349/// *runtime* `row_index`, per state.md §3's pinned `word_offset' =
350/// word_offset + row_stride_words * row_index` formula. Both fns panic
351/// (never silently read/write past the array) if `row_index` is out of
352/// bounds or `slot` does not resolve to a `SlotClass::Memory` location — see
353/// [`resolve_mem_row`]'s own doc comment for the exact panic conditions.
354///
355/// This is the shape a future `sv_rt_readmemh` (RUNTIME's own, not built this
356/// wave) will call once per parsed word: `sv_set_mem_word(handle, slot,
357/// row_index, parsed_word_view)` in a loop, never touching `Arena` directly
358/// (ST-114's literal acceptance text).
359///
360/// **ST-024:** the `sv_get_wide`-shaped read counterpart, through the same
361/// `resolve_mem_row` step and the same live-arena-aliasing `WideView` shape
362/// `sv_get_wide` returns — see that function's own doc comment for the full
363/// seal/pointer-stability argument, which applies identically here (the same
364/// `handle.arena().seal_wide_plane()` call, the same wide planes, just at a
365/// row-resolved offset instead of the slot's own base offset).
366pub extern "C" fn sv_get_mem_word(handle: SimHandle, slot: SlotId, row_index: u64) -> WideView {
367    // SAFETY: see `sv_get_scalar`.
368    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
369    let loc = slot_location_for(handle, slot);
370    let resolved = resolve_mem_row(loc, row_index);
371    let start = resolved.word_offset as usize;
372    // See `sv_get_wide`'s own doc comment for why sealing before handing out
373    // pointers into the live wide-plane storage is required here too.
374    handle.arena().seal_wide_plane();
375    // SAFETY: `start..start+elem_word_count` was reserved for this row within
376    // this instance's memory slot by `allocate_instance`'s bulk wide-plane
377    // allocation (the whole memory's `num_rows * row_stride_words` words are
378    // disjoint from every other instance's own base, and `resolve_mem_row`
379    // asserted `row_index < num_rows` above, so this row's own sub-range
380    // stays within that allocation) — the identical pointer-stability
381    // argument `sv_get_wide` documents applies unchanged, since both read the
382    // same sealed wide planes via the same non-`&mut`-forming raw-pointer
383    // accessors.
384    let aval = unsafe { handle.arena().wide_aval_ptr().add(start) };
385    let bval = unsafe { handle.arena().wide_bval_ptr().add(start) };
386    WideView {
387        aval,
388        bval,
389        word_count: resolved.elem_word_count,
390    }
391}
392
393/// **ST-025:** the `sv_set_wide`-shaped write counterpart, through the same
394/// `resolve_mem_row` step and the same `commit_write_at` primitive every
395/// other write in this crate goes through — never a second, ad hoc write
396/// path. `cell` is this slot's one whole-memory `StateCellId` (the same
397/// `handle.cell_for_slot(slot.0)` `sv_set_wide` uses; a `SlotClass::Memory`
398/// slot mints exactly one cell for the whole array, not one per row — see
399/// `StateEngine::allocate_instance`'s own per-slot loop), so a VC record from
400/// this fn reports "this memory changed", identifying the row via `at`/the
401/// caller's own `row_index`, exactly as `sv_set_wide` already does for a
402/// whole non-memory wide signal.
403pub extern "C" fn sv_set_mem_word(
404    handle: SimHandle,
405    slot: SlotId,
406    row_index: u64,
407    value: WideView,
408) {
409    let _ = set_mem_word_reporting_change(handle, slot, row_index, value);
410}
411
412/// [`sv_set_mem_word`]'s body, returning the memory's whole-array cell and
413/// whether the row's stored value actually changed -- what a
414/// scheduler-aware caller (`sched::kernel_adapter::real_sv_set_mem_word`)
415/// needs to wake processes sensitive to the memory.
416pub fn set_mem_word_reporting_change(
417    handle: SimHandle,
418    slot: SlotId,
419    row_index: u64,
420    value: WideView,
421) -> (StateCellId, bool) {
422    // SAFETY: see `sv_get_scalar`.
423    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
424    let loc = slot_location_for(handle, slot);
425    let resolved = resolve_mem_row(loc, row_index);
426    let cell = handle.cell_for_slot(slot.0 as usize);
427    let n = resolved.elem_word_count as usize;
428    // SAFETY: per the accessor-ABI contract (`sv_sim_abi::accessor`), a
429    // legitimate `SvSetMemWordFn` caller supplies a `WideView` whose `aval`/
430    // `bval` point at (at least) one row's own `elem_word_count` contiguous,
431    // initialized `u64`s it owns for at least the duration of this call --
432    // the same obligation `sv_set_wide` documents, `n` here likewise coming
433    // from `resolved`'s own row shape, never from `value.word_count`.
434    let aval: Vec<u64> = unsafe { std::slice::from_raw_parts(value.aval, n) }.to_vec();
435    let bval: Vec<u64> = unsafe { std::slice::from_raw_parts(value.bval, n) }.to_vec();
436    let result = commit_write_at(
437        handle.arena(),
438        handle.vc(),
439        resolved,
440        cell,
441        FourStateValue::Wide { aval, bval },
442        &mut (),
443    );
444    handle
445        .vc()
446        .views
447        .note_change(cell, Some(row_index), &result);
448    (cell, result.changed)
449}
450
451/// Task F3 (`docs/superpowers/plans/2026-09-27-wave-A2-slice-finish.md`,
452/// CG-125/NL-035): resolves `slot` (the same `SlotId` `sv_get_scalar`/
453/// `sv_set_scalar` already address by) to the absolute [`StateCellId`] a
454/// synthetic inter-instance port-connection process needs for
455/// [`crate::sched::SchedulerOps::commit_blocking_write`]/
456/// [`crate::sched::StaticSensitivity`] (registering a wake on the source
457/// side, writing through the canonical commit path on the destination side).
458/// Every other `StateCellId` in this crate is minted directly by
459/// `StateEngine::alloc_cell`/`allocate_instance`'s own per-slot loop
460/// (`InstanceHandle::cell_for_slot`, `pub(crate)`); this is the one place a
461/// caller *outside* this crate (`sv-sim-bin`'s hierarchy pipeline, which
462/// holds a `SimHandle`+`SlotId` pair from its own signal-slot bookkeeping but
463/// never runs `allocate_instance`'s loop itself) needs to look one back up.
464/// Gives `handle`'s instance its foreign slots: slot `own + k` (where `own`
465/// is the instance's own slot count, returned) names `targets[k]`, another
466/// instance's own slot. A target that is itself a foreign slot is followed
467/// to its owner.
468pub fn set_foreign_slots(handle: SimHandle, targets: &[(SimHandle, SlotId)]) -> u32 {
469    // SAFETY: see `sv_get_scalar`.
470    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
471    let resolved = targets
472        .iter()
473        .map(|&(h, slot)| resolve_target(h, slot))
474        .collect::<Vec<_>>();
475    let views = vec![None; resolved.len()];
476    this.set_foreign(resolved, views);
477    this.own_slot_count() as u32
478}
479
480/// The own slot a foreign-slot target names (a target that is itself a
481/// foreign slot is followed to its owner). A target that is a *windowed*
482/// foreign slot cannot be followed -- the caller composes the windows first.
483fn resolve_target(h: SimHandle, slot: SlotId) -> (*const InstanceHandle, usize) {
484    // SAFETY: see `sv_get_scalar`.
485    let target = unsafe { InstanceHandle::from_sim_handle(h) };
486    let idx = slot.0 as usize;
487    assert!(
488        idx < target.own_slot_count() || target.foreign_window(idx).is_none(),
489        "a foreign slot cannot name a windowed foreign slot; compose the windows first"
490    );
491    let (owner, idx) = target.resolve_slot(idx);
492    (owner as *const InstanceHandle, idx)
493}
494
495/// [`set_foreign_slots`] where a target may be a window of its slot -- a ref
496/// port bound to a select (IEEE 1800-2023 23.3.3.2, 13.5.2): such a foreign
497/// slot is a *view* of the target (`state::alias`) with its own cell, so a
498/// wait on it wakes only when the window changes.
499pub fn set_foreign_slots_windowed(
500    engine: &mut StateEngine,
501    handle: SimHandle,
502    targets: &[(SimHandle, SlotId, Option<super::AliasWindow>)],
503) -> u32 {
504    // SAFETY: see `sv_get_scalar`.
505    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
506    let mut resolved = Vec::with_capacity(targets.len());
507    let mut views = Vec::with_capacity(targets.len());
508    for &(h, slot, window) in targets {
509        let (owner, idx) = resolve_target(h, slot);
510        views.push(window.map(|w| {
511            // SAFETY: `owner` is a live handle (`resolve_target`).
512            let root = unsafe { &*owner }.cell_for_slot(idx);
513            (w, engine.alloc_view_cell(root, w))
514        }));
515        resolved.push((owner, idx));
516    }
517    this.set_foreign(resolved, views);
518    this.own_slot_count() as u32
519}
520
521/// `(handle, slot)` itself for one of the instance's own slots; for a
522/// foreign slot (see [`set_foreign_slots`]), the instance and slot it
523/// names. Every production entry point that reads or writes a slot's
524/// storage for a compiled kernel resolves through this first
525/// (`real_sv_get_*`, `real_sv_set_mem_word`, force); a cell lookup needs no
526/// resolving (a foreign slot's cell is in the instance's own cell table).
527#[inline]
528pub fn resolve_foreign_slot(handle: SimHandle, slot: SlotId) -> (SimHandle, SlotId) {
529    // SAFETY: see `sv_get_scalar`.
530    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
531    let (owner, idx) = this.resolve_slot(slot.0 as usize);
532    (
533        SimHandle(owner as *const InstanceHandle as *mut c_void),
534        SlotId(idx as u32),
535    )
536}
537
538/// `SimHandle sv_rt_slot_owner(SimHandle, SlotId)`: the instance whose storage
539/// `slot` of `handle` is -- `handle` itself for one of its own slots, the
540/// instance a foreign slot names otherwise (see [`set_foreign_slots`]). A
541/// hierarchical call into another instance (`u1.f()`, LRM 23.6) passes this
542/// as the callee's instance handle, so the callee's memories and block
543/// events are `u1`'s own.
544#[cfg_attr(feature = "aot-exports", unsafe(export_name = "sv_rt_slot_owner"))]
545pub extern "C" fn real_sv_rt_slot_owner(handle: SimHandle, slot: SlotId) -> SimHandle {
546    resolve_foreign_slot(handle, slot).0
547}
548
549/// The production binding of the `sv_get_scalar` ABI symbol:
550/// [`sv_get_scalar`] after [`resolve_foreign_slot`].
551#[cfg_attr(feature = "aot-exports", unsafe(export_name = "sv_get_scalar"))]
552pub extern "C" fn real_sv_get_scalar(handle: SimHandle, slot: SlotId) -> u128 {
553    // SAFETY: see `sv_get_scalar`.
554    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
555    let idx = slot.0 as usize;
556    if idx >= this.own_slot_count()
557        && let Some(window) = this.foreign_window(idx)
558    {
559        return match read_window(this, idx, window) {
560            FourStateValue::Narrow(v) => v,
561            FourStateValue::Wide { .. } => {
562                unreachable!("a scalar read of a windowed alias wider than 64 bits")
563            }
564        };
565    }
566    let (handle, slot) = resolve_foreign_slot(handle, slot);
567    sv_get_scalar(handle, slot)
568}
569
570/// The production binding of `sv_get_wide` (see [`real_sv_get_scalar`]).
571#[cfg_attr(feature = "aot-exports", unsafe(export_name = "sv_get_wide"))]
572pub extern "C" fn real_sv_get_wide(handle: SimHandle, slot: SlotId) -> WideView {
573    // SAFETY: see `sv_get_scalar`.
574    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
575    let idx = slot.0 as usize;
576    if idx >= this.own_slot_count()
577        && let Some(window) = this.foreign_window(idx)
578    {
579        return scratch_wide(this, read_window(this, idx, window));
580    }
581    let (handle, slot) = resolve_foreign_slot(handle, slot);
582    sv_get_wide(handle, slot)
583}
584
585/// The current value of the window foreign slot `idx` of `this` stands for.
586#[cold]
587#[inline(never)]
588fn read_window(this: &InstanceHandle, idx: usize, window: super::AliasWindow) -> FourStateValue {
589    let (owner, slot) = this.resolve_slot(idx);
590    let mut loc = slot_location_for(owner, SlotId(slot as u32));
591    if let Some(row) = window.row {
592        loc = resolve_mem_row(loc, row);
593    }
594    super::alias::window_value(&read_slot(owner.arena(), loc), window)
595}
596
597fn scratch_wide(this: &InstanceHandle, value: FourStateValue) -> WideView {
598    let FourStateValue::Wide { aval, bval } = value else {
599        unreachable!("a wide read of a windowed alias no wider than 64 bits")
600    };
601    let (aval, bval, word_count) = this.scratch_wide(aval, bval);
602    WideView {
603        aval,
604        bval,
605        word_count,
606    }
607}
608
609/// The production binding of `sv_get_mem_word` (see [`real_sv_get_scalar`]).
610#[cfg_attr(feature = "aot-exports", unsafe(export_name = "sv_get_mem_word"))]
611pub extern "C" fn real_sv_get_mem_word(
612    handle: SimHandle,
613    slot: SlotId,
614    row_index: u64,
615) -> WideView {
616    let (handle, slot) = resolve_foreign_slot(handle, slot);
617    sv_get_mem_word(handle, slot, row_index)
618}
619
620/// The number of slots of `handle`'s instance's own layout -- the first
621/// foreign slot's number.
622pub fn own_slot_count(handle: SimHandle) -> u32 {
623    // SAFETY: see `sv_get_scalar`.
624    unsafe { InstanceHandle::from_sim_handle(handle) }.own_slot_count() as u32
625}
626
627pub fn cell_for_slot(handle: SimHandle, slot: SlotId) -> StateCellId {
628    // SAFETY: see `sv_get_scalar`.
629    let handle = unsafe { InstanceHandle::from_sim_handle(handle) };
630    handle.cell_for_slot(slot.0 as usize)
631}
632
633/// Task 10 (ST-026): the concrete artifact ARCHITECTURE §7 item 1's
634/// "accessor-ABI as the contract with `alwaysinline` wrappers" resolves to on
635/// STATE's side — a thin, `#[inline(always)]` Rust-level wrapper around the
636/// `extern "C"` fn above, a pure pass-through never a second, divergent
637/// implementation. CODEGEN is expected to call *these* (or emit its own
638/// `alwaysinline`-attributed LLVM declaration of the `extern "C"` symbol
639/// directly — either way, STATE's obligation is only that an inlinable entry
640/// point exists and is documented as the one sanctioned call shape).
641#[inline(always)]
642pub fn sv_get_scalar_inline(handle: SimHandle, slot: SlotId) -> u128 {
643    sv_get_scalar(handle, slot)
644}
645
646/// The `sv_set_scalar` twin of [`sv_get_scalar_inline`] (ST-026).
647#[inline(always)]
648pub fn sv_set_scalar_inline(handle: SimHandle, slot: SlotId, value: u128) {
649    sv_set_scalar(handle, slot, value)
650}
651
652/// The `sv_get_wide` twin of [`sv_get_scalar_inline`] (ST-026).
653#[inline(always)]
654pub fn sv_get_wide_inline(handle: SimHandle, slot: SlotId) -> WideView {
655    sv_get_wide(handle, slot)
656}
657
658/// The `sv_set_wide` twin of [`sv_get_scalar_inline`] (ST-026).
659#[inline(always)]
660pub fn sv_set_wide_inline(handle: SimHandle, slot: SlotId, value: WideView) {
661    sv_set_wide(handle, slot, value)
662}
663
664/// The `sv_get_mem_word` twin of [`sv_get_scalar_inline`] (ST-026, Task 12).
665#[inline(always)]
666pub fn sv_get_mem_word_inline(handle: SimHandle, slot: SlotId, row_index: u64) -> WideView {
667    sv_get_mem_word(handle, slot, row_index)
668}
669
670/// The `sv_set_mem_word` twin of [`sv_get_scalar_inline`] (ST-026, Task 12).
671#[inline(always)]
672pub fn sv_set_mem_word_inline(handle: SimHandle, slot: SlotId, row_index: u64, value: WideView) {
673    sv_set_mem_word(handle, slot, row_index, value)
674}
675
676/// ST-013: fills in the Task-0 contract stub — `read`/`write` operate on the
677/// *absolute*, already-instance-resolved [`StateCellId`] scheduler-side code
678/// uses (distinct from the ABI's template-relative `SlotId` above), by
679/// delegating to the exact same [`read_slot`]/[`commit_write_at`] primitives
680/// the accessor fns use, so both paths are provably the same storage.
681impl StateEngine {
682    /// Writes `row` (a memory's, when `Some`) of the real cell `cell`,
683    /// recording the effect on its window aliases.
684    fn commit_root(
685        &mut self,
686        cell: StateCellId,
687        row: Option<u64>,
688        value: FourStateValue,
689    ) -> super::write::ChangeResult {
690        match row {
691            None => self.commit_write(cell, value, &mut ()),
692            Some(row) => {
693                let loc = resolve_mem_row(self.cells[cell.0 as usize], row);
694                let result = commit_write_at(&self.arena, &self.vc, loc, cell, value, &mut ());
695                self.vc.views.note_change(cell, Some(row), &result);
696                result
697            }
698        }
699    }
700
701    /// The root's own storage of `info` (its row of a memory).
702    fn root_location(&self, info: &super::alias::ViewInfo) -> SlotLocation {
703        let loc = self.cells[info.root.0 as usize];
704        match info.window.row {
705            Some(row) => resolve_mem_row(loc, row),
706            None => loc,
707        }
708    }
709
710    /// A write through the view cell `view`: splices into the root, commits
711    /// the root, and returns whether the *window* changed.
712    fn write_view(
713        &mut self,
714        view: StateCellId,
715        info: super::alias::ViewInfo,
716        value: FourStateValue,
717    ) -> bool {
718        let root_value = read_slot(&self.arena, self.root_location(&info));
719        let spliced = super::alias::splice_window(&root_value, info.window, &value);
720        self.vc.views.table().begin_write(view);
721        let result = self.commit_root(info.root, info.window.row, spliced);
722        self.vc.views.table().end_write();
723        result.changed
724            && super::write::four_state_changed(
725                &super::alias::window_value(&result.old, info.window),
726                &super::alias::window_value(&result.new, info.window),
727            )
728    }
729}
730
731impl SignalAccess for StateEngine {
732    fn read(&self, cell: StateCellId) -> FourStateValue {
733        if let Some(info) = self.vc.views.info(cell) {
734            let root = read_slot(&self.arena, self.root_location(&info));
735            return super::alias::window_value(&root, info.window);
736        }
737        let loc = self.cells[cell.0 as usize];
738        read_slot(&self.arena, loc)
739    }
740
741    fn write(&mut self, cell: StateCellId, value: FourStateValue) -> bool {
742        if let Some(info) = self.vc.views.info(cell) {
743            return self.write_view(cell, info, value);
744        }
745        self.commit_write(cell, value, &mut ()).changed
746    }
747
748    fn read_row(&self, cell: StateCellId, row: u64) -> FourStateValue {
749        let loc = resolve_mem_row(self.cells[cell.0 as usize], row);
750        read_slot(&self.arena, loc)
751    }
752
753    fn write_row(&mut self, cell: StateCellId, row: u64, value: FourStateValue) -> bool {
754        self.commit_root(cell, Some(row), value).changed
755    }
756
757    fn take_alias_changes(&mut self) -> Vec<super::alias::AliasChange> {
758        self.vc.views.take_pending()
759    }
760
761    fn read_wide_window(
762        &self,
763        cell: StateCellId,
764        lo_word: usize,
765        words: usize,
766    ) -> (Vec<u64>, Vec<u64>) {
767        let loc = self.cells[cell.0 as usize];
768        debug_assert_eq!(loc.class, SlotClass::Wide);
769        super::huge_mirror::read_window(&self.arena, loc.word_offset as usize, lo_word, words)
770    }
771
772    fn write_wide_window(
773        &mut self,
774        cell: StateCellId,
775        lo_word: usize,
776        aval: &[u64],
777        bval: &[u64],
778    ) -> bool {
779        let loc = self.cells[cell.0 as usize];
780        // The windowed store replaces `commit_write_at` only where that does
781        // nothing the window skips (see `huge_mirror`'s module doc).
782        if loc.class != SlotClass::Wide
783            || self.vc.force.borrow().contains(cell)
784            || self.vc.records_changes()
785        {
786            return super::write_wide_window_whole(self, cell, lo_word, aval, bval);
787        }
788        let changed = super::huge_mirror::store_window(
789            &self.arena,
790            loc.word_offset as usize,
791            loc.bit_width,
792            lo_word,
793            aval,
794            bval,
795        );
796        if changed {
797            self.dispatch.notify_scheduler(
798                cell,
799                EdgeKind::None,
800                self.vc.current_time(),
801                self.vc.current_region(),
802            );
803        }
804        changed
805    }
806
807    fn subscribe(
808        &mut self,
809        _cell: StateCellId,
810        role: ListenerRole,
811        sink: Box<dyn ValueChangeSink + Send>,
812    ) {
813        // Task 11 (ST-035, ST-073, ST-074): fills in the placeholder left by
814        // Task 9 — this is now the *one* entry point every consumer
815        // (scheduler wakeup, waveform, VPI) registers into, and it forwards
816        // directly to `Dispatch::register`, the fixed 3-role table
817        // (`state::dispatch`), never a second ad hoc `Vec`/`HashMap`-backed
818        // table.
819        //
820        // `_cell` stays unused this task: this wave's `Dispatch` is one
821        // shared 3-role table per `StateEngine` (not per-cell), matching
822        // `commit_write`'s own current fan-out shape (it does not yet route
823        // per-cell either — see Task 12's GVT-gating task for where
824        // per-write routing through this table is actually exercised end to
825        // end). Per-cell subscription filtering, if ever needed, is future
826        // scope, not part of ST-035/ST-073/ST-074's acceptance.
827        self.dispatch.register(role, sink);
828    }
829}
830
831// These tests build real layouts from SV source, which needs the frontend.
832// The feature gate goes first: this file's own source scans find the test
833// module as the test attribute immediately followed by `mod tests`.
834#[cfg(feature = "frontend")]
835#[cfg(test)]
836mod tests {
837    use super::*;
838    use crate::netlist::{SymbolKey, TemplateKey};
839    use crate::state::instance::TemplateRegistry;
840    use crate::state::layout::build_template_layout;
841    use crate::state::value::compile_source;
842    use crate::state::write::last_vc_record_for;
843    use crate::state::{SlotDescriptor, SlotOffset, StateLayout};
844
845    /// Review fix (Task 10 r1, both blocking findings): the shared boundary
846    /// finder both `no_public_function_returns_a_raw_absolute_pointer_for_
847    /// caller_arithmetic` (ST-027) and `state_engine_owns_every_state_owned_
848    /// resource_directly_no_second_pointer` (ST-152) use to isolate a
849    /// scanned file's *production* code from its trailing `mod tests` block.
850    ///
851    /// The original implementation (in both tests, duplicated) truncated at
852    /// the FIRST literal occurrence of the substring `"#[cfg(test)]"`
853    /// anywhere in the file -- including inside doc-comment prose that
854    /// merely *mentions* the attribute in backticks (`write.rs`'s
855    /// `last_vc_record_for` doc comment) and on real, individually
856    /// `#[cfg(test)]`-gated helper items that sit well before the real test
857    /// module (`write.rs`'s own `last_vc_record_for` fn; `arena.rs`'s
858    /// `is_wide_plane_sealed` method, ~220 lines before its real `mod
859    /// tests`). Either shape silently truncates the "production code" this
860    /// function returns far too early, excluding genuine, always-compiled
861    /// code from both structural scans -- a "gate that cannot fail" for any
862    /// file shaped this way (verified by fault injection: a `pub fn`
863    /// returning `*mut u8`/a `static mut` placed in that excluded region
864    /// passed both scans green before this fix).
865    ///
866    /// The fix (r1): don't stop at *any* `#[cfg(test)]`, only at the one
867    /// immediately (modulo whitespace) followed by `mod tests` -- the actual
868    /// test-module boundary every file in this crate uses. A `#[cfg(test)]`
869    /// on some other item, or the bare text of the attribute inside a doc
870    /// comment, is skipped and scanning continues past it.
871    ///
872    /// The fix (r2, review finding "gate that cannot fail for content placed
873    /// *after* `mod tests`"): r1 still `return`ed at the real boundary,
874    /// which means it never scanned anything textually *after* that
875    /// module's closing `}` -- nothing in the language forces `mod tests` to
876    /// be the last item in a file, so genuinely-production code placed after
877    /// it was silently invisible to both scans. Fixed by excising each real
878    /// `#[cfg(test)] mod tests { ... }` span (located via brace-depth
879    /// matching, not truncation) and concatenating everything else --
880    /// before, between, and after every such block -- so scanning resumes
881    /// past the excluded region for the rest of the file instead of
882    /// stopping there. On every file in this crate today `mod tests` is
883    /// still the last item, so the excised suffix is just trailing
884    /// whitespace and this is a no-op change in practice; the fix is that
885    /// it's no longer an *assumed* invariant the scan quietly depends on.
886    fn production_code(src: &str) -> String {
887        let marker = "#[cfg(test)]";
888        let mut result = String::with_capacity(src.len());
889        let mut cursor = 0usize;
890        loop {
891            let Some(rel_idx) = src[cursor..].find(marker) else {
892                result.push_str(&src[cursor..]);
893                break;
894            };
895            let idx = cursor + rel_idx;
896            let after = &src[idx + marker.len()..];
897            let after_trimmed = after.trim_start();
898            if after_trimmed.starts_with("mod tests") {
899                let trim_offset = after.len() - after_trimmed.len();
900                let mod_tests_abs = idx + marker.len() + trim_offset;
901                let after_keyword = &src[mod_tests_abs + "mod tests".len()..];
902                let brace_abs = after_keyword
903                    .find('{')
904                    .map(|rel| mod_tests_abs + "mod tests".len() + rel);
905                let close_abs = brace_abs.and_then(|b| find_matching_brace(src, b));
906                match close_abs {
907                    Some(close_abs) => {
908                        // Keep everything up to (not including) the
909                        // `#[cfg(test)]` marker, then jump past the whole
910                        // module and keep scanning the remainder.
911                        result.push_str(&src[cursor..idx]);
912                        cursor = close_abs + 1;
913                        continue;
914                    }
915                    None => {
916                        // Malformed input (no matching brace found) --
917                        // never silently include unparseable content past
918                        // this point; fall back to the old truncate-here
919                        // behavior for safety rather than guessing.
920                        result.push_str(&src[cursor..idx]);
921                        break;
922                    }
923                }
924            }
925            // Not the real boundary (either doc-comment prose mentioning the
926            // attribute, or a `#[cfg(test)]`-gated item that isn't `mod
927            // tests` itself) -- keep scanning past this occurrence.
928            result.push_str(&src[cursor..idx + marker.len()]);
929            cursor = idx + marker.len();
930        }
931        result
932    }
933
934    /// Depth-aware `{`/`}` matcher for [`production_code`]: given the byte
935    /// index of an opening `{` in `src`, returns the byte index of its
936    /// matching closing `}`, skipping braces that appear inside line/block
937    /// comments, string literals and char literals (so a `"}"` or `'}'` in
938    /// a test body's assertion text doesn't desynchronize the count). This
939    /// is a lightweight tokenizer, not a full Rust parser -- sufficient for
940    /// well-formed source files in this crate, and returns `None` (never
941    /// panics or guesses) rather than silently mis-locating the boundary on
942    /// anything it can't confidently classify.
943    fn find_matching_brace(src: &str, open_idx: usize) -> Option<usize> {
944        let bytes = src.as_bytes();
945        debug_assert_eq!(bytes.get(open_idx), Some(&b'{'));
946        let mut depth: i32 = 0;
947        let mut i = open_idx;
948        while i < bytes.len() {
949            match bytes[i] {
950                b'{' => {
951                    depth += 1;
952                    i += 1;
953                }
954                b'}' => {
955                    depth -= 1;
956                    if depth == 0 {
957                        return Some(i);
958                    }
959                    i += 1;
960                }
961                b'/' if bytes.get(i + 1) == Some(&b'/') => {
962                    while i < bytes.len() && bytes[i] != b'\n' {
963                        i += 1;
964                    }
965                }
966                b'/' if bytes.get(i + 1) == Some(&b'*') => {
967                    i += 2;
968                    let mut comment_depth = 1u32;
969                    while i < bytes.len() && comment_depth > 0 {
970                        if bytes[i] == b'/' && bytes.get(i + 1) == Some(&b'*') {
971                            comment_depth += 1;
972                            i += 2;
973                        } else if bytes[i] == b'*' && bytes.get(i + 1) == Some(&b'/') {
974                            comment_depth -= 1;
975                            i += 2;
976                        } else {
977                            i += 1;
978                        }
979                    }
980                }
981                b'"' => {
982                    i += 1;
983                    while i < bytes.len() && bytes[i] != b'"' {
984                        if bytes[i] == b'\\' {
985                            i += 1;
986                        }
987                        i += 1;
988                    }
989                    i += 1;
990                }
991                b'\'' => {
992                    // Ambiguous between a char literal (`'x'`, `'\n'`,
993                    // `'\u{7B}'`) and a lifetime tick (`'static`, `'a`).
994                    // Look ahead a bounded window for a closing `'` with no
995                    // intervening newline; if found, treat the whole span
996                    // as a char literal and skip it (so a `'{'`/`'}'` char
997                    // literal can never desync the brace count). Otherwise
998                    // it's a lifetime -- just consume the tick.
999                    const MAX_CHAR_LITERAL_LEN: usize = 12; // covers '\u{10FFFF}'
1000                    let window_end = (i + 1 + MAX_CHAR_LITERAL_LEN).min(bytes.len());
1001                    let mut j = i + 1;
1002                    let mut found_close = None;
1003                    while j < window_end {
1004                        if bytes[j] == b'\n' {
1005                            break;
1006                        }
1007                        if bytes[j] == b'\'' && j > i + 1 {
1008                            found_close = Some(j);
1009                            break;
1010                        }
1011                        j += 1;
1012                    }
1013                    match found_close {
1014                        Some(close) => i = close + 1,
1015                        None => i += 1,
1016                    }
1017                }
1018                _ => {
1019                    i += 1;
1020                }
1021            }
1022        }
1023        None
1024    }
1025
1026    /// Review fix (Task 10 r1): the fault-injection cases the two blocking
1027    /// findings describe, run against the fixed [`production_code`] above,
1028    /// locking the fix in. Both cases previously passed (i.e. the injected
1029    /// violation went undetected) against the old first-`#[cfg(test)]`
1030    /// truncation; both must now be caught.
1031    #[test]
1032    fn production_code_does_not_truncate_early_at_a_pre_mod_tests_cfg_test_item() {
1033        // Shape 1 (write.rs): a `#[cfg(test)]`-visible peek helper, gated
1034        // individually, sitting before the real `mod tests` block -- plus a
1035        // doc comment that merely *mentions* the attribute in prose first.
1036        let write_shaped = concat!(
1037            "/// A `#[cfg(test)]`-visible peek at something.\n",
1038            "#[cfg(test)]\n",
1039            "pub fn last_vc_record_for() {}\n",
1040            "\n",
1041            "pub fn genuinely_production_and_must_stay_scanned() -> u32 { 1 }\n",
1042            "\n",
1043            "#[cfg(test)]\n",
1044            "mod tests {\n",
1045            "    // test-only code\n",
1046            "}\n",
1047        );
1048        let scanned = production_code(write_shaped);
1049        assert!(
1050            scanned.contains("genuinely_production_and_must_stay_scanned"),
1051            "a genuine production fn between an early #[cfg(test)] helper and \
1052             the real `mod tests` block must still be included in the scan"
1053        );
1054        assert!(
1055            !scanned.contains("mod tests"),
1056            "the real test module itself must be excluded"
1057        );
1058
1059        // Shape 2 (arena.rs): a small `#[cfg(test)]`-gated method embedded
1060        // deep inside a real `impl` block, well before the real `mod tests`.
1061        let arena_shaped = concat!(
1062            "impl Arena {\n",
1063            "    #[cfg(test)]\n",
1064            "    pub(crate) fn is_wide_plane_sealed(&self) -> bool { true }\n",
1065            "\n",
1066            "    pub fn mode(&self) -> u8 { 0 }\n",
1067            "}\n",
1068            "\n",
1069            "#[cfg(test)]\n",
1070            "mod tests {\n",
1071            "    // test-only code\n",
1072            "}\n",
1073        );
1074        let scanned = production_code(arena_shaped);
1075        assert!(
1076            scanned.contains("pub fn mode"),
1077            "a genuine production fn after an early #[cfg(test)]-gated method \
1078             must still be included in the scan"
1079        );
1080        assert!(
1081            !scanned.contains("mod tests"),
1082            "the real test module itself must be excluded"
1083        );
1084    }
1085
1086    /// Review fix (Task 10 r1): the same [`production_code`] boundary finder
1087    /// applied to the two *real* files the original findings named, proving
1088    /// the fix closes their exact reported blind spots (not just the
1089    /// synthetic shapes above). `write.rs`'s scan must reach past its early
1090    /// doc-comment mention of `#[cfg(test)]` (line 465-ish) and its
1091    /// individually-gated `last_vc_record_for` helper, and must still
1092    /// exclude the real `mod tests` block. `arena.rs`'s scan must reach past
1093    /// its early `#[cfg(test)]`-gated `is_wide_plane_sealed` method and
1094    /// include the genuinely-production `raw_inline_ptr`/`mode`/`ffi_view`
1095    /// functions that sit between it and the real `mod tests` block --
1096    /// exactly the ~500 lines the original first-match truncation dropped.
1097    #[test]
1098    fn production_code_reaches_the_real_mod_tests_boundary_in_write_rs_and_arena_rs() {
1099        let write_src = production_code(include_str!("write.rs"));
1100        assert!(
1101            write_src.contains("pub(crate) fn commit_write_at"),
1102            "write.rs's real mod tests boundary must not truncate before its production fns"
1103        );
1104        assert!(
1105            !write_src.contains("mod tests"),
1106            "write.rs's scan must still exclude its real test module"
1107        );
1108
1109        let arena_src = production_code(include_str!("arena.rs"));
1110        for needed in [
1111            "pub unsafe fn raw_inline_ptr",
1112            "pub fn mode",
1113            "pub fn ffi_view",
1114            "pub fn wide_aval",
1115            "pub fn wide_bval",
1116        ] {
1117            assert!(
1118                arena_src.contains(needed),
1119                "arena.rs's scan must include the genuinely-production `{needed}`, \
1120                 which the original first-#[cfg(test)]-match truncation silently dropped"
1121            );
1122        }
1123        assert!(
1124            !arena_src.contains("mod tests"),
1125            "arena.rs's scan must still exclude its real test module"
1126        );
1127    }
1128
1129    /// Review fix (Task 10 r2, both blocking findings on the boundary
1130    /// finder): r1's `production_code` stopped dead at the real `mod tests`
1131    /// boundary and never scanned anything past it -- nothing in the
1132    /// language forces `mod tests` to be the last item in a file, so a
1133    /// genuinely-production, un-gated item placed textually *after* the
1134    /// real `mod tests` block's closing brace was silently invisible to
1135    /// both structural scans. Mirrors the exact fault-injection shape the
1136    /// finding used (`static mut GLOBAL_LEAK: u32 = 0;` appended after a
1137    /// file's closing `mod tests` brace): the item must still show up in
1138    /// `production_code`'s output, and the brace matcher must not be
1139    /// confused by a `'{'`/`"}"` literal inside the excluded test body, nor
1140    /// stop at only the first of two `mod tests`-shaped blocks.
1141    #[test]
1142    fn production_code_resumes_scanning_after_the_real_mod_tests_closing_brace() {
1143        let shaped = concat!(
1144            "pub fn before_the_test_module() -> u32 { 1 }\n",
1145            "\n",
1146            "#[cfg(test)]\n",
1147            "mod tests {\n",
1148            "    #[test]\n",
1149            "    fn uses_brace_shaped_literals() {\n",
1150            "        let open = '{';\n",
1151            "        let close = '}';\n",
1152            "        let s = \"} not a real close {\";\n",
1153            "        assert_eq!(open, open);\n",
1154            "        assert_eq!(close, close);\n",
1155            "        let _ = s;\n",
1156            "    }\n",
1157            "}\n",
1158            "\n",
1159            "pub fn genuinely_production_placed_after_mod_tests() -> u32 { 2 }\n",
1160            "\n",
1161            "static mut GLOBAL_LEAK_AFTER_MOD_TESTS: u32 = 0;\n",
1162        );
1163        let scanned = production_code(shaped);
1164        assert!(
1165            scanned.contains("before_the_test_module"),
1166            "content before the real mod tests block must still be scanned"
1167        );
1168        assert!(
1169            !scanned.contains("uses_brace_shaped_literals"),
1170            "the real test module's body must still be excluded, even though it \
1171             contains '{{'/'}}' char literals and a \"}}...{{\" string literal that \
1172             could desync a naive brace counter"
1173        );
1174        assert!(
1175            scanned.contains("genuinely_production_placed_after_mod_tests"),
1176            "a genuine production fn placed AFTER the real mod tests block's \
1177             closing brace must still be scanned -- this is the exact gap the \
1178             review finding reported"
1179        );
1180        assert!(
1181            scanned.contains("GLOBAL_LEAK_AFTER_MOD_TESTS"),
1182            "a `static mut` placed after the real mod tests block must still be \
1183             visible to production_code's output, so the ST-152 scan built on top \
1184             of it can catch it"
1185        );
1186
1187        // Two real test modules in one file: both must be excluded, and the
1188        // content between and after them must both survive.
1189        let two_modules = concat!(
1190            "pub fn between_none_yet() -> u32 { 0 }\n",
1191            "#[cfg(test)]\n",
1192            "mod tests {\n",
1193            "    fn first() {}\n",
1194            "}\n",
1195            "pub fn between_the_two_modules() -> u32 { 1 }\n",
1196            "#[cfg(test)]\n",
1197            "mod tests {\n",
1198            "    fn second() {}\n",
1199            "}\n",
1200            "pub fn after_both_modules() -> u32 { 2 }\n",
1201        );
1202        let scanned_two = production_code(two_modules);
1203        assert!(!scanned_two.contains("fn first"));
1204        assert!(!scanned_two.contains("fn second"));
1205        assert!(scanned_two.contains("between_the_two_modules"));
1206        assert!(scanned_two.contains("after_both_modules"));
1207    }
1208
1209    /// Task 10 (ST-026): for a built instance/slot, the `#[inline(always)]`
1210    /// wrappers must produce results identical to the `extern "C"` fns they
1211    /// wrap, on both the scalar and wide tiers — proving each wrapper is a
1212    /// pure pass-through, never a second, divergent implementation. Fails if
1213    /// a wrapper re-derives its own resolution/storage path instead of
1214    /// simply forwarding to the existing `extern "C"` fn.
1215    #[test]
1216    fn inline_wrappers_produce_identical_results_to_the_extern_c_fns() {
1217        let tmpl = TemplateKey {
1218            definition: 10,
1219            params_hash: 0,
1220        };
1221        let mut engine = engine_with_template(
1222            "module m; logic [7:0] x; logic [127:0] w; endmodule\n",
1223            tmpl,
1224        );
1225        let handle = engine.allocate_instance(tmpl).expect("template registered");
1226        let h = handle.into_sim_handle();
1227        let scalar_slot = SlotId(0);
1228        let wide_slot = SlotId(1);
1229
1230        // Scalar: write through the extern "C" fn, read back through both
1231        // the extern "C" fn and the inline wrapper -- they must agree.
1232        sv_set_scalar(h, scalar_slot, narrow_word(0x42, 0));
1233        assert_eq!(
1234            sv_get_scalar_inline(h, scalar_slot),
1235            sv_get_scalar(h, scalar_slot),
1236            "sv_get_scalar_inline must return exactly what sv_get_scalar returns"
1237        );
1238
1239        // Scalar write path: write through the inline wrapper, read back
1240        // through the extern "C" fn -- both directions must agree.
1241        sv_set_scalar_inline(h, scalar_slot, narrow_word(0x99, 0));
1242        assert_eq!(sv_get_scalar(h, scalar_slot), narrow_word(0x99, 0));
1243
1244        // Wide: same round trip, through both wrappers.
1245        let mut aval = [0x1111_1111_1111_1111u64, 0x2222_2222_2222_2222u64];
1246        let mut bval = [0x3333_3333_3333_3333u64, 0x4444_4444_4444_4444u64];
1247        let pattern = WideView {
1248            aval: aval.as_mut_ptr(),
1249            bval: bval.as_mut_ptr(),
1250            word_count: 2,
1251        };
1252        sv_set_wide_inline(h, wide_slot, pattern);
1253        let via_extern_c = sv_get_wide(h, wide_slot);
1254        let via_inline = sv_get_wide_inline(h, wide_slot);
1255        // SAFETY: both views alias the same, still-live arena storage this
1256        // same test's `engine`/`handle` own; `word_count` words are valid to
1257        // read on both planes per `sv_get_wide`'s own contract.
1258        unsafe {
1259            let extern_c_aval = std::slice::from_raw_parts(via_extern_c.aval, 2);
1260            let extern_c_bval = std::slice::from_raw_parts(via_extern_c.bval, 2);
1261            let inline_aval = std::slice::from_raw_parts(via_inline.aval, 2);
1262            let inline_bval = std::slice::from_raw_parts(via_inline.bval, 2);
1263            assert_eq!(extern_c_aval, inline_aval);
1264            assert_eq!(extern_c_bval, inline_bval);
1265        }
1266
1267        // SAFETY: see the module's other tests' identical reclaim comment.
1268        unsafe {
1269            drop(Box::from_raw(h.0 as *mut InstanceHandle));
1270        }
1271    }
1272
1273    /// Task 10 (ST-027): a source-text structural test proving no `pub fn` in
1274    /// either `accessor_impl.rs` or `write.rs` returns a raw pointer at any
1275    /// of this crate's actual signal-storage word widths -- the shape a
1276    /// raw-offset-exposing API (the superseded state.md design) would need
1277    /// to hand a caller a pointer it could do its own base+offset
1278    /// arithmetic against. Fails if a future edit reintroduces that
1279    /// superseded design.
1280    ///
1281    /// Review fix (Task 10 r2, both blocking findings on this test): r1 only
1282    /// scanned for `*mut u8`/`*const u8`, but no storage in this crate is
1283    /// ever addressed as `u8` -- the arena's inline plane is `Vec<u128>` and
1284    /// its wide planes are `Vec<u64>` (`arena.rs`'s own `raw_inline_ptr`
1285    /// already returns `*mut u128`, and the accepted `WideView` accessor
1286    /// already carries `*mut u64` fields), so a realistic reintroduction of
1287    /// the superseded design would use one of *those* widths and sail
1288    /// through undetected. Broadened to the widths this crate actually
1289    /// uses. `sv_get_wide`/`sv_get_wide_inline`/`sv_set_wide`/
1290    /// `sv_set_wide_inline` stay clean under the broadened scan because
1291    /// their signatures name the `WideView` struct, never a bare pointer
1292    /// type, in production code -- asserted explicitly below rather than
1293    /// just assumed.
1294    #[test]
1295    fn no_public_function_returns_a_raw_absolute_pointer_for_caller_arithmetic() {
1296        let forbidden = forbidden_raw_pointer_patterns();
1297
1298        let accessor_impl_src = production_code(include_str!("accessor_impl.rs"));
1299        let write_src = production_code(include_str!("write.rs"));
1300        for (name, src) in [
1301            ("accessor_impl.rs", accessor_impl_src.as_str()),
1302            ("write.rs", write_src.as_str()),
1303        ] {
1304            for pattern in &forbidden {
1305                assert!(
1306                    !src.contains(pattern.as_str()),
1307                    "{name}'s production code must not return a raw `{pattern}` pointer \
1308                     from any function -- this crate's real word types are u128 (inline \
1309                     plane) and u64 (wide planes), not just u8"
1310                );
1311            }
1312        }
1313
1314        // Confirm the broadened scan still doesn't false-positive on the
1315        // legitimate wide-plane accessors: their signatures name the
1316        // `WideView` struct, not a bare pointer type, so exactly the two
1317        // sanctioned `pub fn`s below are allowed to have `WideView` in their
1318        // signature and none of them may have a bare pointer return type.
1319        assert!(
1320            accessor_impl_src.contains("-> WideView"),
1321            "sv_get_wide/sv_get_wide_inline must still return the named WideView type"
1322        );
1323    }
1324
1325    /// The forbidden raw-pointer shapes [`no_public_function_returns_a_raw_
1326    /// absolute_pointer_for_caller_arithmetic`] scans for -- factored out so
1327    /// the fault-injection test below exercises the exact same list the real
1328    /// scan uses, rather than a hand-copied duplicate that could silently
1329    /// drift out of sync with it.
1330    ///
1331    /// Built via concatenation, not as one contiguous literal per pattern,
1332    /// so this file's own source text never contains any forbidden
1333    /// substring contiguously either (this exact test function would
1334    /// otherwise match itself).
1335    fn forbidden_raw_pointer_patterns() -> Vec<String> {
1336        let widths = ["8", "16", "32", "64", "128"];
1337        widths
1338            .iter()
1339            .flat_map(|w| [["*", "mut u", w].concat(), ["*", "const u", w].concat()])
1340            .collect()
1341    }
1342
1343    /// Review fix (Task 10 r2, both blocking findings on ST-027's width
1344    /// scan): fault injection proving the broadened scan actually catches
1345    /// this crate's *real* pointer shapes (`*mut u128`, `*mut u64`), not
1346    /// just the `*mut u8` shape the r1 scan was limited to and which has no
1347    /// natural occurrence anywhere in this codebase. Mirrors the exact
1348    /// shapes the finding named: a `raw_base_ptr`/`instance_base_words`-like
1349    /// signature exposing the arena's real word types for caller-side
1350    /// base+offset arithmetic.
1351    #[test]
1352    fn forbidden_raw_pointer_scan_catches_this_crates_real_word_widths() {
1353        let forbidden = forbidden_raw_pointer_patterns();
1354
1355        let u128_reintroduction = concat!(
1356            "pub fn raw_base_ptr(handle: SimHandle) -> *mut u128 {\n",
1357            "    unimplemented!()\n",
1358            "}\n",
1359        );
1360        assert!(
1361            forbidden
1362                .iter()
1363                .any(|p| u128_reintroduction.contains(p.as_str())),
1364            "a reintroduced *mut u128 raw-offset accessor must be caught by the \
1365             forbidden-pattern list -- this is the arena's actual inline-plane word type"
1366        );
1367
1368        let u64_reintroduction = concat!(
1369            "pub fn instance_base_words(&self) -> (*mut u128, *mut u64, *mut u64) {\n",
1370            "    unimplemented!()\n",
1371            "}\n",
1372        );
1373        assert!(
1374            forbidden
1375                .iter()
1376                .any(|p| u64_reintroduction.contains(p.as_str())),
1377            "a reintroduced *mut u64 raw-offset accessor must be caught by the \
1378             forbidden-pattern list -- this is the arena's actual wide-plane word type"
1379        );
1380
1381        // The r1-era u8-only list must NOT have caught either shape (sanity
1382        // check that this test actually exercises new coverage, not
1383        // coverage the r1 scan already had).
1384        let u8_only: Vec<String> = vec!["*mut u8".to_string(), "*const u8".to_string()];
1385        assert!(
1386            !u8_only
1387                .iter()
1388                .any(|p| u128_reintroduction.contains(p.as_str())),
1389            "sanity check: the u128 shape must not contain the literal u8 substring"
1390        );
1391        assert!(
1392            !u8_only
1393                .iter()
1394                .any(|p| u64_reintroduction.contains(p.as_str())),
1395            "sanity check: the u64 shape must not contain the literal u8 substring"
1396        );
1397    }
1398
1399    /// Task 10 (ST-029): `state::{StateEngine, StateCellId, FourStateValue}`
1400    /// alone (no `crate::process::*`, no `crate::sched::*`) must be enough to
1401    /// perform an `alloc_cell` + `commit_write` + `SignalAccess::read`
1402    /// sequence -- the import list itself is the proof this reference API
1403    /// needs no `ProcessFn`/`CompiledKernel` type to be reachable.
1404    #[test]
1405    fn read_and_write_are_callable_without_constructing_any_kernel_or_process_type() {
1406        use crate::state::write::SlotLocation;
1407        use crate::state::{FourStateValue, SignalAccess, StateCellId, StateEngine};
1408
1409        let templates = TemplateRegistry::new();
1410        let mut engine = StateEngine::new(templates);
1411        // SAFETY: local, single-owned engine; no other live borrow of the
1412        // inline plane, single-threaded test.
1413        let word_offset = unsafe { engine.arena.alloc_inline_word() };
1414        let loc = SlotLocation {
1415            class: SlotClass::Inline,
1416            word_offset,
1417            bit_width: 8,
1418            elem_word_count: 1,
1419            row_stride_words: 0,
1420            num_rows: 0,
1421        };
1422        let cell: StateCellId = engine.alloc_cell(loc);
1423
1424        let changed =
1425            engine.commit_write(cell, FourStateValue::Narrow(narrow_word(0x2A, 0)), &mut ());
1426        assert!(changed.changed);
1427
1428        match SignalAccess::read(&engine, cell) {
1429            FourStateValue::Narrow(w) => assert_eq!(w, narrow_word(0x2A, 0)),
1430            FourStateValue::Wide { .. } => panic!("expected Narrow"),
1431        }
1432    }
1433
1434    /// Task 10 (ST-152): every resource `StateEngine` owns (`arena`,
1435    /// `templates`, `cells`, `vc` -- `dispatch`/the undo log are added by
1436    /// Tasks 11/16, later than this task) is a *direct* field, reachable
1437    /// purely through `&engine.<field>`/`&mut engine.<field>`, never through
1438    /// a second, free-standing global -- and no forbidden-kind global (a
1439    /// mutable process-wide static, an external once-per-process-init macro,
1440    /// or a lazily-initialized single-cell singleton type) exists anywhere in
1441    /// this crate's `state` module (the concrete negative half of the proof;
1442    /// see this test's own body for the exact forbidden spellings it scans
1443    /// for, listed there rather than here so this doc comment's own prose
1444    /// cannot trip the very scan it describes).
1445    #[test]
1446    fn state_engine_owns_every_state_owned_resource_directly_no_second_pointer() {
1447        let templates = TemplateRegistry::new();
1448        let mut engine = StateEngine::new(templates);
1449
1450        // Reach every currently-existing STATE-owned resource purely through
1451        // a direct field access on `&mut engine`/`&engine` -- no second,
1452        // free-standing global exists to reach any of them through instead.
1453        let _arena: &std::rc::Rc<super::super::arena::Arena> = &engine.arena;
1454        let _templates: &std::rc::Rc<TemplateRegistry> = &engine.templates;
1455        let _cells_len_before = engine.cells.len();
1456        let (cell, _) = {
1457            // SAFETY: local, single-owned engine; no other live borrow.
1458            let word_offset = unsafe { engine.arena.alloc_inline_word() };
1459            let loc = crate::state::write::SlotLocation {
1460                class: SlotClass::Inline,
1461                word_offset,
1462                bit_width: 1,
1463                elem_word_count: 1,
1464                row_stride_words: 0,
1465                num_rows: 0,
1466            };
1467            (engine.alloc_cell(loc), loc)
1468        };
1469        assert_eq!(
1470            engine.cells.len(),
1471            _cells_len_before + 1,
1472            "cells is a direct field"
1473        );
1474        engine.commit_write(cell, FourStateValue::Narrow(narrow_word(1, 0)), &mut ());
1475        engine.register_worker_thread(); // reaches `vc` purely via `&mut engine`
1476
1477        // Negative half: no forbidden-kind global anywhere in this crate's
1478        // `state` module's *production* code. See [`forbidden_global_patterns`]
1479        // for the exact forbidden spellings and why each one is there.
1480        let forbidden = forbidden_global_patterns();
1481        let state_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/state");
1482        let entries = std::fs::read_dir(&state_dir).expect("state/ directory is readable");
1483        for entry in entries {
1484            let entry = entry.expect("directory entry is readable");
1485            let path = entry.path();
1486            if path.extension().and_then(|e| e.to_str()) != Some("rs") {
1487                continue;
1488            }
1489            let contents = std::fs::read_to_string(&path)
1490                .unwrap_or_else(|e| panic!("{} must be readable: {e}", path.display()));
1491            let scanned = production_code(&contents);
1492            for forbidden_pattern in &forbidden {
1493                assert!(
1494                    !scanned.contains(forbidden_pattern.as_str()),
1495                    "{}'s production code must not contain a free-standing global of the \
1496                     forbidden kind this test enforces -- every STATE-owned resource must be \
1497                     reachable exclusively through a direct StateEngine field",
1498                    path.display()
1499                );
1500            }
1501        }
1502    }
1503
1504    /// The forbidden free-standing-global shapes [`state_engine_owns_every_
1505    /// state_owned_resource_directly_no_second_pointer`] scans for --
1506    /// factored out so the fault-injection test below exercises the exact
1507    /// same list the real scan uses, rather than a hand-copied duplicate
1508    /// that could silently drift out of sync with it.
1509    ///
1510    /// Review fix (Task 10 r2): r1's list (`static mut`, `lazy_static`,
1511    /// `OnceLock`, `OnceCell`) only catches globals that need the `mut`
1512    /// keyword or one of those three specific types. A free-standing,
1513    /// process-wide, mutably-shared global does not need any of them -- a
1514    /// bare `static` whose type is `Mutex<T>`/`RwLock<T>`/`AtomicPtr<T>` is
1515    /// an ordinary, no-`unsafe`-required way to build exactly the "second,
1516    /// free-standing global" pointer to STATE-owned data ST-152 forbids,
1517    /// using none of the four original spellings.
1518    ///
1519    /// Added: `Mutex<`/`RwLock<` (the two `Sync`-safe interior-mutability
1520    /// wrapper types that can hold an arbitrary `T`, including an
1521    /// `Rc`/`Arc`/raw pointer at STATE's owned data) and `AtomicPtr<` (the
1522    /// pointer-typed atomic -- deliberately narrower than a bare `Atomic`
1523    /// substring, which would also match this crate's existing, legitimate
1524    /// `instance.rs` monotonic-id counter: a plain integer atomic holds no
1525    /// pointer to any STATE-owned resource and is not the shape ST-152
1526    /// forbids, so it must not be flagged; see the fault-injection test
1527    /// below for the check that it stays clean).
1528    ///
1529    /// Every spelling here is built by concatenation (never as one
1530    /// contiguous literal, and never named in a doc/prose comment either)
1531    /// for the same self-matching reason r1's list already documents.
1532    fn forbidden_global_patterns() -> Vec<String> {
1533        vec![
1534            ["static", " mut"].concat(),
1535            ["lazy", "_static"].concat(),
1536            ["Once", "Lock"].concat(),
1537            ["Once", "Cell"].concat(),
1538            ["Mut", "ex<"].concat(),
1539            ["Rw", "Lock<"].concat(),
1540            ["Atomic", "Ptr<"].concat(),
1541        ]
1542    }
1543
1544    /// Review fix (Task 10 r2): fault injection proving the broadened
1545    /// forbidden-global list actually catches a Mutex/RwLock/AtomicPtr-
1546    /// backed static -- the exact evasion shape the finding demonstrated
1547    /// (a `Mutex<Option<Rc<...>>>`-typed free-standing global) that passed
1548    /// all four r1 patterns clean. Also proves the broadened list does NOT
1549    /// false-positive on this crate's one existing, legitimate
1550    /// free-standing static (`instance.rs`'s `NEXT_ENGINE_ID: AtomicU64`
1551    /// monotonic-id counter), which holds no pointer to any STATE-owned
1552    /// resource.
1553    #[test]
1554    fn forbidden_global_scan_catches_mutex_rwlock_and_atomicptr_backed_globals() {
1555        let forbidden = forbidden_global_patterns();
1556
1557        let mutex_leak = concat!(
1558            "static GLOBAL_ARENA_REGISTRY: std::sync::Mutex<Option<std::rc::Rc<u8>>> =\n",
1559            "    std::sync::Mutex::new(None);\n",
1560        );
1561        assert!(
1562            forbidden.iter().any(|p| mutex_leak.contains(p.as_str())),
1563            "a Mutex-backed free-standing global must be caught by the forbidden-pattern list"
1564        );
1565
1566        let rwlock_leak = "static GLOBAL: std::sync::RwLock<u8> = std::sync::RwLock::new(0);";
1567        assert!(
1568            forbidden.iter().any(|p| rwlock_leak.contains(p.as_str())),
1569            "an RwLock-backed free-standing global must be caught by the forbidden-pattern list"
1570        );
1571
1572        let atomic_ptr_leak = "static GLOBAL: std::sync::atomic::AtomicPtr<u8> = \
1573             std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());";
1574        assert!(
1575            forbidden
1576                .iter()
1577                .any(|p| atomic_ptr_leak.contains(p.as_str())),
1578            "an AtomicPtr-backed free-standing global must be caught by the forbidden-pattern list"
1579        );
1580
1581        // None of the r1-era four patterns caught any of the above (sanity
1582        // check that this test actually exercises new coverage, not
1583        // coverage the r1 scan already had).
1584        let r1_patterns: Vec<String> = vec![
1585            ["static", " mut"].concat(),
1586            ["lazy", "_static"].concat(),
1587            ["Once", "Lock"].concat(),
1588            ["Once", "Cell"].concat(),
1589        ];
1590        for shape in [mutex_leak, rwlock_leak, atomic_ptr_leak] {
1591            assert!(
1592                !r1_patterns.iter().any(|p| shape.contains(p.as_str())),
1593                "sanity check: the r1-era pattern list must not already catch this shape"
1594            );
1595        }
1596
1597        // Must NOT false-positive on this crate's real, legitimate plain
1598        // integer atomic counter -- it is not a pointer to STATE-owned data.
1599        let legitimate_counter = "static NEXT_ENGINE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);";
1600        assert!(
1601            !forbidden
1602                .iter()
1603                .any(|p| legitimate_counter.contains(p.as_str())),
1604            "a plain AtomicU64 counter (no pointer to any STATE-owned resource) must not be flagged"
1605        );
1606    }
1607
1608    fn engine_with_template(src: &str, key: TemplateKey) -> StateEngine {
1609        let design = compile_source(src).expect("valid SV source compiles");
1610        let body = design
1611            .top_instances()
1612            .next()
1613            .expect("one top instance")
1614            .instance_body()
1615            .expect("instance has a body");
1616        let layout: StateLayout = build_template_layout(&body).expect("layout builds");
1617
1618        let templates = TemplateRegistry::new();
1619        // SAFETY: `templates` is a local, single-owned value with no other
1620        // live borrow and no concurrent caller.
1621        unsafe {
1622            templates.register(key, layout);
1623        }
1624        StateEngine::new(templates)
1625    }
1626
1627    /// Review fix (Task 9 r4, blocking finding): builds a `StateEngine` with
1628    /// one registered template holding a single `SlotClass::Wide` slot of
1629    /// `elem_word_count` words, entirely by hand -- no `compile_source`/
1630    /// `build_template_layout` (hence no `sv_lang` C++ FFI call) anywhere in
1631    /// this path, unlike [`engine_with_template`] above. `sv_lang`'s FFI
1632    /// cannot run under Miri, so this is the route the Miri-reachable
1633    /// regression test below uses to build a real `sv_get_wide`-capable
1634    /// instance. Mirrors [`engine_with_template`]'s own registration
1635    /// sequence exactly, just with a hand-built [`StateLayout`] in place of
1636    /// one `build_template_layout` produces from real elaborated SV.
1637    fn engine_with_manual_wide_template(key: TemplateKey, elem_word_count: u32) -> StateEngine {
1638        let layout = StateLayout {
1639            slots: vec![SlotDescriptor {
1640                key: SymbolKey(0),
1641                offset: SlotOffset {
1642                    class: SlotClass::Wide,
1643                    word_offset: 0,
1644                    bit_width: elem_word_count * 64,
1645                    elem_word_count,
1646                    row_stride_words: 0,
1647                    num_rows: 0,
1648                },
1649                initial: None,
1650                net_info: None,
1651            }],
1652            inline_words: 0,
1653            wide_words: elem_word_count,
1654        };
1655
1656        let templates = TemplateRegistry::new();
1657        // SAFETY: `templates` is a local, single-owned value with no other
1658        // live borrow and no concurrent caller.
1659        unsafe {
1660            templates.register(key, layout);
1661        }
1662        StateEngine::new(templates)
1663    }
1664
1665    fn narrow_word(val: u64, unk: u64) -> u128 {
1666        (val as u128) | ((unk as u128) << 64)
1667    }
1668
1669    /// Task 11 (ST-138) regression: `slot_location_for` (this module's own
1670    /// private resolver, exercised directly here since this test lives
1671    /// inside the same module) must thread a `SlotClass::Memory` slot's
1672    /// `row_stride_words`/`num_rows` through into the returned
1673    /// `SlotLocation`, not drop/zero them -- the concrete case Task 1's own
1674    /// hand-built-`SlotLocation` unit tests (`state::write::tests`) cannot
1675    /// catch, since they never go through `slot_location_for` at all. A
1676    /// hand-built `SlotOffset` (no `sv_lang` FFI, mirrors
1677    /// `engine_with_manual_wide_template`'s own reasoning) with
1678    /// `row_stride_words: 4, num_rows: 6` deliberately distinct from
1679    /// `elem_word_count: 2`, so a bug that copied `elem_word_count` into
1680    /// those fields instead (or left them zeroed) is caught, not silently
1681    /// matched.
1682    #[test]
1683    fn slot_location_for_threads_memory_row_stride_and_num_rows_through() {
1684        let key = TemplateKey {
1685            definition: 0,
1686            params_hash: 0,
1687        };
1688        let layout = StateLayout {
1689            slots: vec![SlotDescriptor {
1690                key: SymbolKey(0),
1691                offset: SlotOffset {
1692                    class: SlotClass::Memory,
1693                    word_offset: 0,
1694                    bit_width: 32,
1695                    elem_word_count: 2,
1696                    row_stride_words: 4,
1697                    num_rows: 6,
1698                },
1699                initial: None,
1700                net_info: None,
1701            }],
1702            inline_words: 0,
1703            wide_words: 2 * 6,
1704        };
1705        let templates = TemplateRegistry::new();
1706        // SAFETY: `templates` is a local, single-owned value with no other
1707        // live borrow and no concurrent caller.
1708        unsafe {
1709            templates.register(key, layout);
1710        }
1711        let mut engine = StateEngine::new(templates);
1712        let handle = engine.allocate_instance(key).expect("template registered");
1713
1714        let resolved = slot_location_for(&handle, SlotId(0));
1715        assert_eq!(resolved.class, SlotClass::Memory);
1716        assert_eq!(resolved.elem_word_count, 2);
1717        assert_eq!(
1718            resolved.row_stride_words, 4,
1719            "slot_location_for must copy row_stride_words from the SlotOffset, not drop/zero it"
1720        );
1721        assert_eq!(
1722            resolved.num_rows, 6,
1723            "slot_location_for must copy num_rows from the SlotOffset, not drop/zero it"
1724        );
1725    }
1726
1727    /// ST-020, ST-021: build a 1-slot template (8-bit inline var), allocate 2
1728    /// instances, convert each to a `SimHandle`; `sv_set_scalar` distinct
1729    /// patterns through each, and `sv_get_scalar` must read back exactly the
1730    /// pattern written through *that* handle — proving the same `SlotId`
1731    /// resolves to two different absolute words because the two
1732    /// `InstanceHandle`s carry different bases. Fails if `handle.base` is
1733    /// ignored (both instances would alias).
1734    #[test]
1735    fn sv_get_set_scalar_round_trips_through_a_real_instance_handle() {
1736        let tmpl = TemplateKey {
1737            definition: 0,
1738            params_hash: 0,
1739        };
1740        let mut engine = engine_with_template("module m; logic [7:0] x; endmodule\n", tmpl);
1741
1742        let handle_a = engine.allocate_instance(tmpl).expect("template registered");
1743        let handle_b = engine.allocate_instance(tmpl).expect("template registered");
1744        let h_a = handle_a.into_sim_handle();
1745        let h_b = handle_b.into_sim_handle();
1746
1747        let slot = SlotId(0);
1748        let pattern_a = narrow_word(0xAA, 0);
1749        let pattern_b = narrow_word(0x55, 0);
1750        assert_ne!(pattern_a, pattern_b);
1751
1752        sv_set_scalar(h_a, slot, pattern_a);
1753        sv_set_scalar(h_b, slot, pattern_b);
1754
1755        assert_eq!(sv_get_scalar(h_a, slot), pattern_a);
1756        assert_eq!(sv_get_scalar(h_b, slot), pattern_b);
1757
1758        // SAFETY: reclaim the two handles this test leaked via
1759        // `into_sim_handle`, exactly as `from_sim_handle`'s own `# Safety`
1760        // section documents no wave-A caller does for a live simulation run
1761        // -- safe here only because this test performs no further access to
1762        // either handle afterward.
1763        unsafe {
1764            drop(Box::from_raw(h_a.0 as *mut InstanceHandle));
1765            drop(Box::from_raw(h_b.0 as *mut InstanceHandle));
1766        }
1767    }
1768
1769    /// ST-021: write the same value twice via `sv_set_scalar`; the first
1770    /// write (different from the slot's zero-initial value) must register a
1771    /// change in the same VC buffer `commit_write` appends to, and the
1772    /// second (identical value) must not. Fails if `sv_set_scalar` wrote
1773    /// through a path that never touches `commit_write_at`.
1774    #[test]
1775    fn sv_set_scalar_reports_change_via_the_same_primitive_as_signal_access() {
1776        let tmpl = TemplateKey {
1777            definition: 1,
1778            params_hash: 0,
1779        };
1780        let mut engine = engine_with_template("module m; logic [7:0] x; endmodule\n", tmpl);
1781        let handle = engine.allocate_instance(tmpl).expect("template registered");
1782        let cell = handle.cell_for_slot(0);
1783        let h = handle.into_sim_handle();
1784        let slot = SlotId(0);
1785
1786        sv_set_scalar(h, slot, narrow_word(1, 0));
1787        let after_first = last_vc_record_for(cell);
1788        assert!(
1789            after_first.is_some(),
1790            "the first write (from the zero default) must register a change"
1791        );
1792
1793        sv_set_scalar(h, slot, narrow_word(1, 0));
1794        let after_second = last_vc_record_for(cell).expect("still the first write's record");
1795        match after_second.new {
1796            FourStateValue::Narrow(w) => assert_eq!(
1797                w,
1798                narrow_word(1, 0),
1799                "the identical second write must not append a new record"
1800            ),
1801            FourStateValue::Wide { .. } => panic!("expected Narrow"),
1802        }
1803
1804        // SAFETY: see the previous test's identical reclaim comment.
1805        unsafe {
1806            drop(Box::from_raw(h.0 as *mut InstanceHandle));
1807        }
1808    }
1809
1810    /// ST-022, ST-023: a template with one 128-bit wide member
1811    /// (`elem_word_count == 2`); 2 instances; `sv_set_wide` a distinct
1812    /// pattern per instance -- on *both* the `aval` and `bval` (unknown-bit)
1813    /// planes, since ST-022/ST-023's own requirement text names "the
1814    /// aval/bval limb planes" explicitly -- and `sv_get_wide` on the other
1815    /// instance must never reflect the first instance's pattern on either
1816    /// plane (reads its own, independently-based words) -- the wide-plane
1817    /// analogue of the scalar round-trip test. Review fix (r2): the
1818    /// original version of this test used `bval: [0, 0]` for every instance
1819    /// and never read `view.bval` back at all, so a `sv_set_wide` that
1820    /// silently dropped/zeroed the caller's `bval` (e.g. a copy-paste bug
1821    /// writing `aval` into both planes) would still pass; asserting on
1822    /// `bval` here as rigorously as `aval` closes that gap.
1823    #[test]
1824    fn sv_get_set_wide_slices_the_correct_word_range_per_instance() {
1825        let tmpl = TemplateKey {
1826            definition: 2,
1827            params_hash: 0,
1828        };
1829        let mut engine = engine_with_template("module m; logic [127:0] w; endmodule\n", tmpl);
1830
1831        let handle_a = engine.allocate_instance(tmpl).expect("template registered");
1832        let handle_b = engine.allocate_instance(tmpl).expect("template registered");
1833        let h_a = handle_a.into_sim_handle();
1834        let h_b = handle_b.into_sim_handle();
1835        let slot = SlotId(0);
1836
1837        let mut aval_a = [0xAAAA_AAAA_AAAA_AAAAu64, 0xBBBB_BBBB_BBBB_BBBBu64];
1838        let mut bval_a = [0x1111_1111_1111_1111u64, 0x2222_2222_2222_2222u64];
1839        let pattern_a = WideView {
1840            aval: aval_a.as_mut_ptr(),
1841            bval: bval_a.as_mut_ptr(),
1842            word_count: 2,
1843        };
1844        sv_set_wide(h_a, slot, pattern_a);
1845
1846        let mut aval_b = [0xCCCC_CCCC_CCCC_CCCCu64, 0xDDDD_DDDD_DDDD_DDDDu64];
1847        let mut bval_b = [0x3333_3333_3333_3333u64, 0x4444_4444_4444_4444u64];
1848        let pattern_b = WideView {
1849            aval: aval_b.as_mut_ptr(),
1850            bval: bval_b.as_mut_ptr(),
1851            word_count: 2,
1852        };
1853        sv_set_wide(h_b, slot, pattern_b);
1854
1855        let view_a = sv_get_wide(h_a, slot);
1856        // SAFETY: `view_a` was just returned by `sv_get_wide` above, backed
1857        // by this same test's still-live `engine`/`handle_a`; `word_count`
1858        // words are valid to read per `sv_get_wide`'s own contract, on both
1859        // the `aval` and `bval` planes.
1860        let read_a_aval = unsafe { std::slice::from_raw_parts(view_a.aval, 2) };
1861        let read_a_bval = unsafe { std::slice::from_raw_parts(view_a.bval, 2) };
1862        assert_eq!(read_a_aval, [aval_a[0], aval_a[1]]);
1863        assert_eq!(read_a_bval, [bval_a[0], bval_a[1]]);
1864        assert_ne!(
1865            read_a_aval,
1866            [aval_b[0], aval_b[1]],
1867            "instance A's aval read must never reflect instance B's pattern"
1868        );
1869        assert_ne!(
1870            read_a_bval,
1871            [bval_b[0], bval_b[1]],
1872            "instance A's bval read must never reflect instance B's pattern"
1873        );
1874
1875        let view_b = sv_get_wide(h_b, slot);
1876        // SAFETY: same reasoning as above, applied to `handle_b`.
1877        let read_b_aval = unsafe { std::slice::from_raw_parts(view_b.aval, 2) };
1878        let read_b_bval = unsafe { std::slice::from_raw_parts(view_b.bval, 2) };
1879        assert_eq!(read_b_aval, [aval_b[0], aval_b[1]]);
1880        assert_eq!(read_b_bval, [bval_b[0], bval_b[1]]);
1881
1882        // SAFETY: see the first test's identical reclaim comment.
1883        unsafe {
1884            drop(Box::from_raw(h_a.0 as *mut InstanceHandle));
1885            drop(Box::from_raw(h_b.0 as *mut InstanceHandle));
1886        }
1887    }
1888
1889    /// ST-023 (review fix: the wide-tier sibling of
1890    /// `sv_set_scalar_reports_change_via_the_same_primitive_as_signal_access`,
1891    /// since the slicing test above only checks value round-trip, never that
1892    /// a change was actually detected/recorded). `sv_set_wide` with a
1893    /// pattern different from the slot's zero-initial value must register a
1894    /// change; the identical pattern written again must not append a new
1895    /// record. Fails if `sv_set_wide` were a bare write into
1896    /// `arena.wide_aval`/`wide_bval` that never calls into
1897    /// `commit_write_at`'s diff/append logic at all.
1898    ///
1899    /// Review fix (r2): the pattern's `bval` is now nonzero (was `[0, 0]`,
1900    /// indistinguishable from a dropped/zeroed `bval`), and the read-back
1901    /// `VcRecord::new` is asserted on its `bval` plane exactly as its `aval`
1902    /// plane already was (was destructured with `bval: ..`, discarding it)
1903    /// -- so a `sv_set_wide` that silently drops or zeroes the caller's
1904    /// `bval` before it ever reaches `commit_write_at` now fails this test.
1905    #[test]
1906    fn sv_set_wide_reports_change_via_the_same_primitive_as_signal_access() {
1907        let tmpl = TemplateKey {
1908            definition: 3,
1909            params_hash: 0,
1910        };
1911        let mut engine = engine_with_template("module m; logic [127:0] w; endmodule\n", tmpl);
1912        let handle = engine.allocate_instance(tmpl).expect("template registered");
1913        let cell = handle.cell_for_slot(0);
1914        let h = handle.into_sim_handle();
1915        let slot = SlotId(0);
1916
1917        let mut aval = [0x1111_1111_1111_1111u64, 0x2222_2222_2222_2222u64];
1918        let mut bval = [0x5555_5555_5555_5555u64, 0x6666_6666_6666_6666u64];
1919        let pattern = WideView {
1920            aval: aval.as_mut_ptr(),
1921            bval: bval.as_mut_ptr(),
1922            word_count: 2,
1923        };
1924
1925        sv_set_wide(h, slot, pattern);
1926        let after_first = last_vc_record_for(cell);
1927        assert!(
1928            after_first.is_some(),
1929            "the first wide write (from the zero default) must register a change"
1930        );
1931        match after_first.expect("checked is_some above").new {
1932            FourStateValue::Wide { aval: a, bval: b } => {
1933                assert_eq!(a, vec![aval[0], aval[1]]);
1934                assert_eq!(b, vec![bval[0], bval[1]]);
1935            }
1936            FourStateValue::Narrow(_) => panic!("expected Wide"),
1937        }
1938
1939        // Reissue the identical pattern (same backing arrays, unchanged).
1940        let pattern_again = WideView {
1941            aval: aval.as_mut_ptr(),
1942            bval: bval.as_mut_ptr(),
1943            word_count: 2,
1944        };
1945        sv_set_wide(h, slot, pattern_again);
1946        let after_second = last_vc_record_for(cell).expect("still the first write's record");
1947        match after_second.new {
1948            FourStateValue::Wide { aval: a, bval: b } => {
1949                assert_eq!(
1950                    a,
1951                    vec![aval[0], aval[1]],
1952                    "the identical second write must not append a new record"
1953                );
1954                assert_eq!(
1955                    b,
1956                    vec![bval[0], bval[1]],
1957                    "the identical second write must not append a new record"
1958                );
1959            }
1960            FourStateValue::Narrow(_) => panic!("expected Wide"),
1961        }
1962
1963        // SAFETY: see the first test's identical reclaim comment.
1964        unsafe {
1965            drop(Box::from_raw(h.0 as *mut InstanceHandle));
1966        }
1967    }
1968
1969    /// ST-013: for one instance, write a value via `sv_set_scalar` (the ABI
1970    /// path) then read it back via `StateEngine`'s `SignalAccess::read` on
1971    /// the `StateCellId` Task 8 mints for that same concrete signal --
1972    /// proving STATE has exactly one canonical storage location reachable
1973    /// both ways, never two.
1974    #[test]
1975    fn signal_access_read_write_agree_with_the_accessor_fns_on_the_same_storage() {
1976        let tmpl = TemplateKey {
1977            definition: 4,
1978            params_hash: 0,
1979        };
1980        let mut engine = engine_with_template("module m; logic [7:0] x; endmodule\n", tmpl);
1981        let handle = engine.allocate_instance(tmpl).expect("template registered");
1982        let cell = handle.cell_for_slot(0);
1983        let h = handle.into_sim_handle();
1984        let slot = SlotId(0);
1985
1986        sv_set_scalar(h, slot, narrow_word(0x7B, 0));
1987
1988        match SignalAccess::read(&engine, cell) {
1989            FourStateValue::Narrow(w) => assert_eq!(w, narrow_word(0x7B, 0)),
1990            FourStateValue::Wide { .. } => panic!("expected Narrow"),
1991        }
1992
1993        // SAFETY: see the first test's identical reclaim comment.
1994        unsafe {
1995            drop(Box::from_raw(h.0 as *mut InstanceHandle));
1996        }
1997    }
1998
1999    /// Review fix (Task 9 r3, blocking finding 1): `sv_get_wide`'s returned
2000    /// `WideView` aliases `Arena::wide_aval`/`wide_bval`'s own live storage
2001    /// and is retained by the caller past the call -- before this fix, a
2002    /// later `allocate_instance` call for the same (or any) wide-slot
2003    /// template could grow those planes via `Arena::alloc_wide_words` enough
2004    /// to reallocate their backing `Vec<u64>`s, silently dangling every
2005    /// previously issued `WideView`'s `aval`/`bval` pointers (a genuine
2006    /// use-after-free with no compiler diagnostic -- confirmed via an
2007    /// isolated Miri repro of this exact pattern, per the finding). Allocate
2008    /// one wide-slot instance, obtain a `WideView` via `sv_get_wide`, then
2009    /// call `allocate_instance` again for the same template -- this must now
2010    /// panic (`Arena::alloc_wide_words`'s new seal check firing) rather than
2011    /// silently succeeding and leaving `view`'s pointers dangling. Mirrors
2012    /// `instance.rs`'s own `allocate_instance_called_many_more_times_leaves_
2013    /// the_first_handles_arena_pointer_valid` test's "force further growth,
2014    /// then read back through the first handle" shape, except here the
2015    /// correct outcome is a loud panic, not a successful read: no
2016    /// stable-address storage exists for the wide planes (see
2017    /// `Arena::wide_sealed`'s doc comment for why the `TemplateRegistry`
2018    /// per-entry-boxing fix doesn't apply to them), so the chosen fix is an
2019    /// enforced phase boundary instead.
2020    #[test]
2021    #[should_panic(expected = "refusing to grow the wide plane")]
2022    fn sv_get_wide_view_survives_no_further_allocate_instance_call_it_must_panic_not_dangle() {
2023        let tmpl = TemplateKey {
2024            definition: 5,
2025            params_hash: 0,
2026        };
2027        let mut engine = engine_with_template("module m; logic [127:0] w; endmodule\n", tmpl);
2028
2029        let handle = engine.allocate_instance(tmpl).expect("template registered");
2030        let h = handle.into_sim_handle();
2031        let slot = SlotId(0);
2032
2033        // Obtain a WideView that (per sv_get_wide's own contract, and this
2034        // module's own doc comment above) aliases the arena's live
2035        // wide_aval/wide_bval storage.
2036        let view = sv_get_wide(h, slot);
2037        assert_eq!(view.word_count, 2);
2038
2039        // Any further wide-slot allocation must now be refused -- it would
2040        // otherwise be free to reallocate the very buffer `view` points
2041        // into. This call is expected to panic before returning.
2042        let _ = engine.allocate_instance(tmpl);
2043
2044        // Unreachable: the panic above must fire first. Left here (rather
2045        // than omitted) so that if the seal is ever accidentally weakened to
2046        // a silent no-op instead of a panic, this test still fails loudly
2047        // (via the leaked, un-reclaimed `h`/`view` triggering nothing
2048        // useful) rather than passing by accident through an early return.
2049        unreachable!("allocate_instance after sv_get_wide must panic, not return");
2050    }
2051
2052    /// Review fix (Task 9 r4, blocking finding): `sv_get_wide`'s returned
2053    /// `WideView` pointers are Undefined Behavior to dereference under
2054    /// Stacked Borrows as soon as *any* subsequent `Arena::wide_aval_mut`/
2055    /// `wide_bval_mut` reborrow happens anywhere in the same `Arena` --
2056    /// which the finding showed fires on the very next `sv_get_wide` call
2057    /// for a *different* instance/slot (no write required at all), or on
2058    /// the next wide-class `commit_write_at` write to any wide signal. The
2059    /// Task 9 r3 fix (`Arena::wide_sealed`/`seal_wide_plane`) only prevents
2060    /// the backing `Vec` from *reallocating* -- a real but different hazard
2061    /// -- and does nothing about this separate, far more easily triggered
2062    /// reborrow-invalidation hazard.
2063    ///
2064    /// This test exercises both of the finding's exact trigger shapes
2065    /// against a real `sv_get_wide`-returned `WideView`, then reads back
2066    /// through the *first* view -- the shape `sv_get_set_wide_slices_the_
2067    /// correct_word_range_per_instance` never covered, since it always
2068    /// fully consumed `view_a` before `view_b` ever came into existence.
2069    /// Built via [`engine_with_manual_wide_template`] (no `compile_source`/
2070    /// `sv_lang` FFI) specifically so it can run under Miri:
2071    ///
2072    /// ```text
2073    /// CARGO_TARGET_DIR=/mnt/bulk/sv-sim-target-wA-state \
2074    ///   rustup run nightly cargo miri test -p sv-sim-sim \
2075    ///   sv_get_wide_view_stays_valid_across_a_later_sv_get_wide_and_a_later_wide_class_write
2076    /// ```
2077    ///
2078    /// Before the Task 9 r4 fix (raw-base-pointer construction in
2079    /// `Arena::wide_aval_ptr`/`wide_bval_ptr`, used by `sv_get_wide` instead
2080    /// of `wide_aval_mut()[start..].as_mut_ptr()`), the above Miri
2081    /// invocation reproduced exactly the "tag does not exist in the borrow
2082    /// stack for this location" error the finding described, at the
2083    /// `view_a` read-backs below. Under `cargo test` (no Miri) this test
2084    /// also passes before the fix -- the UB is invisible without Miri,
2085    /// which is exactly why the fix needs Miri to validate, not just this
2086    /// test's ordinary pass/fail.
2087    #[test]
2088    fn sv_get_wide_view_stays_valid_across_a_later_sv_get_wide_and_a_later_wide_class_write() {
2089        let tmpl = TemplateKey {
2090            definition: 6,
2091            params_hash: 0,
2092        };
2093        let mut engine = engine_with_manual_wide_template(tmpl, 2);
2094
2095        // Both instances are allocated up front, before any sv_get_wide
2096        // call -- exactly like sv_get_set_wide_slices_the_correct_word_
2097        // range_per_instance above -- so `seal_wide_plane` (fired by the
2098        // first sv_get_wide call below) never blocks either allocation.
2099        let handle_a = engine.allocate_instance(tmpl).expect("template registered");
2100        let handle_b = engine.allocate_instance(tmpl).expect("template registered");
2101        let h_a = handle_a.into_sim_handle();
2102        let h_b = handle_b.into_sim_handle();
2103        let slot = SlotId(0);
2104
2105        // Seed instance A's wide slot with a known pattern via the ordinary
2106        // write path, then retain a WideView onto it via sv_get_wide -- the
2107        // exact "aliasing view retained past the call" shape the finding
2108        // describes.
2109        let mut seed_aval = [0x1111_1111_1111_1111u64, 0x2222_2222_2222_2222u64];
2110        let mut seed_bval = [0x3333_3333_3333_3333u64, 0x4444_4444_4444_4444u64];
2111        sv_set_wide(
2112            h_a,
2113            slot,
2114            WideView {
2115                aval: seed_aval.as_mut_ptr(),
2116                bval: seed_bval.as_mut_ptr(),
2117                word_count: 2,
2118            },
2119        );
2120        let view_a = sv_get_wide(h_a, slot);
2121        assert_eq!(view_a.word_count, 2);
2122
2123        // Finding scenario (b): a second, read-only-shaped `sv_get_wide`
2124        // call for a *different* instance's slot, with zero intervening
2125        // writes. Before the fix, this alone invalidated `view_a`'s
2126        // pointers under Miri.
2127        let view_b_probe = sv_get_wide(h_b, slot);
2128        assert_eq!(view_b_probe.word_count, 2);
2129
2130        // `view_a` must still be dereferenceable and correct afterward.
2131        // SAFETY: `view_a` aliases instance A's own wide-plane words,
2132        // reserved for it (Task 7's disjoint-base guarantee) and still live
2133        // for this same test's `engine`/`handle_a`.
2134        unsafe {
2135            assert_eq!(std::slice::from_raw_parts(view_a.aval, 2), seed_aval);
2136            assert_eq!(std::slice::from_raw_parts(view_a.bval, 2), seed_bval);
2137        }
2138
2139        // Finding scenario (a): a later wide-class write to a *different*
2140        // wide signal (instance B's slot) via `sv_set_wide` -- reaches
2141        // `write::write_slot`'s `wide_aval_mut`/`wide_bval_mut`-based arm.
2142        // Before the fix, this also invalidated `view_a`'s pointers under
2143        // Miri.
2144        let mut other_aval = [0x5555_5555_5555_5555u64, 0x6666_6666_6666_6666u64];
2145        let mut other_bval = [0x7777_7777_7777_7777u64, 0x8888_8888_8888_8888u64];
2146        sv_set_wide(
2147            h_b,
2148            slot,
2149            WideView {
2150                aval: other_aval.as_mut_ptr(),
2151                bval: other_bval.as_mut_ptr(),
2152                word_count: 2,
2153            },
2154        );
2155
2156        // `view_a` must STILL be valid, readable, and writable through its
2157        // own raw pointers after that unrelated write -- and a fresh
2158        // `sv_get_wide` call must observe a write made directly through the
2159        // retained view, proving it still addresses the arena's real live
2160        // storage.
2161        // SAFETY: same reasoning as above; instance B's own disjoint base
2162        // means its write never touches instance A's words.
2163        unsafe {
2164            assert_eq!(std::slice::from_raw_parts(view_a.aval, 2), seed_aval);
2165            assert_eq!(std::slice::from_raw_parts(view_a.bval, 2), seed_bval);
2166            *view_a.aval.add(0) = 0x9999_9999_9999_9999;
2167        }
2168        let view_a_again = sv_get_wide(h_a, slot);
2169        // SAFETY: a fresh view over the same still-live slot.
2170        unsafe {
2171            assert_eq!(*view_a_again.aval, 0x9999_9999_9999_9999);
2172        }
2173
2174        // SAFETY: reclaim the two handles this test leaked via
2175        // `into_sim_handle`, exactly as this module's other tests do.
2176        unsafe {
2177            drop(Box::from_raw(h_a.0 as *mut InstanceHandle));
2178            drop(Box::from_raw(h_b.0 as *mut InstanceHandle));
2179        }
2180    }
2181}