vj-sim 0.2.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! `ref` arguments bound to a window of a variable or class property
//! (LRM 1800-2023 13.5.2): `f(s.n)`, `f(v[3:0])`, `f(obj.x)`, `f(g)`.
//!
//! The callee accesses the actual directly, so a body that writes the same
//! storage through another path while it holds the `ref` (the module
//! variable, the class handle, a nested call) must see one location. A
//! compiled function holds such a `ref` as a reference id like an element
//! reference of a queue (`collections::ref_open`), but the id names a window
//! of a root image instead: the root's bytes in the caller's frame, or a
//! class property's words in the object heap. The image is the packed value
//! `(unk << W) | val` of codegen (a 2-state build: `val` alone).
//!
//! Ids carry [`WIN_BIT`] so they never collide with an element reference's.

use crate::objects::{self, FieldSlot};
use std::sync::{Mutex, OnceLock};

/// Set in the id of every window reference.
pub const WIN_BIT: u64 = 1 << 62;

#[derive(Clone, Copy)]
enum Root {
    /// A root image in memory (its address; valid until the call ends).
    Mem(usize),
    /// A class property: the object and the property's slot.
    Field(u64, FieldSlot),
}

#[derive(Clone, Copy)]
struct Win {
    root: Root,
    /// The image carries no unknown plane.
    two_state: bool,
}

#[derive(Default)]
struct Wins {
    slots: Vec<Option<Win>>,
    free: Vec<u32>,
}

fn table() -> &'static Mutex<Wins> {
    static T: OnceLock<Mutex<Wins>> = OnceLock::new();
    T.get_or_init(|| Mutex::new(Wins::default()))
}

/// Whether `id` names a window reference.
pub fn is_window(id: u64) -> bool {
    id & WIN_BIT != 0
}

fn push(w: Win) -> u64 {
    let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
    let k = match t.free.pop() {
        Some(k) => {
            t.slots[k as usize] = Some(w);
            k
        }
        None => {
            t.slots.push(Some(w));
            (t.slots.len() - 1) as u32
        }
    };
    WIN_BIT | (u64::from(k) + 1)
}

fn lookup(id: u64) -> Option<Win> {
    let t = table().lock().unwrap_or_else(|e| e.into_inner());
    let k = (id & !WIN_BIT).checked_sub(1)? as usize;
    t.slots.get(k).copied().flatten()
}

/// Opens a window reference on the root image at `addr`.
///
/// # Safety
/// The image (`ceil(bits / 8)` bytes, `bits` the root's packed width) must
/// stay valid and unaliased by Rust references until [`close`].
pub unsafe fn open_mem(addr: usize, two_state: bool) -> u64 {
    push(Win {
        root: Root::Mem(addr),
        two_state,
    })
}

/// Opens a window reference on the class property `slot` of object `h`.
pub fn open_field(h: u64, slot: FieldSlot, two_state: bool) -> u64 {
    push(Win {
        root: Root::Field(h, slot),
        two_state,
    })
}

/// Closes window reference `id`.
pub fn close(id: u64) {
    let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
    if let Some(k) = (id & !WIN_BIT).checked_sub(1).map(|k| k as usize)
        && t.slots.get_mut(k).and_then(Option::take).is_some()
    {
        t.free.push(k as u32);
    }
}

fn bit_at(words: &[u64], i: usize) -> bool {
    words.get(i / 64).is_some_and(|w| w >> (i % 64) & 1 != 0)
}

fn set_bit(words: &mut [u64], i: usize, v: bool) {
    if let Some(w) = words.get_mut(i / 64) {
        if v {
            *w |= 1 << (i % 64);
        } else {
            *w &= !(1 << (i % 64));
        }
    }
}

/// The root image's bit count for a `big_w`-bit value.
fn image_bits(win: Win, big_w: u32) -> usize {
    if win.two_state {
        big_w as usize
    } else {
        2 * big_w as usize
    }
}

/// Reads the root image as little-endian words (`None`: a dead object).
fn load(win: Win, big_w: u32) -> Option<Vec<u64>> {
    let bits = image_bits(win, big_w);
    match win.root {
        Root::Mem(addr) => {
            let mut words = vec![0u64; bits.div_ceil(64).max(1)];
            // SAFETY: `open_mem`'s contract: the image spans `ceil(bits / 8)` bytes.
            let bytes = unsafe { std::slice::from_raw_parts(addr as *const u8, bits.div_ceil(8)) };
            for (i, b) in bytes.iter().enumerate() {
                words[i / 8] |= u64::from(*b) << (8 * (i % 8));
            }
            Some(words)
        }
        Root::Field(h, slot) => {
            let mut words = vec![0u64; slot.words.max(1)];
            objects::field_get(h, slot, &mut words).then_some(words)
        }
    }
}

/// Writes the root image back from `words`.
fn store(win: Win, big_w: u32, words: &[u64]) {
    let bits = image_bits(win, big_w);
    match win.root {
        Root::Mem(addr) => {
            // SAFETY: as in `load`.
            let bytes =
                unsafe { std::slice::from_raw_parts_mut(addr as *mut u8, bits.div_ceil(8)) };
            for (i, b) in bytes.iter_mut().enumerate() {
                *b = (words[i / 8] >> (8 * (i % 8))) as u8;
            }
        }
        Root::Field(h, slot) => {
            objects::field_set(h, slot, words);
        }
    }
}

/// Part `[lo, lo + w)` of the `big_w`-bit root that window `id` names, into
/// `out` as a `w`-bit packed `(unk << w) | val`. A bit past the root reads
/// as 0. `false` when `id` is no open window or its object is dead (`out` is
/// then left alone).
pub fn get_part(id: u64, lo: u64, w: u32, big_w: u32, out: &mut [u64]) -> bool {
    let Some(win) = lookup(id) else { return false };
    let Some(img) = load(win, big_w) else {
        return false;
    };
    out.fill(0);
    for k in 0..w as usize {
        let i = lo as usize + k;
        if i < big_w as usize {
            set_bit(out, k, bit_at(&img, i));
            if !win.two_state {
                set_bit(out, w as usize + k, bit_at(&img, big_w as usize + i));
            }
        }
    }
    true
}

/// Writes the `w`-bit packed `val` into part `[lo, lo + w)` of the root that
/// window `id` names, keeping the root's other bits as they are now.
pub fn put_part(id: u64, lo: u64, w: u32, big_w: u32, val: &[u64]) {
    let Some(win) = lookup(id) else { return };
    let Some(mut img) = load(win, big_w) else {
        return;
    };
    for k in 0..w as usize {
        let i = lo as usize + k;
        if i < big_w as usize {
            set_bit(&mut img, i, bit_at(val, k));
            if !win.two_state {
                set_bit(&mut img, big_w as usize + i, bit_at(val, w as usize + k));
            }
        }
    }
    store(win, big_w, &img);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_window_reads_and_writes_only_its_bits() {
        // A 16-bit 4-state root: val 0xABCD, unk 0.
        let mut img: u32 = 0xABCD;
        // SAFETY: `img` outlives the window.
        let id = unsafe { open_mem(&mut img as *mut u32 as usize, false) };
        assert!(is_window(id));
        let mut out = [0u64; 1];
        assert!(get_part(id, 4, 8, 16, &mut out));
        assert_eq!(out[0], 0xBC);
        put_part(id, 4, 8, 16, &[0x5A]);
        close(id);
        assert_eq!(img, 0xA5AD);
        assert!(!get_part(id, 0, 8, 16, &mut out));
    }
}