1pub use self::{
2 element::{ElementSegment, ElementSegmentEntity, ElementSegmentIdx},
3 error::TableError,
4};
5use super::{AsContext, AsContextMut, Stored};
6use crate::{
7 collections::arena::ArenaIndex,
8 core::{TrapCode, UntypedVal, ValType},
9 error::EntityGrowError,
10 store::{Fuel, FuelError, ResourceLimiterRef},
11 value::WithType,
12 IndexType,
13 Val,
14};
15use alloc::vec::Vec;
16use core::{cmp::max, iter};
17
18mod element;
19mod error;
20
21#[cfg(test)]
22mod tests;
23
24#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
26pub struct TableIdx(u32);
27
28impl ArenaIndex for TableIdx {
29 fn into_usize(self) -> usize {
30 self.0 as usize
31 }
32
33 fn from_usize(value: usize) -> Self {
34 let value = value.try_into().unwrap_or_else(|error| {
35 panic!("index {value} is out of bounds as table index: {error}")
36 });
37 Self(value)
38 }
39}
40
41#[derive(Debug, Copy, Clone, PartialEq, Eq)]
43pub struct TableType {
44 element: ValType,
46 min: u64,
48 max: Option<u64>,
52 index_ty: IndexType,
54}
55
56impl TableType {
57 pub fn new(element: ValType, min: u32, max: Option<u32>) -> Self {
63 Self::new_impl(element, IndexType::I32, u64::from(min), max.map(u64::from))
64 }
65
66 pub fn new64(element: ValType, min: u64, max: Option<u64>) -> Self {
78 Self::new_impl(element, IndexType::I64, min, max)
79 }
80
81 pub(crate) fn new_impl(
83 element: ValType,
84 index_ty: IndexType,
85 min: u64,
86 max: Option<u64>,
87 ) -> Self {
88 let absolute_max = index_ty.max_size();
89 assert!(u128::from(min) <= absolute_max);
90 max.inspect(|&max| {
91 assert!(min <= max && u128::from(max) <= absolute_max);
92 });
93 Self {
94 element,
95 min,
96 max,
97 index_ty,
98 }
99 }
100
101 pub fn is_64(&self) -> bool {
105 self.index_ty.is_64()
106 }
107
108 pub(crate) fn index_ty(&self) -> IndexType {
110 self.index_ty
111 }
112
113 pub fn element(&self) -> ValType {
115 self.element
116 }
117
118 pub fn minimum(&self) -> u64 {
120 self.min
121 }
122
123 pub fn maximum(&self) -> Option<u64> {
127 self.max
128 }
129
130 fn matches_element_type(&self, ty: ValType) -> Result<(), TableError> {
132 let expected = self.element();
133 let actual = ty;
134 if actual != expected {
135 return Err(TableError::ElementTypeMismatch { expected, actual });
136 }
137 Ok(())
138 }
139
140 pub(crate) fn is_subtype_or_err(&self, other: &TableType) -> Result<(), TableError> {
155 match self.is_subtype_of(other) {
156 true => Ok(()),
157 false => Err(TableError::InvalidSubtype {
158 ty: *self,
159 other: *other,
160 }),
161 }
162 }
163
164 pub(crate) fn is_subtype_of(&self, other: &Self) -> bool {
173 if self.is_64() != other.is_64() {
174 return false;
175 }
176 if self.matches_element_type(other.element()).is_err() {
177 return false;
178 }
179 if self.minimum() < other.minimum() {
180 return false;
181 }
182 match (self.maximum(), other.maximum()) {
183 (_, None) => true,
184 (Some(max), Some(other_max)) => max <= other_max,
185 _ => false,
186 }
187 }
188}
189
190#[derive(Debug)]
192pub struct TableEntity {
193 ty: TableType,
194 elements: Vec<UntypedVal>,
195}
196
197impl TableEntity {
198 pub fn new(
204 ty: TableType,
205 init: Val,
206 limiter: &mut ResourceLimiterRef<'_>,
207 ) -> Result<Self, TableError> {
208 ty.matches_element_type(init.ty())?;
209 let Ok(min_size) = usize::try_from(ty.minimum()) else {
210 return Err(TableError::MinimumSizeOverflow);
211 };
212 let Ok(max_size) = ty.maximum().map(usize::try_from).transpose() else {
213 return Err(TableError::MaximumSizeOverflow);
214 };
215 if let Some(limiter) = limiter.as_resource_limiter() {
216 if !limiter.table_growing(0, min_size, max_size)? {
217 return Err(TableError::ResourceLimiterDeniedAllocation);
218 }
219 }
220 let mut elements = Vec::new();
221 if elements.try_reserve(min_size).is_err() {
222 let error = TableError::OutOfSystemMemory;
223 if let Some(limiter) = limiter.as_resource_limiter() {
224 limiter.table_grow_failed(&error)
225 }
226 return Err(error);
227 };
228 elements.extend(iter::repeat_n::<UntypedVal>(init.into(), min_size));
229 Ok(Self { ty, elements })
230 }
231
232 pub fn ty(&self) -> TableType {
234 self.ty
235 }
236
237 pub fn dynamic_ty(&self) -> TableType {
244 TableType::new_impl(
245 self.ty().element(),
246 self.ty().index_ty,
247 self.size(),
248 self.ty().maximum(),
249 )
250 }
251
252 pub fn size(&self) -> u64 {
254 let len = self.elements.len();
255 let Ok(len64) = u64::try_from(len) else {
256 panic!("table.size is out of system bounds: {len}");
257 };
258 len64
259 }
260
261 pub fn grow(
274 &mut self,
275 delta: u64,
276 init: Val,
277 fuel: Option<&mut Fuel>,
278 limiter: &mut ResourceLimiterRef<'_>,
279 ) -> Result<u64, EntityGrowError> {
280 self.ty()
281 .matches_element_type(init.ty())
282 .map_err(|_| EntityGrowError::InvalidGrow)?;
283 self.grow_untyped(delta, init.into(), fuel, limiter)
284 }
285
286 pub fn grow_untyped(
300 &mut self,
301 delta: u64,
302 init: UntypedVal,
303 fuel: Option<&mut Fuel>,
304 limiter: &mut ResourceLimiterRef<'_>,
305 ) -> Result<u64, EntityGrowError> {
306 let Ok(delta_size) = usize::try_from(delta) else {
307 return Err(EntityGrowError::InvalidGrow);
308 };
309 let Some(desired) = self.size().checked_add(delta) else {
310 return Err(EntityGrowError::InvalidGrow);
311 };
312 let max_size = self.ty.index_ty.max_size() / 8;
314 if u128::from(desired) > max_size {
315 return Err(EntityGrowError::InvalidGrow);
316 }
317 let current = self.elements.len();
318 let Ok(desired) = usize::try_from(desired) else {
319 return Err(EntityGrowError::InvalidGrow);
320 };
321 let Ok(maximum) = self.ty.maximum().map(usize::try_from).transpose() else {
322 return Err(EntityGrowError::InvalidGrow);
323 };
324
325 if let Some(limiter) = limiter.as_resource_limiter() {
327 match limiter.table_growing(current, desired, maximum) {
328 Ok(true) => (),
329 Ok(false) => return Err(EntityGrowError::InvalidGrow),
330 Err(_) => return Err(EntityGrowError::TrapCode(TrapCode::GrowthOperationLimited)),
331 }
332 }
333 let notify_limiter =
334 |limiter: &mut ResourceLimiterRef<'_>| -> Result<u64, EntityGrowError> {
335 if let Some(limiter) = limiter.as_resource_limiter() {
336 limiter.table_grow_failed(&TableError::OutOfSystemMemory);
337 }
338 Err(EntityGrowError::InvalidGrow)
339 };
340 if let Some(maximum) = maximum {
341 if desired > maximum {
342 return notify_limiter(limiter);
343 }
344 }
345 if let Some(fuel) = fuel {
346 match fuel.consume_fuel(|costs| costs.fuel_for_copies(delta)) {
347 Ok(_) | Err(FuelError::FuelMeteringDisabled) => {}
348 Err(FuelError::OutOfFuel) => return notify_limiter(limiter),
349 }
350 }
351 if self.elements.try_reserve(delta_size).is_err() {
352 return notify_limiter(limiter);
353 }
354 let size_before = self.size();
355 self.elements.resize(desired, init);
356 Ok(size_before)
357 }
358
359 fn make_typed(&self, untyped: UntypedVal) -> Val {
361 untyped.with_type(self.ty().element())
362 }
363
364 pub fn get(&self, index: u64) -> Option<Val> {
368 self.get_untyped(index)
369 .map(|untyped| self.make_typed(untyped))
370 }
371
372 pub fn get_untyped(&self, index: u64) -> Option<UntypedVal> {
381 let index = usize::try_from(index).ok()?;
382 self.elements.get(index).copied()
383 }
384
385 pub fn set(&mut self, index: u64, value: Val) -> Result<(), TableError> {
392 self.ty().matches_element_type(value.ty())?;
393 self.set_untyped(index, value.into())
394 }
395
396 pub fn set_untyped(&mut self, index: u64, value: UntypedVal) -> Result<(), TableError> {
402 let current = self.size();
403 let untyped = self
404 .elements
405 .get_mut(index as usize)
406 .ok_or(TableError::AccessOutOfBounds { current, index })?;
407 *untyped = value;
408 Ok(())
409 }
410
411 pub fn init(
422 &mut self,
423 element: &ElementSegmentEntity,
424 dst_index: u64,
425 src_index: u32,
426 len: u32,
427 fuel: Option<&mut Fuel>,
428 ) -> Result<(), TrapCode> {
429 let table_type = self.ty();
430 assert!(
431 table_type.element().is_ref(),
432 "table.init currently only works on reftypes"
433 );
434 table_type
435 .matches_element_type(element.ty())
436 .map_err(|_| TrapCode::BadSignature)?;
437 let Ok(dst_index) = usize::try_from(dst_index) else {
439 return Err(TrapCode::TableOutOfBounds);
440 };
441 let Ok(src_index) = usize::try_from(src_index) else {
442 return Err(TrapCode::TableOutOfBounds);
443 };
444 let Ok(len_size) = usize::try_from(len) else {
445 return Err(TrapCode::TableOutOfBounds);
446 };
447 let dst_items = self
449 .elements
450 .get_mut(dst_index..)
451 .and_then(|items| items.get_mut(..len_size))
452 .ok_or(TrapCode::TableOutOfBounds)?;
453 let src_items = element
454 .items()
455 .get(src_index..)
456 .and_then(|items| items.get(..len_size))
457 .ok_or(TrapCode::TableOutOfBounds)?;
458 if len == 0 {
459 return Ok(());
463 }
464 if let Some(fuel) = fuel {
465 fuel.consume_fuel_if(|costs| costs.fuel_for_copies(u64::from(len)))?;
466 }
467 dst_items.copy_from_slice(src_items);
469 Ok(())
470 }
471
472 pub fn copy(
480 dst_table: &mut Self,
481 dst_index: u64,
482 src_table: &Self,
483 src_index: u64,
484 len: u64,
485 fuel: Option<&mut Fuel>,
486 ) -> Result<(), TrapCode> {
487 let Ok(src_index) = usize::try_from(src_index) else {
489 return Err(TrapCode::TableOutOfBounds);
490 };
491 let Ok(dst_index) = usize::try_from(dst_index) else {
492 return Err(TrapCode::TableOutOfBounds);
493 };
494 let Ok(len_size) = usize::try_from(len) else {
495 return Err(TrapCode::TableOutOfBounds);
496 };
497 let dst_items = dst_table
499 .elements
500 .get_mut(dst_index..)
501 .and_then(|items| items.get_mut(..len_size))
502 .ok_or(TrapCode::TableOutOfBounds)?;
503 let src_items = src_table
504 .elements
505 .get(src_index..)
506 .and_then(|items| items.get(..len_size))
507 .ok_or(TrapCode::TableOutOfBounds)?;
508 if let Some(fuel) = fuel {
509 fuel.consume_fuel_if(|costs| costs.fuel_for_copies(len))?;
510 }
511 dst_items.copy_from_slice(src_items);
513 Ok(())
514 }
515
516 pub fn copy_within(
522 &mut self,
523 dst_index: u64,
524 src_index: u64,
525 len: u64,
526 fuel: Option<&mut Fuel>,
527 ) -> Result<(), TrapCode> {
528 let max_offset = max(dst_index, src_index);
530 max_offset
531 .checked_add(len)
532 .filter(|&offset| offset <= self.size())
533 .ok_or(TrapCode::TableOutOfBounds)?;
534 let Ok(src_index) = usize::try_from(src_index) else {
536 return Err(TrapCode::TableOutOfBounds);
537 };
538 let Ok(dst_index) = usize::try_from(dst_index) else {
539 return Err(TrapCode::TableOutOfBounds);
540 };
541 let Ok(len_size) = usize::try_from(len) else {
542 return Err(TrapCode::TableOutOfBounds);
543 };
544 if let Some(fuel) = fuel {
545 fuel.consume_fuel_if(|costs| costs.fuel_for_copies(len))?;
546 }
547 self.elements
549 .copy_within(src_index..src_index.wrapping_add(len_size), dst_index);
550 Ok(())
551 }
552
553 pub fn fill(
567 &mut self,
568 dst: u64,
569 val: Val,
570 len: u64,
571 fuel: Option<&mut Fuel>,
572 ) -> Result<(), TrapCode> {
573 self.ty()
574 .matches_element_type(val.ty())
575 .map_err(|_| TrapCode::BadSignature)?;
576 self.fill_untyped(dst, val.into(), len, fuel)
577 }
578
579 pub fn fill_untyped(
595 &mut self,
596 dst: u64,
597 val: UntypedVal,
598 len: u64,
599 fuel: Option<&mut Fuel>,
600 ) -> Result<(), TrapCode> {
601 let Ok(dst_index) = usize::try_from(dst) else {
602 return Err(TrapCode::TableOutOfBounds);
603 };
604 let Ok(len_size) = usize::try_from(len) else {
605 return Err(TrapCode::TableOutOfBounds);
606 };
607 let dst = self
608 .elements
609 .get_mut(dst_index..)
610 .and_then(|elements| elements.get_mut(..len_size))
611 .ok_or(TrapCode::TableOutOfBounds)?;
612 if let Some(fuel) = fuel {
613 fuel.consume_fuel_if(|costs| costs.fuel_for_copies(len))?;
614 }
615 dst.fill(val);
616 Ok(())
617 }
618}
619
620#[derive(Debug, Copy, Clone)]
622#[repr(transparent)]
623pub struct Table(Stored<TableIdx>);
624
625impl Table {
626 pub(super) fn from_inner(stored: Stored<TableIdx>) -> Self {
628 Self(stored)
629 }
630
631 pub(super) fn as_inner(&self) -> &Stored<TableIdx> {
633 &self.0
634 }
635
636 pub fn new(mut ctx: impl AsContextMut, ty: TableType, init: Val) -> Result<Self, TableError> {
642 let (inner, mut resource_limiter) = ctx
643 .as_context_mut()
644 .store
645 .store_inner_and_resource_limiter_ref();
646 let entity = TableEntity::new(ty, init, &mut resource_limiter)?;
647 let table = inner.alloc_table(entity);
648 Ok(table)
649 }
650
651 pub fn ty(&self, ctx: impl AsContext) -> TableType {
657 ctx.as_context().store.inner.resolve_table(self).ty()
658 }
659
660 pub(crate) fn dynamic_ty(&self, ctx: impl AsContext) -> TableType {
671 ctx.as_context()
672 .store
673 .inner
674 .resolve_table(self)
675 .dynamic_ty()
676 }
677
678 pub fn size(&self, ctx: impl AsContext) -> u64 {
684 ctx.as_context().store.inner.resolve_table(self).size()
685 }
686
687 pub fn grow(
704 &self,
705 mut ctx: impl AsContextMut,
706 delta: u64,
707 init: Val,
708 ) -> Result<u64, TableError> {
709 let (inner, mut limiter) = ctx
710 .as_context_mut()
711 .store
712 .store_inner_and_resource_limiter_ref();
713 let table = inner.resolve_table_mut(self);
714 let current = table.size();
715 let maximum = table.ty().maximum().unwrap_or(u64::MAX);
716 table
717 .grow(delta, init, None, &mut limiter)
718 .map_err(|_| TableError::GrowOutOfBounds {
719 maximum,
720 current,
721 delta,
722 })
723 }
724
725 pub fn get(&self, ctx: impl AsContext, index: u64) -> Option<Val> {
733 ctx.as_context().store.inner.resolve_table(self).get(index)
734 }
735
736 pub fn set(
747 &self,
748 mut ctx: impl AsContextMut,
749 index: u64,
750 value: Val,
751 ) -> Result<(), TableError> {
752 ctx.as_context_mut()
753 .store
754 .inner
755 .resolve_table_mut(self)
756 .set(index, value)
757 }
758
759 #[inline]
766 pub(crate) fn eq(lhs: &Self, rhs: &Self) -> bool {
767 lhs.as_inner() == rhs.as_inner()
768 }
769
770 pub fn copy(
782 mut store: impl AsContextMut,
783 dst_table: &Table,
784 dst_index: u64,
785 src_table: &Table,
786 src_index: u64,
787 len: u64,
788 ) -> Result<(), TableError> {
789 if Self::eq(dst_table, src_table) {
790 let table = store
793 .as_context_mut()
794 .store
795 .inner
796 .resolve_table_mut(dst_table);
797 table
798 .copy_within(dst_index, src_index, len, None)
799 .map_err(|_| TableError::CopyOutOfBounds)
800 } else {
801 let dst_ty = dst_table.ty(&store);
804 let src_ty = src_table.ty(&store).element();
805 dst_ty.matches_element_type(src_ty)?;
806 let (dst_table, src_table, _fuel) = store
807 .as_context_mut()
808 .store
809 .inner
810 .resolve_table_pair_and_fuel(dst_table, src_table);
811 TableEntity::copy(dst_table, dst_index, src_table, src_index, len, None)
812 .map_err(|_| TableError::CopyOutOfBounds)
813 }
814 }
815
816 pub fn fill(
830 &self,
831 mut ctx: impl AsContextMut,
832 dst: u64,
833 val: Val,
834 len: u64,
835 ) -> Result<(), TrapCode> {
836 ctx.as_context_mut()
837 .store
838 .inner
839 .resolve_table_mut(self)
840 .fill(dst, val, len, None)
841 }
842}