Skip to main content

baedeker_core/runtime/
table.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4//! Sparse-capable table instance storage.
5//!
6//! Spec-valid tables can declare up to `u32::MAX` entries; a dense
7//! `Vec<Value>` for such a table would need ~100 GiB. Tables start dense
8//! and migrate to sparse storage (non-default entries only) past a
9//! threshold, so a huge table costs memory proportional to the entries
10//! actually written โ€” not its declared size.
11//!
12//! See [Spec ยง4.2.7](https://webassembly.github.io/spec/core/exec/runtime.html#table-instances).
13
14use alloc::collections::BTreeMap;
15use alloc::vec::Vec;
16
17use crate::runtime::Value;
18use crate::types::TableType;
19
20/// Entry count below which a table stays densely allocated. Above this,
21/// storage is sparse (`BTreeMap` of non-default entries).
22const DENSE_LIMIT: u32 = 1 << 20; // 1M entries โ‰ˆ 24 MiB of Values
23
24/// A WebAssembly table instance.
25#[derive(Debug, Clone)]
26pub struct Table {
27    ty: TableType,
28    default: Value,
29    storage: TableStorage,
30}
31
32#[derive(Debug, Clone)]
33enum TableStorage {
34    Dense(Vec<Value>),
35    Sparse {
36        len: u32,
37        elems: BTreeMap<u32, Value>,
38    },
39}
40
41impl Table {
42    /// Create a table with `min` entries of `default`. Dense allocation is
43    /// fallible (`None` on allocation failure); sparse storage never
44    /// allocates eagerly.
45    pub fn new(ty: TableType, default: Value) -> Option<Self> {
46        let min = ty.limits.min;
47        let storage = if min <= DENSE_LIMIT {
48            let mut vec = Vec::new();
49            vec.try_reserve(min as usize).ok()?;
50            vec.resize(min as usize, default);
51            TableStorage::Dense(vec)
52        } else {
53            TableStorage::Sparse {
54                len: min,
55                elems: BTreeMap::new(),
56            }
57        };
58        Some(Self {
59            ty,
60            default,
61            storage,
62        })
63    }
64
65    /// The table's declared type (limits + element type).
66    pub fn ty(&self) -> &TableType {
67        &self.ty
68    }
69
70    /// Current entry count.
71    pub fn len(&self) -> u32 {
72        match &self.storage {
73            TableStorage::Dense(vec) => vec.len() as u32,
74            TableStorage::Sparse { len, .. } => *len,
75        }
76    }
77
78    /// Whether the table has no entries.
79    pub fn is_empty(&self) -> bool {
80        self.len() == 0
81    }
82
83    /// Read the entry at `idx`, or `None` when out of bounds.
84    pub fn get(&self, idx: u32) -> Option<Value> {
85        match &self.storage {
86            TableStorage::Dense(vec) => vec.get(idx as usize).copied(),
87            TableStorage::Sparse { len, elems } => {
88                if idx < *len {
89                    Some(elems.get(&idx).copied().unwrap_or(self.default))
90                } else {
91                    None
92                }
93            }
94        }
95    }
96
97    /// Write `value` at `idx`; `false` on out of bounds.
98    pub fn set(&mut self, idx: u32, value: Value) -> bool {
99        match &mut self.storage {
100            TableStorage::Dense(vec) => {
101                let Some(slot) = vec.get_mut(idx as usize) else {
102                    return false;
103                };
104                *slot = value;
105            }
106            TableStorage::Sparse { len, elems } => {
107                if idx >= *len {
108                    return false;
109                }
110                if value == self.default {
111                    elems.remove(&idx);
112                } else {
113                    elems.insert(idx, value);
114                }
115            }
116        }
117        true
118    }
119
120    /// Grow by `delta` entries filled with `fill`, returning the previous
121    /// length, or `None` when the declared max (or allocation) rejects.
122    pub fn grow(&mut self, delta: u32, fill: Value) -> Option<u32> {
123        let old = self.len();
124        let new = old.checked_add(delta)?;
125        if let Some(max) = self.ty.limits.max
126            && new > max
127        {
128            return None;
129        }
130        match &mut self.storage {
131            TableStorage::Dense(vec) => {
132                if new <= DENSE_LIMIT {
133                    let additional = (new - old) as usize;
134                    if vec.try_reserve(additional).is_err() {
135                        return None;
136                    }
137                    vec.resize(new as usize, fill);
138                } else {
139                    // Migrate to sparse: keep only non-default entries.
140                    let mut elems = BTreeMap::new();
141                    for (idx, value) in vec.iter().enumerate() {
142                        if *value != self.default {
143                            elems.insert(idx as u32, *value);
144                        }
145                    }
146                    if fill != self.default {
147                        for idx in old..new {
148                            elems.insert(idx, fill);
149                        }
150                    }
151                    self.storage = TableStorage::Sparse { len: new, elems };
152                }
153            }
154            TableStorage::Sparse { len, elems } => {
155                if fill != self.default {
156                    for idx in old..new {
157                        elems.insert(idx, fill);
158                    }
159                }
160                *len = new;
161            }
162        }
163        Some(old)
164    }
165
166    /// Fill `count` entries starting at `start` with `value`; `false` on
167    /// out of bounds.
168    pub fn fill(&mut self, start: u32, value: Value, count: u32) -> bool {
169        let Some(end) = start.checked_add(count) else {
170            return false;
171        };
172        match &mut self.storage {
173            TableStorage::Dense(vec) => {
174                if end as usize > vec.len() {
175                    return false;
176                }
177                vec[start as usize..end as usize].fill(value);
178            }
179            TableStorage::Sparse { len, elems } => {
180                if end > *len {
181                    return false;
182                }
183                if value == self.default {
184                    for idx in start..end {
185                        elems.remove(&idx);
186                    }
187                } else {
188                    for idx in start..end {
189                        elems.insert(idx, value);
190                    }
191                }
192            }
193        }
194        true
195    }
196
197    /// Snapshot entries in `start..start + count`; `None` on out of bounds.
198    pub fn read_slice(&self, start: u32, count: u32) -> Option<Vec<Value>> {
199        let end = start.checked_add(count)?;
200        match &self.storage {
201            TableStorage::Dense(vec) => {
202                if end as usize > vec.len() {
203                    return None;
204                }
205                Some(vec[start as usize..end as usize].to_vec())
206            }
207            TableStorage::Sparse { len, elems } => {
208                if end > *len {
209                    return None;
210                }
211                let mut out = Vec::with_capacity(count as usize);
212                for idx in start..end {
213                    out.push(elems.get(&idx).copied().unwrap_or(self.default));
214                }
215                Some(out)
216            }
217        }
218    }
219
220    /// Write `values` starting at `start`; `false` on out of bounds.
221    pub fn write_slice(&mut self, start: u32, values: &[Value]) -> bool {
222        let Some(end) = start.checked_add(values.len() as u32) else {
223            return false;
224        };
225        match &mut self.storage {
226            TableStorage::Dense(vec) => {
227                if end as usize > vec.len() {
228                    return false;
229                }
230                vec[start as usize..end as usize].copy_from_slice(values);
231            }
232            TableStorage::Sparse { len, elems } => {
233                if end > *len {
234                    return false;
235                }
236                for (offset, value) in values.iter().enumerate() {
237                    let idx = start + offset as u32;
238                    if *value == self.default {
239                        elems.remove(&idx);
240                    } else {
241                        elems.insert(idx, *value);
242                    }
243                }
244            }
245        }
246        true
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use alloc::vec;
253
254    use super::*;
255    use crate::types::{Limits, RefType};
256
257    fn funcref_table(min: u32, max: Option<u32>) -> TableType {
258        TableType {
259            elem: RefType::FuncRef,
260            limits: Limits { min, max },
261            init: None,
262        }
263    }
264
265    #[test]
266    fn dense_roundtrip_and_grow() {
267        let mut table = Table::new(funcref_table(2, Some(8)), Value::FuncRef(None)).unwrap();
268        assert_eq!(table.len(), 2);
269        assert_eq!(table.get(0), Some(Value::FuncRef(None)));
270        assert!(table.set(1, Value::FuncRef(Some((0, 3)))));
271        assert_eq!(table.get(1), Some(Value::FuncRef(Some((0, 3)))));
272        assert!(!table.set(2, Value::FuncRef(None)));
273
274        assert_eq!(table.grow(2, Value::FuncRef(Some((1, 1)))), Some(2));
275        assert_eq!(table.len(), 4);
276        assert_eq!(table.get(3), Some(Value::FuncRef(Some((1, 1)))));
277        // Declared max rejects growth.
278        assert_eq!(table.grow(9, Value::FuncRef(None)), None);
279    }
280
281    #[test]
282    fn huge_table_is_sparse_and_usable() {
283        // u32::MAX entries must not allocate densely.
284        let mut table = Table::new(funcref_table(u32::MAX, None), Value::FuncRef(None)).unwrap();
285        assert_eq!(table.len(), u32::MAX);
286        assert_eq!(table.get(4_000_000_000), Some(Value::FuncRef(None)));
287        assert!(table.set(4_000_000_000, Value::FuncRef(Some((2, 5)))));
288        assert_eq!(table.get(4_000_000_000), Some(Value::FuncRef(Some((2, 5)))));
289        // Writing the default clears the sparse entry.
290        assert!(table.set(4_000_000_000, Value::FuncRef(None)));
291        assert_eq!(table.get(4_000_000_000), Some(Value::FuncRef(None)));
292    }
293
294    #[test]
295    fn grow_past_dense_limit_migrates() {
296        let mut table = Table::new(funcref_table(4, None), Value::FuncRef(None)).unwrap();
297        assert!(table.set(2, Value::FuncRef(Some((7, 7)))));
298        let old = table.grow(DENSE_LIMIT, Value::FuncRef(None)).unwrap();
299        assert_eq!(old, 4);
300        assert!(matches!(table.storage, TableStorage::Sparse { .. }));
301        // Contents survive the migration.
302        assert_eq!(table.get(2), Some(Value::FuncRef(Some((7, 7)))));
303        assert_eq!(table.get(DENSE_LIMIT), Some(Value::FuncRef(None)));
304    }
305
306    #[test]
307    fn fill_and_slices() {
308        let mut table = Table::new(funcref_table(4, None), Value::FuncRef(None)).unwrap();
309        assert!(table.fill(1, Value::FuncRef(Some((0, 1))), 2));
310        assert_eq!(
311            table.read_slice(0, 4).unwrap(),
312            vec![
313                Value::FuncRef(None),
314                Value::FuncRef(Some((0, 1))),
315                Value::FuncRef(Some((0, 1))),
316                Value::FuncRef(None),
317            ]
318        );
319        assert!(table.write_slice(2, &[Value::FuncRef(Some((9, 9))), Value::FuncRef(None)]));
320        assert_eq!(table.get(2), Some(Value::FuncRef(Some((9, 9)))));
321        assert_eq!(table.get(3), Some(Value::FuncRef(None)));
322        assert!(!table.fill(3, Value::FuncRef(None), 2));
323        assert!(table.read_slice(3, 2).is_none());
324        assert!(!table.write_slice(3, &[Value::FuncRef(None), Value::FuncRef(None)]));
325    }
326}