Skip to main content

vj_sim/
winref.rs

1//! `ref` arguments bound to a window of a variable or class property
2//! (LRM 1800-2023 13.5.2): `f(s.n)`, `f(v[3:0])`, `f(obj.x)`, `f(g)`.
3//!
4//! The callee accesses the actual directly, so a body that writes the same
5//! storage through another path while it holds the `ref` (the module
6//! variable, the class handle, a nested call) must see one location. A
7//! compiled function holds such a `ref` as a reference id like an element
8//! reference of a queue (`collections::ref_open`), but the id names a window
9//! of a root image instead: the root's bytes in the caller's frame, or a
10//! class property's words in the object heap. The image is the packed value
11//! `(unk << W) | val` of codegen (a 2-state build: `val` alone).
12//!
13//! Ids carry [`WIN_BIT`] so they never collide with an element reference's.
14
15use crate::objects::{self, FieldSlot};
16use std::sync::{Mutex, OnceLock};
17
18/// Set in the id of every window reference.
19pub const WIN_BIT: u64 = 1 << 62;
20
21#[derive(Clone, Copy)]
22enum Root {
23    /// A root image in memory (its address; valid until the call ends).
24    Mem(usize),
25    /// A class property: the object and the property's slot.
26    Field(u64, FieldSlot),
27}
28
29#[derive(Clone, Copy)]
30struct Win {
31    root: Root,
32    /// The image carries no unknown plane.
33    two_state: bool,
34}
35
36#[derive(Default)]
37struct Wins {
38    slots: Vec<Option<Win>>,
39    free: Vec<u32>,
40}
41
42fn table() -> &'static Mutex<Wins> {
43    static T: OnceLock<Mutex<Wins>> = OnceLock::new();
44    T.get_or_init(|| Mutex::new(Wins::default()))
45}
46
47/// Whether `id` names a window reference.
48pub fn is_window(id: u64) -> bool {
49    id & WIN_BIT != 0
50}
51
52fn push(w: Win) -> u64 {
53    let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
54    let k = match t.free.pop() {
55        Some(k) => {
56            t.slots[k as usize] = Some(w);
57            k
58        }
59        None => {
60            t.slots.push(Some(w));
61            (t.slots.len() - 1) as u32
62        }
63    };
64    WIN_BIT | (u64::from(k) + 1)
65}
66
67fn lookup(id: u64) -> Option<Win> {
68    let t = table().lock().unwrap_or_else(|e| e.into_inner());
69    let k = (id & !WIN_BIT).checked_sub(1)? as usize;
70    t.slots.get(k).copied().flatten()
71}
72
73/// Opens a window reference on the root image at `addr`.
74///
75/// # Safety
76/// The image (`ceil(bits / 8)` bytes, `bits` the root's packed width) must
77/// stay valid and unaliased by Rust references until [`close`].
78pub unsafe fn open_mem(addr: usize, two_state: bool) -> u64 {
79    push(Win {
80        root: Root::Mem(addr),
81        two_state,
82    })
83}
84
85/// Opens a window reference on the class property `slot` of object `h`.
86pub fn open_field(h: u64, slot: FieldSlot, two_state: bool) -> u64 {
87    push(Win {
88        root: Root::Field(h, slot),
89        two_state,
90    })
91}
92
93/// Closes window reference `id`.
94pub fn close(id: u64) {
95    let mut t = table().lock().unwrap_or_else(|e| e.into_inner());
96    if let Some(k) = (id & !WIN_BIT).checked_sub(1).map(|k| k as usize)
97        && t.slots.get_mut(k).and_then(Option::take).is_some()
98    {
99        t.free.push(k as u32);
100    }
101}
102
103fn bit_at(words: &[u64], i: usize) -> bool {
104    words.get(i / 64).is_some_and(|w| w >> (i % 64) & 1 != 0)
105}
106
107fn set_bit(words: &mut [u64], i: usize, v: bool) {
108    if let Some(w) = words.get_mut(i / 64) {
109        if v {
110            *w |= 1 << (i % 64);
111        } else {
112            *w &= !(1 << (i % 64));
113        }
114    }
115}
116
117/// The root image's bit count for a `big_w`-bit value.
118fn image_bits(win: Win, big_w: u32) -> usize {
119    if win.two_state {
120        big_w as usize
121    } else {
122        2 * big_w as usize
123    }
124}
125
126/// Reads the root image as little-endian words (`None`: a dead object).
127fn load(win: Win, big_w: u32) -> Option<Vec<u64>> {
128    let bits = image_bits(win, big_w);
129    match win.root {
130        Root::Mem(addr) => {
131            let mut words = vec![0u64; bits.div_ceil(64).max(1)];
132            // SAFETY: `open_mem`'s contract: the image spans `ceil(bits / 8)` bytes.
133            let bytes = unsafe { std::slice::from_raw_parts(addr as *const u8, bits.div_ceil(8)) };
134            for (i, b) in bytes.iter().enumerate() {
135                words[i / 8] |= u64::from(*b) << (8 * (i % 8));
136            }
137            Some(words)
138        }
139        Root::Field(h, slot) => {
140            let mut words = vec![0u64; slot.words.max(1)];
141            objects::field_get(h, slot, &mut words).then_some(words)
142        }
143    }
144}
145
146/// Writes the root image back from `words`.
147fn store(win: Win, big_w: u32, words: &[u64]) {
148    let bits = image_bits(win, big_w);
149    match win.root {
150        Root::Mem(addr) => {
151            // SAFETY: as in `load`.
152            let bytes =
153                unsafe { std::slice::from_raw_parts_mut(addr as *mut u8, bits.div_ceil(8)) };
154            for (i, b) in bytes.iter_mut().enumerate() {
155                *b = (words[i / 8] >> (8 * (i % 8))) as u8;
156            }
157        }
158        Root::Field(h, slot) => {
159            objects::field_set(h, slot, words);
160        }
161    }
162}
163
164/// Part `[lo, lo + w)` of the `big_w`-bit root that window `id` names, into
165/// `out` as a `w`-bit packed `(unk << w) | val`. A bit past the root reads
166/// as 0. `false` when `id` is no open window or its object is dead (`out` is
167/// then left alone).
168pub fn get_part(id: u64, lo: u64, w: u32, big_w: u32, out: &mut [u64]) -> bool {
169    let Some(win) = lookup(id) else { return false };
170    let Some(img) = load(win, big_w) else {
171        return false;
172    };
173    out.fill(0);
174    for k in 0..w as usize {
175        let i = lo as usize + k;
176        if i < big_w as usize {
177            set_bit(out, k, bit_at(&img, i));
178            if !win.two_state {
179                set_bit(out, w as usize + k, bit_at(&img, big_w as usize + i));
180            }
181        }
182    }
183    true
184}
185
186/// Writes the `w`-bit packed `val` into part `[lo, lo + w)` of the root that
187/// window `id` names, keeping the root's other bits as they are now.
188pub fn put_part(id: u64, lo: u64, w: u32, big_w: u32, val: &[u64]) {
189    let Some(win) = lookup(id) else { return };
190    let Some(mut img) = load(win, big_w) else {
191        return;
192    };
193    for k in 0..w as usize {
194        let i = lo as usize + k;
195        if i < big_w as usize {
196            set_bit(&mut img, i, bit_at(val, k));
197            if !win.two_state {
198                set_bit(&mut img, big_w as usize + i, bit_at(val, w as usize + k));
199            }
200        }
201    }
202    store(win, big_w, &img);
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn a_window_reads_and_writes_only_its_bits() {
211        // A 16-bit 4-state root: val 0xABCD, unk 0.
212        let mut img: u32 = 0xABCD;
213        // SAFETY: `img` outlives the window.
214        let id = unsafe { open_mem(&mut img as *mut u32 as usize, false) };
215        assert!(is_window(id));
216        let mut out = [0u64; 1];
217        assert!(get_part(id, 4, 8, 16, &mut out));
218        assert_eq!(out[0], 0xBC);
219        put_part(id, 4, 8, 16, &[0x5A]);
220        close(id);
221        assert_eq!(img, 0xA5AD);
222        assert!(!get_part(id, 0, 8, 16, &mut out));
223    }
224}