Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use crate::wasm::TrapKind;
9use crate::wasm::vm::mmap_vec::MmapVec;
10use crate::wasm::vm::provenance::VmPtr;
11use crate::wasm::vm::{VMFuncRef, VMTableDefinition};
12use anyhow::anyhow;
13use core::ptr;
14use core::ptr::NonNull;
15use core::range::Range;
16
17#[derive(Debug, Clone, Copy)]
18pub enum TableElement {
19 /// A `funcref`.
20 FuncRef(Option<NonNull<VMFuncRef>>),
21}
22
23#[derive(Copy, Clone, PartialEq, Eq, Debug)]
24pub enum TableElementType {
25 Func,
26 GcRef,
27}
28
29#[derive(Debug)]
30pub struct Table {
31 /// The underlying allocation backing this memory
32 elements: MmapVec<TableElement>,
33 /// The current size of the table.
34 size: usize,
35 /// The optional maximum accessible size, in elements, for this table.
36 maximum: Option<usize>,
37}
38// Safety: The store synchronization protocol ensures this type will only ever be access in a thread-safe way
39unsafe impl Send for Table {}
40// Safety: The store synchronization protocol ensures this type will only ever be access in a thread-safe way
41unsafe impl Sync for Table {}
42
43impl Table {
44 pub(super) fn from_parts(elements: MmapVec<TableElement>, maximum: Option<usize>) -> Self {
45 Self {
46 size: elements.len(),
47 elements,
48 maximum,
49 }
50 }
51
52 pub fn size(&self) -> usize {
53 self.elements.len()
54 }
55
56 pub fn init_func(
57 &mut self,
58 dst: u64,
59 items: impl ExactSizeIterator<Item = Option<NonNull<VMFuncRef>>>,
60 ) -> Result<(), TrapKind> {
61 let dst = usize::try_from(dst).map_err(|_| TrapKind::TableOutOfBounds)?;
62 let elements = self
63 .elements
64 .slice_mut()
65 .get_mut(dst..)
66 .and_then(|s| s.get_mut(..items.len()))
67 .ok_or(TrapKind::TableOutOfBounds)?;
68
69 for (item, slot) in items.zip(elements) {
70 *slot = TableElement::FuncRef(item);
71 }
72
73 Ok(())
74 }
75
76 pub fn fill(&mut self, dst: u64, val: TableElement, len: u64) -> Result<(), TrapKind> {
77 let start = usize::try_from(dst).map_err(|_| TrapKind::TableOutOfBounds)?;
78 let len = usize::try_from(len).map_err(|_| TrapKind::TableOutOfBounds)?;
79 let end = start.checked_add(len).ok_or(TrapKind::TableOutOfBounds)?;
80
81 if end > self.size() {
82 return Err(TrapKind::TableOutOfBounds);
83 }
84
85 self.elements.slice_mut()[start..end].fill(val);
86
87 Ok(())
88 }
89
90 pub fn get(&self, index: u64) -> Option<TableElement> {
91 let index = usize::try_from(index).ok()?;
92 self.elements.get(index).copied()
93 }
94
95 pub fn set(&mut self, index: u64, elem: TableElement) -> crate::Result<()> {
96 let index: usize = index.try_into()?;
97 let slot = self
98 .elements
99 .slice_mut()
100 .get_mut(index)
101 .ok_or(anyhow!("table element index out of bounds"))?;
102 *slot = elem;
103
104 Ok(())
105 }
106
107 pub fn grow(&mut self, delta: u64, init: TableElement) -> Result<Option<usize>, TrapKind> {
108 let old_size = self.size();
109
110 // Don't try to resize the table if its size isn't changing, just return
111 // success.
112 if delta == 0 {
113 return Ok(Some(old_size));
114 }
115
116 let delta = usize::try_from(delta).map_err(|_| TrapKind::TableOutOfBounds)?;
117 let new_size = old_size
118 .checked_add(delta)
119 .ok_or(TrapKind::TableOutOfBounds)?;
120
121 // The WebAssembly spec requires failing a `table.grow` request if
122 // it exceeds the declared limits of the table. We may have set lower
123 // limits in the instance allocator as well.
124 if let Some(max) = self.maximum {
125 if new_size > max {
126 return Ok(None);
127 }
128 }
129
130 // we only support static tables that have all their memory reserved (not allocated) upfront
131 // this means resizing is as simple as just updating the size field
132 self.size = new_size;
133
134 self.fill(
135 u64::try_from(old_size).unwrap(),
136 init,
137 u64::try_from(delta).unwrap(),
138 )
139 .expect("table should not be out of bounds");
140
141 Ok(Some(old_size))
142 }
143
144 pub fn copy(
145 dst_table: *mut Self,
146 src_table: *mut Self,
147 dst_index: u64,
148 src_index: u64,
149 len: u64,
150 ) -> Result<(), TrapKind> {
151 // Safety: the table pointers are valid
152 unsafe {
153 let src_index = usize::try_from(src_index).map_err(|_| TrapKind::TableOutOfBounds)?;
154 let dst_index = usize::try_from(dst_index).map_err(|_| TrapKind::TableOutOfBounds)?;
155 let len = usize::try_from(len).map_err(|_| TrapKind::TableOutOfBounds)?;
156
157 if src_index
158 .checked_add(len)
159 .is_none_or(|n| n > (*src_table).size())
160 || dst_index
161 .checked_add(len)
162 .is_none_or(|m| m > (*dst_table).size())
163 {
164 return Err(TrapKind::TableOutOfBounds);
165 }
166
167 let src_range = Range::from(src_index..src_index + len);
168 let dst_range = Range::from(dst_index..dst_index + len);
169
170 if ptr::eq(dst_table, src_table) {
171 (*dst_table).copy_elements_within(dst_range, src_range);
172 } else {
173 Self::copy_elements(&mut *dst_table, &*src_table, dst_range, src_range);
174 }
175
176 Ok(())
177 }
178 }
179
180 fn copy_elements_within(&mut self, dst_range: Range<usize>, src_range: Range<usize>) {
181 self.elements
182 .slice_mut()
183 .copy_within(src_range, dst_range.start);
184 }
185
186 fn copy_elements(
187 dst_table: &mut Self,
188 src_table: &Self,
189 dst_range: Range<usize>,
190 src_range: Range<usize>,
191 ) {
192 // This can only be used when copying between different tables
193 debug_assert!(!ptr::eq(dst_table, src_table));
194
195 dst_table.elements.slice_mut()[dst_range]
196 .copy_from_slice(&src_table.elements.slice()[src_range]);
197 }
198
199 pub fn as_vmtable_definition(&self) -> VMTableDefinition {
200 VMTableDefinition {
201 base: VmPtr::from(NonNull::new(self.elements.as_ptr().cast_mut().cast()).unwrap()),
202 current_elements: self.elements.len().into(),
203 }
204 }
205}