Skip to main content

oxide_core/heap/
table.rs

1//! The ordered hash table of `Map`, `Set`, `WeakMap` and `WeakSet`.
2//!
3//! The entries live in the record's internal slots, one or two values each
4//! (`width`), in insertion order; a deleted entry's key is `Value::EMPTY`.
5//! [`Table`] holds the open-addressing index over them and each entry's
6//! hash. When tombstones outnumber the live entries the slots compact, and
7//! every live iterator's cursor (registered as a weak reference, so an
8//! abandoned iterator costs nothing once collected) is moved to the same
9//! entry's new position.
10
11use std::cell::Cell;
12use std::rc::{Rc, Weak};
13
14use super::object::InternalData;
15use super::{Heap, HeapError};
16use crate::value::{Kind, ObjectId, Value};
17
18/// An empty index bucket.
19const VACANT: u32 = 0;
20/// A bucket whose entry was deleted.
21const DELETED: u32 = u32::MAX;
22
23/// The index and bookkeeping of one collection.
24#[derive(Debug)]
25pub struct Table {
26    width: usize,
27    /// Buckets: [`VACANT`], [`DELETED`], or an entry number plus one.
28    index: Vec<u32>,
29    /// Each entry's hash, live or deleted.
30    hashes: Vec<u32>,
31    live: usize,
32    cursors: Vec<Weak<Cell<u32>>>,
33}
34
35impl Table {
36    pub fn new(width: usize) -> Self {
37        Table {
38            width,
39            index: Vec::new(),
40            hashes: Vec::new(),
41            live: 0,
42            cursors: Vec::new(),
43        }
44    }
45
46    pub fn len(&self) -> usize {
47        self.live
48    }
49
50    pub fn is_empty(&self) -> bool {
51        self.live == 0
52    }
53
54    pub fn width(&self) -> usize {
55        self.width
56    }
57
58    /// Whether deleted entry `entry` is a QuickJS zombie: the record a live
59    /// iterator last returned, which QuickJS keeps linked (and its printer
60    /// shows as an empty item) until the iterator moves on.
61    pub fn is_zombie(&self, entry: usize) -> bool {
62        self.cursors.iter().any(|cursor| {
63            cursor
64                .upgrade()
65                .is_some_and(|cursor| cursor.get() as usize == entry + 1)
66        })
67    }
68
69    /// Entries, live or deleted: the bound of an iteration cursor.
70    pub fn entries(&self) -> usize {
71        self.hashes.len()
72    }
73
74    pub fn owned_bytes(&self) -> usize {
75        self.index.capacity() * 4 + self.hashes.capacity() * 4 + self.cursors.capacity() * 8
76    }
77
78    fn insert_index(&mut self, hash: u32, entry: usize) {
79        let mask = self.index.len() - 1;
80        let mut bucket = hash as usize & mask;
81        while self.index[bucket] != VACANT && self.index[bucket] != DELETED {
82            bucket = (bucket + 1) & mask;
83        }
84        self.index[bucket] = entry as u32 + 1;
85    }
86
87    /// Rebuilds the index with room for `entries` entries.
88    fn rebuild(&mut self, entries: usize, slots: &[Value]) {
89        let buckets = (entries * 2).next_power_of_two().max(8);
90        self.index.clear();
91        self.index.resize(buckets, VACANT);
92        for entry in 0..self.hashes.len() {
93            if !slots[entry * self.width].is_empty() {
94                self.insert_index(self.hashes[entry], entry);
95            }
96        }
97    }
98}
99
100/// Deletes entry `entry` of a collection record without compacting (the
101/// collector's removal of a dead weak key).
102pub(crate) fn tombstone(internal: &mut super::object::Internal, entry: usize) {
103    let InternalData::Table(table) = &mut internal.data else {
104        unreachable!("a collection");
105    };
106    let width = table.width;
107    let hash = table.hashes[entry];
108    let mask = table.index.len() - 1;
109    let mut bucket = hash as usize & mask;
110    while table.index[bucket] != entry as u32 + 1 {
111        bucket = (bucket + 1) & mask;
112    }
113    table.index[bucket] = DELETED;
114    table.live -= 1;
115    for slot in &mut internal.slots[entry * width..entry * width + width] {
116        *slot = Value::EMPTY;
117    }
118}
119
120impl Heap {
121    /// The hash of a key under `SameValueZero`: numbers by value (so `1` and
122    /// `1.0` and `-0` and `+0` agree), strings and BigInts by content,
123    /// everything else by identity.
124    pub(crate) fn table_hash(&self, key: Value) -> u32 {
125        let bits = match key.kind() {
126            Kind::Int32 | Kind::Number => {
127                let number = key.as_f64().unwrap();
128                if number == 0.0 {
129                    0
130                } else if number.is_nan() {
131                    0x7FF8_0000_0000_0000
132                } else {
133                    number.to_bits()
134                }
135            }
136            Kind::String => return self.string(key.as_string().unwrap()).hash(),
137            Kind::BigInt => {
138                let record = self.bigint(key.as_bigint().unwrap());
139                let mut hash = u64::from(record.negative);
140                for &limb in record.limbs.iter() {
141                    hash = hash.rotate_left(5) ^ limb;
142                }
143                hash
144            }
145            _ => key.bits(),
146        };
147        let mixed = bits.wrapping_mul(0x9E37_79B9_7F4A_7C15);
148        (mixed >> 32) as u32 ^ mixed as u32
149    }
150
151    pub(crate) fn table(&self, object: ObjectId) -> &Table {
152        match &self.internal(object).data {
153            InternalData::Table(table) => table,
154            _ => unreachable!("a collection"),
155        }
156    }
157
158    fn table_mut(&mut self, object: ObjectId) -> &mut Table {
159        match &mut self.internal_mut(object).data {
160            InternalData::Table(table) => table,
161            _ => unreachable!("a collection"),
162        }
163    }
164
165    /// The entry number of `key`, if present.
166    pub(crate) fn table_find(&self, object: ObjectId, key: Value) -> Option<usize> {
167        let table = self.table(object);
168        if table.live == 0 {
169            return None;
170        }
171        let hash = self.table_hash(key);
172        let slots = &self.internal(object).slots;
173        let mask = table.index.len() - 1;
174        let mut bucket = hash as usize & mask;
175        loop {
176            match table.index[bucket] {
177                VACANT => return None,
178                DELETED => {}
179                entry => {
180                    let entry = entry as usize - 1;
181                    if table.hashes[entry] == hash
182                        && crate::vm::ops::same_value_zero_heap(
183                            self,
184                            slots[entry * table.width],
185                            key,
186                        )
187                    {
188                        return Some(entry);
189                    }
190                }
191            }
192            bucket = (bucket + 1) & mask;
193        }
194    }
195
196    /// The value of a map's `key`.
197    pub(crate) fn table_get(&self, object: ObjectId, key: Value) -> Option<Value> {
198        let entry = self.table_find(object, key)?;
199        let width = self.table(object).width;
200        Some(self.internal(object).slots[entry * width + width - 1])
201    }
202
203    /// Adds `key` (with `value` for a map) or replaces its value. `-0` is
204    /// stored as `+0`.
205    pub(crate) fn table_set(
206        &mut self,
207        object: ObjectId,
208        key: Value,
209        value: Value,
210    ) -> Result<(), HeapError> {
211        let key = if key.as_f64() == Some(0.0) {
212            Value::int32(0)
213        } else {
214            key
215        };
216        let width = self.table(object).width;
217        if let Some(entry) = self.table_find(object, key) {
218            if width == 2 {
219                self.internal_mut(object).slots[entry * 2 + 1] = value;
220            }
221            return Ok(());
222        }
223        let hash = self.table_hash(key);
224        self.reserve_internal_slots(object, width, &[key, value])?;
225        let internal = self.internal_mut(object);
226        internal.slots.push(key);
227        if width == 2 {
228            internal.slots.push(value);
229        }
230        let InternalData::Table(table) = &mut internal.data else {
231            unreachable!("a collection");
232        };
233        let entry = table.hashes.len();
234        table.hashes.push(hash);
235        table.live += 1;
236        // Keep the index at most half full, counting deleted buckets.
237        if (entry + 1) * 2 > table.index.len() {
238            let entries = table.live.max(entry + 1);
239            table.rebuild(entries, &internal.slots);
240        } else {
241            table.insert_index(hash, entry);
242        }
243        Ok(())
244    }
245
246    /// Deletes `key`; whether it was present.
247    pub(crate) fn table_delete(&mut self, object: ObjectId, key: Value) -> bool {
248        let Some(entry) = self.table_find(object, key) else {
249            return false;
250        };
251        self.table_delete_entry(object, entry);
252        true
253    }
254
255    /// Deletes entry `entry` (live), compacting when tombstones dominate.
256    pub(crate) fn table_delete_entry(&mut self, object: ObjectId, entry: usize) {
257        let internal = self.internal_mut(object);
258        let InternalData::Table(table) = &mut internal.data else {
259            unreachable!("a collection");
260        };
261        let width = table.width;
262        let hash = table.hashes[entry];
263        let mask = table.index.len() - 1;
264        let mut bucket = hash as usize & mask;
265        while table.index[bucket] != entry as u32 + 1 {
266            bucket = (bucket + 1) & mask;
267        }
268        table.index[bucket] = DELETED;
269        table.live -= 1;
270        for slot in &mut internal.slots[entry * width..entry * width + width] {
271            *slot = Value::EMPTY;
272        }
273        if table.hashes.len() >= 16 && table.live * 2 < table.hashes.len() {
274            self.table_compact(object);
275        }
276    }
277
278    /// Deletes every entry.
279    pub(crate) fn table_clear(&mut self, object: ObjectId) {
280        let internal = self.internal_mut(object);
281        internal.slots.fill(Value::EMPTY);
282        self.table_compact(object);
283    }
284
285    /// Drops the deleted entries and moves every live cursor to the new
286    /// position of the entry it was at.
287    fn table_compact(&mut self, object: ObjectId) {
288        let internal = self.internal_mut(object);
289        let InternalData::Table(table) = &mut internal.data else {
290            unreachable!("a collection");
291        };
292        let width = table.width;
293        let entries = table.hashes.len();
294        // `before[i]`: the live entries before entry `i`.
295        let mut before = Vec::with_capacity(entries + 1);
296        let mut kept = 0u32;
297        let mut write = 0;
298        for entry in 0..entries {
299            before.push(kept);
300            if !internal.slots[entry * width].is_empty() {
301                for offset in 0..width {
302                    internal.slots[write * width + offset] = internal.slots[entry * width + offset];
303                }
304                table.hashes[write] = table.hashes[entry];
305                write += 1;
306                kept += 1;
307            }
308        }
309        before.push(kept);
310        internal.slots.truncate(write * width);
311        table.hashes.truncate(write);
312        table.cursors.retain(|cursor| match cursor.upgrade() {
313            Some(cursor) => {
314                let at = (cursor.get() as usize).min(entries);
315                cursor.set(before[at]);
316                true
317            }
318            None => false,
319        });
320        table.live = write;
321        table.rebuild(write, &internal.slots);
322    }
323
324    /// A cursor at the first entry, moved by compactions.
325    pub(crate) fn table_cursor(&mut self, object: ObjectId) -> Rc<Cell<u32>> {
326        let cursor = Rc::new(Cell::new(0));
327        let table = self.table_mut(object);
328        table.cursors.retain(|cursor| cursor.strong_count() > 0);
329        table.cursors.push(Rc::downgrade(&cursor));
330        cursor
331    }
332
333    /// The live entry at or after `cursor` (its key and value, the value
334    /// being the key for a set), advancing the cursor past it.
335    pub(crate) fn table_next(
336        &self,
337        object: ObjectId,
338        cursor: &Cell<u32>,
339    ) -> Option<(Value, Value)> {
340        let table = self.table(object);
341        let slots = &self.internal(object).slots;
342        let width = table.width;
343        let mut entry = cursor.get() as usize;
344        while entry < table.hashes.len() {
345            let key = slots[entry * width];
346            entry += 1;
347            if !key.is_empty() {
348                cursor.set(entry as u32);
349                return Some((key, slots[entry * width - 1]));
350            }
351        }
352        cursor.set(entry as u32);
353        None
354    }
355
356    /// Reserves room for `additional` internal slots of `object`, charging
357    /// the growth to the trigger; `keep` survives the safepoint.
358    pub(crate) fn reserve_internal_slots(
359        &mut self,
360        object: ObjectId,
361        additional: usize,
362        keep: &[Value],
363    ) -> Result<(), HeapError> {
364        let internal = self.internal(object);
365        if internal.slots.capacity() - internal.slots.len() >= additional {
366            return Ok(());
367        }
368        let extra = internal.slots.capacity().max(4) * 8;
369        let mut kept = Vec::with_capacity(keep.len() + 1);
370        kept.push(Value::object(object));
371        kept.extend_from_slice(keep);
372        self.safepoint(
373            &super::Need {
374                extra,
375                ..Default::default()
376            },
377            &kept,
378        )?;
379        let internal = self.internal_mut(object);
380        let grow = internal.slots.capacity().max(4).max(additional);
381        internal.slots.reserve(grow);
382        Ok(())
383    }
384}