Skip to main content

qcode/value/
interner.rs

1//! Append-only interners for constants (literals and byte blobs), wrapped in an
2//! `RwLock` so they can be **minted through a shared `&` reference** — the
3//! prerequisite for a function pass creating a constant while it holds only
4//! a `ContextView`. Interned ids are globally stable and never remapped.
5//!
6//! Reads (`Index`) return a `&T` that outlives the read guard. This is sound
7//! because the backing [`Registry`] is *address-stable*: it never moves an
8//! element once pushed (see `jstd::registry::Registry`), so the reference stays
9//! valid for the life of the interner even as later mints grow it. The read lock
10//! only guards the brief indexing.
11
12use std::ops::{Index, IndexMut};
13use std::sync::RwLock;
14
15use jstd::registry::{Identified, Identifier, Registry};
16use rustc_hash::FxHashMap as HashMap;
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18
19use crate::{
20    types::TypeId,
21    value::literal::{Literal, LiteralId},
22};
23
24/// Mask `value` to `size` bytes (the low `size*8` bits), matching the width a
25/// literal of that size can hold.
26fn mask_to_size(value: u64, size: usize) -> u64 {
27    if size >= 8 {
28        value
29    } else {
30        value & ((1u64 << (size * 8)) - 1)
31    }
32}
33
34/// A generic append-only interner: an address-stable [`Registry`] behind an
35/// `RwLock`. Pushes take a write lock; reads (`Index`) take a read lock and hand
36/// back a stable `&T`.
37pub struct Interner<Id: Identifier, T> {
38    inner: RwLock<Registry<Id, T>>,
39}
40
41impl<Id: Identifier, T> Default for Interner<Id, T> {
42    fn default() -> Self {
43        Self {
44            inner: RwLock::new(Registry::default()),
45        }
46    }
47}
48
49impl<Id: Identifier, T: Clone> Clone for Interner<Id, T> {
50    fn clone(&self) -> Self {
51        Self {
52            inner: RwLock::new(self.read().clone()),
53        }
54    }
55}
56
57impl<Id: Identifier, T> Interner<Id, T> {
58    fn read(&self) -> std::sync::RwLockReadGuard<'_, Registry<Id, T>> {
59        self.inner.read().expect("interner RwLock poisoned")
60    }
61
62    /// Appends `value`, returning its stable id. Takes a write lock.
63    pub fn push(&self, value: T) -> Id {
64        self.inner
65            .write()
66            .expect("interner RwLock poisoned")
67            .push(value)
68    }
69
70    /// The number of interned values.
71    pub fn len(&self) -> usize {
72        self.read().len()
73    }
74
75    /// Whether the interner is empty.
76    pub fn is_empty(&self) -> bool {
77        self.read().is_empty()
78    }
79}
80
81impl<Id: Identifier, T: Clone> Interner<Id, T> {
82    /// An owned snapshot of all `(id, value)` pairs in id order. Owned (cloned)
83    /// so it does not borrow through the lock; callers that only need ids or a
84    /// stable `&T` should prefer indexing.
85    pub fn iter(&self) -> impl Iterator<Item = Identified<Id, T>> {
86        self.read()
87            .iter()
88            .map(|item| Identified::new(item.id, item.inner.clone()))
89            .collect::<Vec<_>>()
90            .into_iter()
91    }
92}
93
94impl<Id: Identifier, T> Index<Id> for Interner<Id, T> {
95    type Output = T;
96
97    fn index(&self, id: Id) -> &T {
98        let ptr: *const T = {
99            let reg = self.read();
100            &reg[id] as *const T
101        };
102        // SAFETY: the backing registry is address-stable (elements never move
103        // once pushed), so `ptr` is valid for the life of `self`; the borrow is
104        // tied to `&self` here.
105        unsafe { &*ptr }
106    }
107}
108
109impl<Id: Identifier, T> IndexMut<Id> for Interner<Id, T> {
110    fn index_mut(&mut self, id: Id) -> &mut T {
111        // Exclusive access via `&mut self`: no lock needed, and no stability
112        // trick — this is the plain in-place mutation path for interned values
113        // that are edited right after creation (e.g. a byte blob's element type).
114        &mut self.inner.get_mut().expect("interner RwLock poisoned")[id]
115    }
116}
117
118impl<Id: Identifier, T: Serialize> Serialize for Interner<Id, T> {
119    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
120        self.read().serialize(serializer)
121    }
122}
123
124impl<'de, Id: Identifier, T: Deserialize<'de>> Deserialize<'de> for Interner<Id, T> {
125    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
126        Ok(Self {
127            inner: RwLock::new(Registry::deserialize(deserializer)?),
128        })
129    }
130}
131
132/// The literal interner: an address-stable literal [`Registry`] plus its
133/// dedup cache `(masked value, type) → LiteralId`, together behind one `RwLock`
134/// so the check-or-mint is atomic. Reads (`Index`) hand back a stable `&Literal`.
135#[derive(Default)]
136pub struct LiteralInterner {
137    inner: RwLock<LiteralPool>,
138}
139
140#[derive(Default, Clone)]
141struct LiteralPool {
142    literals: Registry<LiteralId, Literal>,
143    /// Intern cache for non-symbolic literals. Derived from `literals`; not
144    /// serialized (rebuilt on load).
145    cache: HashMap<(u64, TypeId), LiteralId>,
146}
147
148impl Clone for LiteralInterner {
149    fn clone(&self) -> Self {
150        Self {
151            inner: RwLock::new(self.read().clone()),
152        }
153    }
154}
155
156impl LiteralInterner {
157    fn read(&self) -> std::sync::RwLockReadGuard<'_, LiteralPool> {
158        self.inner.read().expect("literal interner RwLock poisoned")
159    }
160
161    /// Returns a canonical [`LiteralId`] for the given typed constant, minting
162    /// (and caching) it if new. The value is masked to `type_id`'s size before
163    /// lookup; symbolic literals (from [`push_literal`](Self::push_literal)) are
164    /// not cached and never alias with constants produced here.
165    pub fn get_or_make_typed_literal(&self, value: u64, type_id: TypeId, size: usize) -> LiteralId {
166        let value = mask_to_size(value, size);
167        // Hot path: cache hit under a read lock.
168        if let Some(&id) = self.read().cache.get(&(value, type_id)) {
169            return id;
170        }
171        // Miss: take the write lock and re-check before minting.
172        let mut pool = self
173            .inner
174            .write()
175            .expect("literal interner RwLock poisoned");
176        if let Some(&id) = pool.cache.get(&(value, type_id)) {
177            return id;
178        }
179        let id = pool.literals.push(Literal {
180            value,
181            type_id,
182            symbolic: None,
183        });
184        pool.cache.insert((value, type_id), id);
185        id
186    }
187
188    /// Pushes a [`Literal`] with arbitrary fields (e.g. a symbolic ref) without
189    /// interning.
190    pub fn push_literal(&self, literal: Literal) -> LiteralId {
191        self.inner
192            .write()
193            .expect("literal interner RwLock poisoned")
194            .literals
195            .push(literal)
196    }
197
198    /// The number of interned literals.
199    pub fn len(&self) -> usize {
200        self.read().literals.len()
201    }
202
203    /// Whether the interner is empty.
204    pub fn is_empty(&self) -> bool {
205        self.read().literals.is_empty()
206    }
207
208    /// An owned snapshot of all `(id, literal)` pairs in id order. Owned (cloned)
209    /// so it does not borrow through the lock — used by the post-lift address /
210    /// string resolvers, which then mutate literals via [`IndexMut`].
211    pub fn iter(&self) -> impl Iterator<Item = Identified<LiteralId, Literal>> {
212        self.read()
213            .literals
214            .iter()
215            .map(|item| Identified::new(item.id, item.inner.clone()))
216            .collect::<Vec<_>>()
217            .into_iter()
218    }
219}
220
221impl Index<LiteralId> for LiteralInterner {
222    type Output = Literal;
223
224    fn index(&self, id: LiteralId) -> &Literal {
225        let ptr: *const Literal = {
226            let pool = self.read();
227            &pool.literals[id] as *const Literal
228        };
229        // SAFETY: the literal registry is address-stable (see `Interner::index`).
230        unsafe { &*ptr }
231    }
232}
233
234impl IndexMut<LiteralId> for LiteralInterner {
235    fn index_mut(&mut self, id: LiteralId) -> &mut Literal {
236        // Exclusive `&mut self` access (used to attach a symbolic ref to an
237        // existing literal after lifting); no lock or stability trick needed. The
238        // dedup cache still points at this id — attaching a symbol does not change
239        // the `(value, type)` key it was interned under.
240        &mut self
241            .inner
242            .get_mut()
243            .expect("literal interner RwLock poisoned")
244            .literals[id]
245    }
246}
247
248impl Serialize for LiteralInterner {
249    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
250        // Serialize just the literals (a flat sequence, unchanged wire format);
251        // the cache is derived data and is rebuilt on load.
252        self.read().literals.serialize(serializer)
253    }
254}
255
256impl<'de> Deserialize<'de> for LiteralInterner {
257    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
258        let literals = Registry::<LiteralId, Literal>::deserialize(deserializer)?;
259        // Rebuild the dedup cache so post-load minting reuses existing literals
260        // instead of creating duplicates. Non-symbolic literals only, first wins.
261        let mut cache: HashMap<(u64, TypeId), LiteralId> = HashMap::default();
262        for item in literals.iter() {
263            if item.inner.symbolic.is_none() {
264                cache
265                    .entry((item.inner.value, item.inner.type_id))
266                    .or_insert(item.id);
267            }
268        }
269        Ok(Self {
270            inner: RwLock::new(LiteralPool { literals, cache }),
271        })
272    }
273}