Skip to main content

sv_sim_sim/state/
huge_mirror.rs

1//! Windowed commits for huge (`> 65535`-bit) signals (lane `wide-commit`).
2//!
3//! A compiled process keeps a *shadow* of every huge signal it touches: the
4//! PK image (`pk_words(N) = ceil(2N/64)` words, the val plane at bit 0 and the
5//! unk plane at bit `N`) that `sv_rt_wide` selects/inserts into. Before this
6//! module existed, each segment re-primed the shadow from the arena planes
7//! (`O(N)`), and every write through the shadow was committed by converting
8//! the whole image back to planes and diffing the whole value (`O(N)` again,
9//! twice over), even for a one-bit write.
10//!
11//! The arena now keeps, per huge signal, one long-lived **mirror**: the PK
12//! image itself, at a stable heap address, plus a *dirty window* `[lo, hi)` of
13//! value bits the compiled code has written since the last commit.
14//!
15//! Invariant: **a valid mirror equals the planes outside its dirty window.**
16//!
17//! * [`huge_prime`] returns the mirror's image (what the shadow used to be).
18//!   A valid mirror is returned as is (no copy); an invalid one -- or one the
19//!   caller asked to rebuild after external code may have written the planes
20//!   -- is rebuilt from the planes, `O(N)`, once.
21//! * [`huge_mark`] widens the dirty window to cover bits the caller wrote to
22//!   the image.
23//! * [`huge_commit`] converts only the words the window covers back to
24//!   planes, compares them with what the planes hold (the whole-value
25//!   "did anything change" test, restricted to the only words that can have
26//!   changed), stores them and reports the lsb transition for edge
27//!   classification. A clean window is a no-op.
28//! * Every other writer of the planes ([`super::write::write_slot`],
29//!   `rollback::restore`) invalidates the mirrors it overlaps, so a stale
30//!   image is never trusted. The commit's own plane stores bypass that hook.
31//!
32//! The windowed path replaces `commit_write_at` only when `commit_write_at`
33//! would do nothing the window skips: no force filter for this cell, no
34//! value-change record. Otherwise the whole image is committed through the
35//! normal path (the pre-existing behaviour, `O(N)`).
36//!
37//! `SV_SIM_HUGE_VERIFY=1` makes every prime and every commit check the whole
38//! mirror against the planes and abort on a divergence -- a missed `mark` or a
39//! missed invalidation would otherwise lose a write silently.
40
41use std::cell::{Cell, RefCell};
42use std::collections::BTreeMap;
43
44use sv_sim_abi::{Bit4, SimHandle, SlotId};
45
46use super::accessor_impl::{resolve_foreign_slot, slot_location_for};
47use super::instance::InstanceHandle;
48use super::write::SlotLocation;
49use super::{Arena, ChangeDelta, FourStateValue, SlotClass, StateCellId};
50
51/// One huge signal's long-lived PK image and its dirty window.
52struct HugeMirror {
53    /// The signal's bit width `N`.
54    width: u32,
55    /// The PK image: `ceil(2N/64)` words plus two zero guard words (the word
56    /// reader looks one word past the end for an unaligned tail).
57    pk: Box<[u64]>,
58    /// Whether the image reflects the planes outside the dirty window.
59    valid: bool,
60    /// The value bits `[dirty_lo, dirty_hi)` written since the last commit;
61    /// empty when `dirty_lo >= dirty_hi`.
62    dirty_lo: u32,
63    dirty_hi: u32,
64}
65
66impl HugeMirror {
67    fn new(width: u32) -> Self {
68        let words = (2 * width as usize).div_ceil(64) + 2;
69        HugeMirror {
70            width,
71            pk: vec![0u64; words].into_boxed_slice(),
72            valid: false,
73            dirty_lo: 0,
74            dirty_hi: 0,
75        }
76    }
77
78    fn clear_dirty(&mut self) {
79        self.dirty_lo = 0;
80        self.dirty_hi = 0;
81    }
82
83    fn dirty(&self) -> bool {
84        self.dirty_lo < self.dirty_hi
85    }
86
87    fn plane_words(&self) -> usize {
88        (self.width as usize).div_ceil(64)
89    }
90}
91
92/// The arena's registry of huge-signal mirrors, keyed by the signal's absolute
93/// wide-plane word offset.
94pub(crate) struct HugeMirrors {
95    map: RefCell<BTreeMap<u32, HugeMirror>>,
96    /// `map.len()`, so the write hook's common case (no mirror at all) is one
97    /// load.
98    count: Cell<usize>,
99    /// `SV_SIM_HUGE_VERIFY` was set when the arena was built: every prime and
100    /// commit checks the whole mirror against the planes.
101    verify: bool,
102}
103
104impl Default for HugeMirrors {
105    fn default() -> Self {
106        HugeMirrors {
107            map: RefCell::default(),
108            count: Cell::new(0),
109            verify: std::env::var_os("SV_SIM_HUGE_VERIFY").is_some_and(|v| v != "0"),
110        }
111    }
112}
113
114impl HugeMirrors {
115    /// Marks every mirror overlapping the wide-plane words
116    /// `[start, start + words)` invalid: some writer other than the commit
117    /// just changed them.
118    pub(crate) fn invalidate_range(&self, start: usize, words: usize) {
119        if self.count.get() == 0 || words == 0 {
120            return;
121        }
122        let end = start + words;
123        let mut map = self.map.borrow_mut();
124        // Mirrors never overlap each other, so walking down from the last key
125        // below `end`, the first one that ends at or before `start` ends the
126        // overlap.
127        for (&key, m) in map.range_mut(..end as u32).rev() {
128            if key as usize + m.plane_words() <= start {
129                break;
130            }
131            m.valid = false;
132        }
133    }
134
135    /// Marks every mirror invalid (`rollback::restore` rewrites the planes
136    /// wholesale).
137    pub(crate) fn invalidate_all(&self) {
138        if self.count.get() == 0 {
139            return;
140        }
141        for m in self.map.borrow_mut().values_mut() {
142            m.valid = false;
143        }
144    }
145}
146
147fn low_mask(n: usize) -> u64 {
148    if n >= 64 { u64::MAX } else { (1u64 << n) - 1 }
149}
150
151/// Writes the low `n <= 64` bits of `v` into `dst` at bit `pos`.
152fn put_word(dst: &mut [u64], pos: usize, v: u64, n: usize) {
153    let wi = pos / 64;
154    let bo = pos % 64;
155    let m = low_mask(n);
156    let v = v & m;
157    dst[wi] = (dst[wi] & !(m << bo)) | (v << bo);
158    if bo + n > 64 {
159        dst[wi + 1] = (dst[wi + 1] & !(m >> (64 - bo))) | (v >> (64 - bo));
160    }
161}
162
163/// `len <= 64` bits of `img` from bit `pos`.
164fn pk_word(img: &[u64], pos: usize, len: usize) -> u64 {
165    let (wi, bo) = (pos / 64, pos % 64);
166    let lo = img.get(wi).copied().unwrap_or(0) >> bo;
167    let hi = if bo == 0 {
168        0
169    } else {
170        img.get(wi + 1).copied().unwrap_or(0) << (64 - bo)
171    };
172    (lo | hi) & low_mask(len)
173}
174
175/// The IEEE planes' word `i` of the value held by `img`.
176fn planes_word(img: &[u64], w: usize, i: usize) -> (u64, u64) {
177    let len = (w - i * 64).min(64);
178    let v = pk_word(img, i * 64, len);
179    let u = pk_word(img, w + i * 64, len);
180    (v ^ u, u)
181}
182
183/// The bits of plane word `i` that the value bits `[lo, hi)` cover.
184fn window_mask(lo: usize, hi: usize, i: usize) -> u64 {
185    let base = i * 64;
186    let wl = lo.saturating_sub(base).min(64);
187    let wh = hi.saturating_sub(base).min(64);
188    if wh <= wl {
189        return 0;
190    }
191    low_mask(wh) & !low_mask(wl)
192}
193
194/// Rebuilds the whole image from the planes at `start` (the signal's absolute
195/// word offset) and clears the window.
196fn rebuild(arena: &Arena, start: usize, m: &mut HugeMirror) {
197    let w = m.width as usize;
198    let n = m.plane_words();
199    let (a, b) = (arena.wide_aval(), arena.wide_bval());
200    m.pk.fill(0);
201    for i in 0..n {
202        let len = (w - i * 64).min(64);
203        let (av, bv) = (a[start + i], b[start + i]);
204        put_word(&mut m.pk, i * 64, av ^ bv, len);
205        put_word(&mut m.pk, w + i * 64, bv, len);
206    }
207    m.valid = true;
208    m.clear_dirty();
209}
210
211/// `SV_SIM_HUGE_VERIFY`: the whole mirror must equal the planes.
212fn verify_matches(arena: &Arena, start: usize, m: &HugeMirror, what: &str) {
213    let w = m.width as usize;
214    let (a, b) = (arena.wide_aval(), arena.wide_bval());
215    for i in 0..m.plane_words() {
216        let len = (w - i * 64).min(64);
217        let mask = low_mask(len);
218        let (na, nb) = planes_word(&m.pk, w, i);
219        let (oa, ob) = (a[start + i], b[start + i]);
220        assert!(
221            ((oa ^ na) | (ob ^ nb)) & mask == 0,
222            "SV_SIM_HUGE_VERIFY ({what}): huge signal at wide word {start} (width {w}) \
223             diverges from its mirror in plane word {i}: planes ({oa:#x},{ob:#x}) vs \
224             mirror ({na:#x},{nb:#x}); dirty window [{}, {})",
225            m.dirty_lo,
226            m.dirty_hi
227        );
228    }
229}
230
231/// The signal `(handle, slot)` resolved to its owner: the owner's handle, the
232/// owner's absolute location.
233fn locate(handle: SimHandle, slot: SlotId) -> (&'static InstanceHandle, SlotLocation) {
234    let (owner, oslot) = resolve_foreign_slot(handle, slot);
235    // SAFETY: see `accessor_impl::sv_get_scalar` -- every handle a compiled
236    // kernel passes was minted by `into_sim_handle` and lives for the whole
237    // run, which is what makes `'static` sound here.
238    let ih: &'static InstanceHandle = unsafe { InstanceHandle::from_sim_handle(owner) };
239    (ih, slot_location_for(ih, oslot))
240}
241
242/// The process shadow of the huge signal `(handle, slot)` of `width` bits: the
243/// signal's mirror, valid and current. `force_rebuild` re-reads the planes
244/// even from a valid mirror -- for the caller that knows external code wrote
245/// them behind the mirror's back.
246///
247/// The returned image is `ceil(2*width/64)` words (plus guards) at an address
248/// that never changes for the life of the arena.
249pub fn huge_prime(handle: SimHandle, slot: SlotId, width: u32, force_rebuild: bool) -> *mut u64 {
250    let (ih, loc) = locate(handle, slot);
251    debug_assert_eq!(loc.class, SlotClass::Wide);
252    debug_assert_eq!(loc.bit_width, width);
253    let arena = ih.arena();
254    let start = loc.word_offset as usize;
255    let mut map = arena.huge.map.borrow_mut();
256    let m = map.entry(loc.word_offset).or_insert_with(|| {
257        arena.huge.count.set(arena.huge.count.get() + 1);
258        HugeMirror::new(width)
259    });
260    debug_assert_eq!(m.width, width);
261    if !m.valid || force_rebuild {
262        rebuild(arena, start, m);
263    } else if arena.huge.verify {
264        verify_matches(arena, start, m, "prime");
265    }
266    m.pk.as_mut_ptr()
267}
268
269/// Widens the dirty window of `(handle, slot)`'s mirror by the value bits
270/// `[start, start + len)` (clipped to the signal; `len <= 0` is a no-op, which
271/// is what an out-of-range select that wrote nothing passes).
272pub fn huge_mark(handle: SimHandle, slot: SlotId, start: i64, len: i64) {
273    if len <= 0 {
274        return;
275    }
276    let (ih, loc) = locate(handle, slot);
277    let arena = ih.arena();
278    let mut map = arena.huge.map.borrow_mut();
279    let Some(m) = map.get_mut(&loc.word_offset) else {
280        return;
281    };
282    let lo = i128::from(start).max(0);
283    let hi = (i128::from(start) + i128::from(len)).min(i128::from(m.width));
284    if lo >= hi {
285        return;
286    }
287    let (lo, hi) = (lo as u32, hi as u32);
288    if m.dirty() {
289        m.dirty_lo = m.dirty_lo.min(lo);
290        m.dirty_hi = m.dirty_hi.max(hi);
291    } else {
292        m.dirty_lo = lo;
293        m.dirty_hi = hi;
294    }
295}
296
297/// What [`huge_commit`] decided; the caller (`sched::kernel_adapter`, which
298/// owns the scheduler hooks this crate layer cannot name) carries it out.
299pub enum HugeCommit {
300    /// Nothing was dirty.
301    Nothing,
302    /// The window's words were stored and nothing changed.
303    Unchanged,
304    /// The window's words were stored and the value changed; `old`/`new` are
305    /// bit 0 before and after (the only bit an edge event looks at).
306    Changed {
307        cell: StateCellId,
308        old: Bit4,
309        new: Bit4,
310        /// The window's old and new plane words, when `wants_delta` asked
311        /// for them (a process waits on a select of this signal).
312        delta: Option<ChangeDelta>,
313    },
314    /// The window could not be committed on its own (a forced cell or a
315    /// change log): commit `value` (the whole signal) through
316    /// `SchedulerOps::commit_blocking_write`, then call
317    /// [`huge_commit_full_done`].
318    Full {
319        cell: StateCellId,
320        value: FourStateValue,
321        /// Whether the mirror is still current once that write is done.
322        revalidate: bool,
323    },
324}
325
326/// Commits the dirty window of `(handle, slot)`'s mirror to the planes.
327pub fn huge_commit(
328    handle: SimHandle,
329    slot: SlotId,
330    width: u32,
331    wants_delta: &dyn Fn(StateCellId) -> bool,
332) -> HugeCommit {
333    let (ih, loc) = locate(handle, slot);
334    let arena = ih.arena();
335    let start = loc.word_offset as usize;
336    let mut map = arena.huge.map.borrow_mut();
337    let Some(m) = map.get_mut(&loc.word_offset) else {
338        return HugeCommit::Nothing;
339    };
340    if !m.dirty() {
341        return HugeCommit::Nothing;
342    }
343    debug_assert_eq!(m.width, width);
344    let w = m.width as usize;
345    let (lo, hi) = (m.dirty_lo as usize, m.dirty_hi as usize);
346    let (wlo, whi) = (lo / 64, (hi - 1) / 64);
347
348    // SAFETY: see `accessor_impl::sv_get_scalar`.
349    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
350    let cell = this.cell_for_slot(slot.0 as usize);
351    let forced = ih.vc().force.borrow().contains(cell);
352    if forced || ih.vc().records_changes() {
353        // `commit_write_at` filters through the force table and records the
354        // old/new values: do exactly that, over the whole value. The value is
355        // the planes overlaid with the mirror's window words when the mirror
356        // is stale, else the whole mirror.
357        let n = m.plane_words();
358        let (pa, pb) = (arena.wide_aval(), arena.wide_bval());
359        let mut aval = pa[start..start + n].to_vec();
360        let mut bval = pb[start..start + n].to_vec();
361        if m.valid {
362            for i in 0..n {
363                (aval[i], bval[i]) = planes_word(&m.pk, w, i);
364            }
365        } else {
366            // A stale mirror is right only inside its window.
367            for i in wlo..=whi {
368                let (a, b) = planes_word(&m.pk, w, i);
369                let k = window_mask(lo, hi, i);
370                aval[i] = (aval[i] & !k) | (a & k);
371                bval[i] = (bval[i] & !k) | (b & k);
372            }
373        }
374        m.clear_dirty();
375        return HugeCommit::Full {
376            cell,
377            value: FourStateValue::Wide { aval, bval },
378            revalidate: m.valid && !forced,
379        };
380    }
381
382    // SAFETY: `start + plane_words` is this signal's reserved range of the
383    // wide planes; the raw-pointer accessors never form a slice over the
384    // plane (see their doc comments), so retained `WideView`s stay valid.
385    let (ap, bp) = unsafe {
386        (
387            arena.wide_aval_ptr().add(start),
388            arena.wide_bval_ptr().add(start),
389        )
390    };
391    let mut changed = false;
392    let (mut old0, mut new0) = (Bit4::Zero, Bit4::Zero);
393    let mut delta = wants_delta(cell).then(|| ChangeDelta {
394        lo_word: wlo,
395        ..ChangeDelta::default()
396    });
397    for i in wlo..=whi {
398        let len = (w - i * 64).min(64);
399        let mask = low_mask(len);
400        let (na, nb) = planes_word(&m.pk, w, i);
401        // SAFETY: `i < plane_words()`, see above.
402        let (oa, ob) = unsafe { (ap.add(i).read(), bp.add(i).read()) };
403        // A stale mirror is right only inside its window.
404        let (na, nb) = if m.valid {
405            (na, nb)
406        } else {
407            let k = window_mask(lo, hi, i);
408            ((oa & !k) | (na & k), (ob & !k) | (nb & k))
409        };
410        if i == 0 {
411            old0 = Bit4::from_aval_bval(oa & 1 != 0, ob & 1 != 0);
412            new0 = Bit4::from_aval_bval(na & 1 != 0, nb & 1 != 0);
413        }
414        if ((oa ^ na) | (ob ^ nb)) & mask != 0 {
415            changed = true;
416        }
417        if let Some(d) = delta.as_mut() {
418            d.old_a.push(oa);
419            d.old_b.push(ob);
420            d.new_a.push(na);
421            d.new_b.push(nb);
422        }
423        if oa != na || ob != nb {
424            // SAFETY: as above; single logical writer.
425            unsafe {
426                ap.add(i).write(na);
427                bp.add(i).write(nb);
428            }
429        }
430    }
431    m.clear_dirty();
432    if arena.huge.verify && m.valid {
433        verify_matches(arena, start, m, "commit");
434    }
435    if changed {
436        HugeCommit::Changed {
437            cell,
438            old: old0,
439            new: new0,
440            delta,
441        }
442    } else {
443        HugeCommit::Unchanged
444    }
445}
446
447/// A nonblocking write to part of a huge signal, in plane words: `aval`/
448/// `bval`/`mask` cover the plane words from `window` on.
449pub struct NbaWindow {
450    pub cell: StateCellId,
451    pub window: u32,
452    pub aval: Vec<u64>,
453    pub bval: Vec<u64>,
454    pub mask: Vec<u64>,
455}
456
457/// The windowed form of `sig[start +: len] <= image`: `img` is the PK image of
458/// the `len`-bit right-hand side (val plane at bit 0, unk plane at bit `len`).
459/// The target bits `[start, start + len)` are clipped to the signal, as a
460/// part-select write clips; `None` when nothing is left (the write has no
461/// effect). Costs `O(len)`, never `O(width)`.
462pub fn huge_nba_window(
463    handle: SimHandle,
464    slot: SlotId,
465    width: u32,
466    start: i64,
467    len: i64,
468    img: &[u64],
469) -> Option<NbaWindow> {
470    let lo = i128::from(start).max(0);
471    let hi = (i128::from(start) + i128::from(len)).min(i128::from(width));
472    if len <= 0 || lo >= hi {
473        return None;
474    }
475    let (lo, hi) = (lo as usize, hi as usize);
476    let (start, len) = (start as i128, len as usize);
477    let (wlo, whi) = (lo / 64, (hi - 1) / 64);
478    let words = whi - wlo + 1;
479    let (mut aval, mut bval, mut mask) = (
480        Vec::with_capacity(words),
481        Vec::with_capacity(words),
482        Vec::with_capacity(words),
483    );
484    for i in wlo..=whi {
485        let base = i * 64;
486        let (s, e) = (lo.max(base), hi.min(base + 64));
487        let (n, sh) = (e - s, s - base);
488        let src = (s as i128 - start) as usize;
489        let v = pk_word(img, src, n);
490        let u = pk_word(img, len + src, n);
491        aval.push((v ^ u) << sh);
492        bval.push(u << sh);
493        mask.push(low_mask(n) << sh);
494    }
495    // SAFETY: see `accessor_impl::sv_get_scalar`.
496    let this = unsafe { InstanceHandle::from_sim_handle(handle) };
497    Some(NbaWindow {
498        cell: this.cell_for_slot(slot.0 as usize),
499        window: wlo as u32,
500        aval,
501        bval,
502        mask,
503    })
504}
505
506/// The plane words `[lo_word, lo_word + words)` of the huge signal at the
507/// wide-plane word offset `start`.
508pub(crate) fn read_window(
509    arena: &Arena,
510    start: usize,
511    lo_word: usize,
512    words: usize,
513) -> (Vec<u64>, Vec<u64>) {
514    let at = start + lo_word;
515    (
516        arena.wide_aval()[at..at + words].to_vec(),
517        arena.wide_bval()[at..at + words].to_vec(),
518    )
519}
520
521/// Stores the plane words `aval`/`bval` at `[lo_word, lo_word + aval.len())`
522/// of the huge signal whose planes begin at `start` (`width` bits), the
523/// windowed twin of a whole-value `commit_write_at` for the nonblocking
524/// commit. The caller has already ruled out a force filter and a change log.
525/// Returns whether any bit changed. The signal's mirror, if any, is brought
526/// up to date outside its dirty window so the invariant of this module keeps
527/// holding without an `O(N)` rebuild.
528pub(crate) fn store_window(
529    arena: &Arena,
530    start: usize,
531    width: u32,
532    lo_word: usize,
533    aval: &[u64],
534    bval: &[u64],
535) -> bool {
536    let w = width as usize;
537    let plane_words = w.div_ceil(64);
538    debug_assert_eq!(aval.len(), bval.len());
539    debug_assert!(lo_word + aval.len() <= plane_words);
540    // SAFETY: `start + plane_words` is this signal's reserved range of the
541    // wide planes; the raw-pointer accessors never form a slice over the
542    // plane, so retained `WideView`s stay valid.
543    let (ap, bp) = unsafe {
544        (
545            arena.wide_aval_ptr().add(start),
546            arena.wide_bval_ptr().add(start),
547        )
548    };
549    let mut changed = false;
550    for (j, (&na, &nb)) in aval.iter().zip(bval).enumerate() {
551        let i = lo_word + j;
552        let mask = low_mask((w - i * 64).min(64));
553        // SAFETY: `i < plane_words`, see above.
554        let (oa, ob) = unsafe { (ap.add(i).read(), bp.add(i).read()) };
555        if ((oa ^ na) | (ob ^ nb)) & mask != 0 {
556            changed = true;
557        }
558        if oa != na || ob != nb {
559            // SAFETY: as above; single logical writer.
560            unsafe {
561                ap.add(i).write(na);
562                bp.add(i).write(nb);
563            }
564        }
565    }
566    if changed {
567        let mut map = arena.huge.map.borrow_mut();
568        if let Some(m) = map.get_mut(&(start as u32)).filter(|m| m.valid) {
569            let (dlo, dhi) = if m.dirty() {
570                (m.dirty_lo as usize, m.dirty_hi as usize)
571            } else {
572                (0, 0)
573            };
574            for (j, (&na, &nb)) in aval.iter().zip(bval).enumerate() {
575                let i = lo_word + j;
576                let len = (w - i * 64).min(64);
577                // Bits the compiled code has written to the image but not
578                // yet committed keep their image value.
579                let keep = window_mask(dlo, dhi, i);
580                let (ca, cb) = planes_word(&m.pk, w, i);
581                let (ma, mb) = ((ca & keep) | (na & !keep), (cb & keep) | (nb & !keep));
582                put_word(&mut m.pk, i * 64, ma ^ mb, len);
583                put_word(&mut m.pk, w + i * 64, mb, len);
584            }
585            if arena.huge.verify && !m.dirty() {
586                verify_matches(arena, start, m, "nba");
587            }
588        }
589    }
590    changed
591}
592
593/// Called after the [`HugeCommit::Full`] write: the write itself went through
594/// `write_slot`, which invalidated the mirror; a mirror that is still current
595/// (`revalidate`) is made valid again.
596pub fn huge_commit_full_done(handle: SimHandle, slot: SlotId, revalidate: bool) {
597    if !revalidate {
598        return;
599    }
600    let (ih, loc) = locate(handle, slot);
601    let arena = ih.arena();
602    if let Some(m) = arena.huge.map.borrow_mut().get_mut(&loc.word_offset) {
603        m.valid = true;
604        m.clear_dirty();
605    }
606}