wasmtime/runtime/vm/instance/allocator/pooling/
table_pool.rs1use super::{
2 index_allocator::{SimpleIndexAllocator, SlotId},
3 TableAllocationIndex,
4};
5use crate::runtime::vm::sys::vm::commit_pages;
6use crate::runtime::vm::{
7 mmap::AlignedLength, InstanceAllocationRequest, Mmap, PoolingInstanceAllocatorConfig,
8 SendSyncPtr, Table,
9};
10use crate::{prelude::*, vm::HostAlignedByteCount};
11use std::mem;
12use std::ptr::NonNull;
13use wasmtime_environ::{Module, Tunables};
14
15#[derive(Debug)]
20pub struct TablePool {
21 index_allocator: SimpleIndexAllocator,
22 mapping: Mmap<AlignedLength>,
23 table_size: HostAlignedByteCount,
24 max_total_tables: usize,
25 tables_per_instance: usize,
26 keep_resident: HostAlignedByteCount,
27 table_elements: usize,
28}
29
30impl TablePool {
31 pub fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
33 let table_size = HostAlignedByteCount::new_rounded_up(
34 mem::size_of::<*mut u8>()
35 .checked_mul(config.limits.table_elements)
36 .ok_or_else(|| anyhow!("table size exceeds addressable memory"))?,
37 )?;
38
39 let max_total_tables = usize::try_from(config.limits.total_tables).unwrap();
40 let tables_per_instance = usize::try_from(config.limits.max_tables_per_module).unwrap();
41
42 let allocation_size = table_size
43 .checked_mul(max_total_tables)
44 .context("total size of tables exceeds addressable memory")?;
45
46 let mapping = Mmap::accessible_reserved(allocation_size, allocation_size)
47 .context("failed to create table pool mapping")?;
48
49 Ok(Self {
50 index_allocator: SimpleIndexAllocator::new(config.limits.total_tables),
51 mapping,
52 table_size,
53 max_total_tables,
54 tables_per_instance,
55 keep_resident: HostAlignedByteCount::new_rounded_up(config.table_keep_resident)?,
56 table_elements: usize::try_from(config.limits.table_elements).unwrap(),
57 })
58 }
59
60 pub fn validate(&self, module: &Module) -> Result<()> {
62 let tables = module.num_defined_tables();
63
64 if tables > usize::try_from(self.tables_per_instance).unwrap() {
65 bail!(
66 "defined tables count of {} exceeds the per-instance limit of {}",
67 tables,
68 self.tables_per_instance,
69 );
70 }
71
72 if tables > self.max_total_tables {
73 bail!(
74 "defined tables count of {} exceeds the total tables limit of {}",
75 tables,
76 self.max_total_tables,
77 );
78 }
79
80 for (i, table) in module.tables.iter().skip(module.num_imported_tables) {
81 if table.limits.min > u64::try_from(self.table_elements)? {
82 bail!(
83 "table index {} has a minimum element size of {} which exceeds the limit of {}",
84 i.as_u32(),
85 table.limits.min,
86 self.table_elements,
87 );
88 }
89 }
90 Ok(())
91 }
92
93 #[allow(unused)] pub fn is_empty(&self) -> bool {
96 self.index_allocator.is_empty()
97 }
98
99 fn get(&self, table_index: TableAllocationIndex) -> *mut u8 {
101 assert!(table_index.index() < self.max_total_tables);
102
103 unsafe {
104 self.mapping
105 .as_ptr()
106 .add(
107 self.table_size
108 .checked_mul(table_index.index())
109 .expect(
110 "checked in constructor that table_size * table_index doesn't overflow",
111 )
112 .byte_count(),
113 )
114 .cast_mut()
115 }
116 }
117
118 pub fn allocate(
120 &self,
121 request: &mut InstanceAllocationRequest,
122 ty: &wasmtime_environ::Table,
123 tunables: &Tunables,
124 ) -> Result<(TableAllocationIndex, Table)> {
125 let allocation_index = self
126 .index_allocator
127 .alloc()
128 .map(|slot| TableAllocationIndex(slot.0))
129 .ok_or_else(|| {
130 super::PoolConcurrencyLimitError::new(self.max_total_tables, "tables")
131 })?;
132
133 match (|| {
134 let base = self.get(allocation_index);
135
136 unsafe {
137 commit_pages(base, self.table_elements * mem::size_of::<*mut u8>())?;
138 }
139
140 let ptr = NonNull::new(std::ptr::slice_from_raw_parts_mut(
141 base.cast(),
142 self.table_elements * mem::size_of::<*mut u8>(),
143 ))
144 .unwrap();
145 unsafe {
146 Table::new_static(
147 ty,
148 tunables,
149 SendSyncPtr::new(ptr),
150 &mut *request.store.get().unwrap(),
151 )
152 }
153 })() {
154 Ok(table) => Ok((allocation_index, table)),
155 Err(e) => {
156 self.index_allocator.free(SlotId(allocation_index.0));
157 Err(e)
158 }
159 }
160 }
161
162 pub unsafe fn deallocate(&self, allocation_index: TableAllocationIndex, table: Table) {
173 assert!(table.is_static());
174 drop(table);
175 self.index_allocator.free(SlotId(allocation_index.0));
176 }
177
178 pub unsafe fn reset_table_pages_to_zero(
189 &self,
190 allocation_index: TableAllocationIndex,
191 table: &mut Table,
192 mut decommit: impl FnMut(*mut u8, usize),
193 ) {
194 assert!(table.is_static());
195 let base = self.get(allocation_index);
196
197 let size = HostAlignedByteCount::new_rounded_up(table.size() * mem::size_of::<*mut u8>())
202 .expect("table entry size doesn't overflow");
203
204 let size_to_memset = size.min(self.keep_resident);
206 std::ptr::write_bytes(base, 0, size_to_memset.byte_count());
207
208 decommit(
210 base.add(size_to_memset.byte_count()),
211 size.checked_sub(size_to_memset)
212 .expect("size_to_memset <= size")
213 .byte_count(),
214 );
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::runtime::vm::InstanceLimits;
222
223 #[test]
224 fn test_table_pool() -> Result<()> {
225 let pool = TablePool::new(&PoolingInstanceAllocatorConfig {
226 limits: InstanceLimits {
227 total_tables: 7,
228 table_elements: 100,
229 max_memory_size: 0,
230 max_memories_per_module: 0,
231 ..Default::default()
232 },
233 ..Default::default()
234 })?;
235
236 let host_page_size = HostAlignedByteCount::host_page_size();
237
238 assert_eq!(pool.table_size, host_page_size);
239 assert_eq!(pool.max_total_tables, 7);
240 assert_eq!(pool.table_elements, 100);
241
242 let base = pool.mapping.as_ptr() as usize;
243
244 for i in 0..7 {
245 let index = TableAllocationIndex(i);
246 let ptr = pool.get(index);
247 assert_eq!(
248 ptr as usize - base,
249 pool.table_size.checked_mul(i as usize).unwrap()
250 );
251 }
252
253 Ok(())
254 }
255}