1use crate::{WasmResult, WasmTrap};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct FuncRef {
21 pub type_index: u32,
25 pub func_index: u32,
29}
30
31pub struct Table<const MAX_SIZE: usize> {
37 entries: [Option<FuncRef>; MAX_SIZE],
38 active_size: usize,
41}
42
43impl<const MAX_SIZE: usize> Table<MAX_SIZE> {
44 pub fn try_new(initial_size: usize) -> Result<Self, crate::ConstructionError> {
49 if initial_size > MAX_SIZE {
50 return Err(crate::ConstructionError::TableInitialSizeExceedsMax {
51 initial: initial_size,
52 max: MAX_SIZE,
53 });
54 }
55 Ok(Self {
56 entries: [None; MAX_SIZE],
57 active_size: initial_size,
58 })
59 }
60
61 #[inline(always)]
63 pub fn size(&self) -> usize {
64 self.active_size
65 }
66
67 #[inline]
72 pub fn get(&self, index: u32) -> WasmResult<FuncRef> {
73 let idx = index as usize;
74 if idx >= self.active_size {
75 return Err(WasmTrap::TableOutOfBounds);
76 }
77 self.entries
78 .get(idx)
79 .copied()
80 .flatten()
81 .ok_or(WasmTrap::UndefinedElement)
82 }
83
84 #[inline]
88 pub fn set(&mut self, index: u32, entry: Option<FuncRef>) -> WasmResult<()> {
89 let idx = index as usize;
90 if idx >= self.active_size {
91 return Err(WasmTrap::TableOutOfBounds);
92 }
93 match self.entries.get_mut(idx) {
94 Some(slot) => {
95 *slot = entry;
96 Ok(())
97 }
98 None => Err(WasmTrap::TableOutOfBounds),
99 }
100 }
101
102 #[inline(always)]
111 pub fn init_elements(&mut self, base: u32, entries: &[(u32, u32)]) -> WasmResult<()> {
112 init_elements_inner(&mut self.entries, self.active_size, base, entries)
113 }
114
115 pub fn grow(&mut self, delta: u32, init: Option<FuncRef>) -> i32 {
118 let old = self.active_size;
119 let new = old.wrapping_add(delta as usize);
120 if new > MAX_SIZE {
121 return -1;
122 }
123 for slot in &mut self.entries[old..new] {
124 *slot = init;
125 }
126 self.active_size = new;
127 old as i32
128 }
129}
130
131#[inline(never)]
134fn init_elements_inner(
135 slots: &mut [Option<FuncRef>],
136 active_size: usize,
137 base: u32,
138 entries: &[(u32, u32)],
139) -> WasmResult<()> {
140 for (i, &(type_index, func_index)) in entries.iter().enumerate() {
141 let idx = (base as usize)
142 .checked_add(i)
143 .ok_or(WasmTrap::TableOutOfBounds)?;
144 if idx >= active_size {
145 return Err(WasmTrap::TableOutOfBounds);
146 }
147 match slots.get_mut(idx) {
148 Some(slot) => {
149 *slot = Some(FuncRef {
150 type_index,
151 func_index,
152 })
153 }
154 None => return Err(WasmTrap::TableOutOfBounds),
155 }
156 }
157 Ok(())
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 fn sample_ref(type_idx: u32, func_idx: u32) -> FuncRef {
165 FuncRef {
166 type_index: type_idx,
167 func_index: func_idx,
168 }
169 }
170
171 #[test]
172 fn new_table_is_empty() {
173 let table = Table::<8>::try_new(4).unwrap();
174 assert_eq!(table.size(), 4);
175 assert_eq!(table.get(0), Err(WasmTrap::UndefinedElement));
177 assert_eq!(table.get(3), Err(WasmTrap::UndefinedElement));
178 }
179
180 #[test]
181 fn get_out_of_bounds() {
182 let table = Table::<8>::try_new(4).unwrap();
183 assert_eq!(table.get(4), Err(WasmTrap::TableOutOfBounds));
184 assert_eq!(table.get(100), Err(WasmTrap::TableOutOfBounds));
185 }
186
187 #[test]
188 fn set_and_get() {
189 let mut table = Table::<8>::try_new(4).unwrap();
190 let fr = sample_ref(0, 5);
191 table.set(2, Some(fr)).unwrap();
192
193 let got = table.get(2).unwrap();
194 assert_eq!(got.type_index, 0);
195 assert_eq!(got.func_index, 5);
196 }
197
198 #[test]
199 fn set_out_of_bounds() {
200 let mut table = Table::<8>::try_new(4).unwrap();
201 assert_eq!(
202 table.set(4, Some(sample_ref(0, 0))),
203 Err(WasmTrap::TableOutOfBounds)
204 );
205 }
206
207 #[test]
208 fn set_none_clears_entry() {
209 let mut table = Table::<8>::try_new(4).unwrap();
210 table.set(1, Some(sample_ref(0, 3))).unwrap();
211 assert!(table.get(1).is_ok());
212 table.set(1, None).unwrap();
213 assert_eq!(table.get(1), Err(WasmTrap::UndefinedElement));
214 }
215
216 #[test]
217 fn grow_success() {
218 let mut table = Table::<8>::try_new(2).unwrap();
219 let old = table.grow(3, None);
220 assert_eq!(old, 2);
221 assert_eq!(table.size(), 5);
222 assert_eq!(table.get(4), Err(WasmTrap::UndefinedElement));
224 }
225
226 #[test]
227 fn grow_with_init() {
228 let mut table = Table::<8>::try_new(2).unwrap();
229 let fr = sample_ref(1, 7);
230 table.grow(2, Some(fr));
231 let got = table.get(3).unwrap();
232 assert_eq!(got.func_index, 7);
233 }
234
235 #[test]
236 fn grow_beyond_max_fails() {
237 let mut table = Table::<4>::try_new(2).unwrap();
238 assert_eq!(table.grow(3, None), -1); assert_eq!(table.size(), 2); }
241
242 #[test]
245 fn init_elements_writes_entries() {
246 let mut table = Table::<8>::try_new(4).unwrap();
247 table.init_elements(0, &[(1, 2), (3, 4)]).unwrap();
248 let e0 = table.get(0).unwrap();
249 assert_eq!(e0.type_index, 1);
250 assert_eq!(e0.func_index, 2);
251 let e1 = table.get(1).unwrap();
252 assert_eq!(e1.type_index, 3);
253 assert_eq!(e1.func_index, 4);
254 }
255
256 #[test]
257 fn init_elements_empty_is_noop() {
258 let mut table = Table::<4>::try_new(4).unwrap();
259 assert!(table.init_elements(0, &[]).is_ok());
260 assert_eq!(table.get(0), Err(WasmTrap::UndefinedElement));
261 }
262
263 #[test]
264 fn init_elements_at_base_offset() {
265 let mut table = Table::<8>::try_new(6).unwrap();
266 table.init_elements(3, &[(0, 5)]).unwrap();
267 assert_eq!(table.get(3).unwrap().func_index, 5);
268 assert_eq!(table.get(0), Err(WasmTrap::UndefinedElement));
269 }
270
271 #[test]
272 fn init_elements_out_of_bounds() {
273 let mut table = Table::<4>::try_new(4).unwrap();
274 let result = table.init_elements(3, &[(0, 0), (0, 1)]);
276 assert_eq!(result, Err(WasmTrap::TableOutOfBounds));
277 }
278
279 #[test]
280 fn init_elements_exactly_fills_table() {
281 let mut table = Table::<4>::try_new(4).unwrap();
282 table
283 .init_elements(0, &[(0, 0), (0, 1), (0, 2), (0, 3)])
284 .unwrap();
285 assert_eq!(table.get(3).unwrap().func_index, 3);
286 }
287
288 #[test]
289 fn try_new_fails_if_initial_exceeds_max() {
290 let result = Table::<4>::try_new(5);
291 assert!(result.is_err());
292 assert!(matches!(
293 result,
294 Err(crate::ConstructionError::TableInitialSizeExceedsMax { initial: 5, max: 4 })
295 ));
296 }
297}
298
299#[cfg(kani)]
302mod proofs {
303 use super::*;
304
305 #[kani::proof]
307 #[kani::unwind(1)]
308 fn get_never_panics() {
309 let table = Table::<8>::new(4);
310 let index: u32 = kani::any();
311 let _ = table.get(index);
312 }
314
315 #[kani::proof]
317 #[kani::unwind(1)]
318 fn set_never_panics() {
319 let mut table = Table::<8>::new(4);
320 let index: u32 = kani::any();
321 let type_index: u32 = kani::any();
322 let func_index: u32 = kani::any();
323 let entry = Some(FuncRef {
324 type_index,
325 func_index,
326 });
327 let _ = table.set(index, entry);
328 }
329
330 #[kani::proof]
332 #[kani::unwind(5)]
333 fn grow_respects_max_size() {
334 let mut table = Table::<4>::new(1);
335 let delta: u32 = kani::any();
336
337 let old_size = table.size();
338 let result = table.grow(delta, None);
339
340 kani::assert(table.size() <= 4, "active_size must not exceed MAX_SIZE");
342
343 if result >= 0 {
345 kani::assert(result == old_size as i32, "grow returns old size");
346 let new_expected = old_size as u64 + delta as u64;
347 if new_expected <= 4 {
348 kani::assert(
349 table.size() == new_expected as usize,
350 "grow updates active_size correctly",
351 );
352 }
353 } else {
354 kani::assert(
356 table.size() == old_size,
357 "failed grow leaves active_size unchanged",
358 );
359 }
360 }
361
362 #[kani::proof]
364 #[kani::unwind(4)]
365 fn grow_fails_beyond_max() {
366 let mut table = Table::<4>::new(2);
367 let result = table.grow(3, None);
369 kani::assert(result == -1, "grow beyond MAX_SIZE returns -1");
370 kani::assert(table.size() == 2, "failed grow leaves size unchanged");
371 }
372
373 #[kani::proof]
375 #[kani::unwind(1)]
376 fn set_get_roundtrip() {
377 let mut table = Table::<8>::new(4);
378 let index: u32 = kani::any();
379 let type_index: u32 = kani::any();
380 let func_index: u32 = kani::any();
381
382 let entry = FuncRef {
383 type_index,
384 func_index,
385 };
386
387 if table.set(index, Some(entry)).is_ok() {
389 let result = table.get(index);
390 kani::assert(result.is_ok(), "get succeeds after successful set");
391 let retrieved = result.unwrap();
392 kani::assert(retrieved.type_index == type_index, "type_index preserved");
393 kani::assert(retrieved.func_index == func_index, "func_index preserved");
394 }
395 }
396
397 #[kani::proof]
399 #[kani::unwind(1)]
400 fn get_out_of_bounds_returns_error() {
401 let table = Table::<8>::new(4);
402 let result1 = table.get(4);
404 kani::assert(
405 result1 == Err(WasmTrap::TableOutOfBounds),
406 "get at active_size is out of bounds",
407 );
408
409 let result2 = table.get(100);
410 kani::assert(
411 result2 == Err(WasmTrap::TableOutOfBounds),
412 "get beyond active_size is out of bounds",
413 );
414 }
415
416 #[kani::proof]
418 #[kani::unwind(1)]
419 fn set_out_of_bounds_returns_error() {
420 let mut table = Table::<8>::new(4);
421 let entry = FuncRef {
422 type_index: 0,
423 func_index: 0,
424 };
425
426 let result = table.set(4, Some(entry));
427 kani::assert(
428 result == Err(WasmTrap::TableOutOfBounds),
429 "set at active_size is out of bounds",
430 );
431 }
432
433 #[kani::proof]
435 #[kani::unwind(1)]
436 fn get_empty_slot_returns_undefined() {
437 let table = Table::<8>::new(4);
438 let result = table.get(0);
440 kani::assert(
441 result == Err(WasmTrap::UndefinedElement),
442 "get on empty slot returns UndefinedElement",
443 );
444 }
445
446 #[kani::proof]
448 #[kani::unwind(1)]
449 fn set_none_clears_slot() {
450 let mut table = Table::<8>::new(4);
451 let entry = FuncRef {
452 type_index: 1,
453 func_index: 5,
454 };
455
456 table.set(1, Some(entry)).unwrap();
458 kani::assert(table.get(1).is_ok(), "slot is set");
459
460 table.set(1, None).unwrap();
461 let result = table.get(1);
462 kani::assert(
463 result == Err(WasmTrap::UndefinedElement),
464 "set None clears the slot",
465 );
466 }
467
468 #[kani::proof]
470 #[kani::unwind(3)]
471 fn grow_initializes_new_slots() {
472 let mut table = Table::<8>::new(2);
473 let init = FuncRef {
474 type_index: 7,
475 func_index: 42,
476 };
477
478 let result = table.grow(2, Some(init));
479
480 if result >= 0 {
481 let slot2 = table.get(2);
483
484 if slot2.is_ok() {
485 let val = slot2.unwrap();
486 kani::assert(
487 val.type_index == 7 && val.func_index == 42,
488 "new slot initialized with init value",
489 );
490 }
491 }
492 }
493
494 #[kani::proof]
496 #[kani::unwind(1)]
497 fn size_returns_active_size() {
498 let table = Table::<8>::new(5);
499 kani::assert(table.size() == 5, "size() returns active_size");
500 }
501
502 #[kani::proof]
504 #[kani::unwind(1)]
505 fn get_success_implies_valid_index() {
506 let mut table = Table::<8>::new(4);
507 let entry = FuncRef {
509 type_index: 0,
510 func_index: 1,
511 };
512 table.set(0, Some(entry)).unwrap();
513
514 let index: u32 = kani::any();
515 let result = table.get(index);
516
517 if result.is_ok() {
519 kani::assert(
520 (index as usize) < table.size(),
521 "successful get implies index < active_size",
522 );
523 }
524 }
525
526 #[kani::proof]
528 #[kani::unwind(1)]
529 fn set_success_implies_valid_index() {
530 let mut table = Table::<8>::new(4);
531 let index: u32 = kani::any();
532 let entry = FuncRef {
533 type_index: 0,
534 func_index: 0,
535 };
536
537 let result = table.set(index, Some(entry));
538
539 if result.is_ok() {
540 kani::assert(
541 (index as usize) < table.size(),
542 "successful set implies index < active_size",
543 );
544 }
545 }
546}