1use super::{InstanceAllocationRequest, InstanceAllocator};
11use crate::{instance::Instance, Memory, Mmap, Table};
12use crate::{CompiledModuleId, MemoryImageSlot};
13use anyhow::{anyhow, bail, Context, Result};
14use libc::c_void;
15use std::convert::TryFrom;
16use std::mem;
17use std::sync::Mutex;
18use wasmtime_environ::{
19 DefinedMemoryIndex, DefinedTableIndex, HostPtr, MemoryStyle, Module, PrimaryMap, Tunables,
20 VMOffsets, WASM_PAGE_SIZE,
21};
22
23mod index_allocator;
24use index_allocator::{IndexAllocator, SlotId};
25
26cfg_if::cfg_if! {
27 if #[cfg(windows)] {
28 mod windows;
29 use windows as imp;
30 } else {
31 mod unix;
32 use unix as imp;
33 }
34}
35
36use imp::{commit_table_pages, decommit_table_pages};
37
38#[cfg(all(feature = "async", unix))]
39use imp::{commit_stack_pages, reset_stack_pages_to_zero};
40
41fn round_up_to_pow2(n: usize, to: usize) -> usize {
42 debug_assert!(to > 0);
43 debug_assert!(to.is_power_of_two());
44 (n + to - 1) & !(to - 1)
45}
46
47#[derive(Debug, Copy, Clone)]
51pub struct InstanceLimits {
52 pub count: u32,
54
55 pub size: usize,
57
58 pub tables: u32,
60
61 pub table_elements: u32,
63
64 pub memories: u32,
66
67 pub memory_pages: u64,
69}
70
71impl Default for InstanceLimits {
72 fn default() -> Self {
73 Self {
76 count: 1000,
77 size: 1 << 20, tables: 1,
79 table_elements: 10_000,
80 memories: 1,
81 memory_pages: 160,
82 }
83 }
84}
85
86#[derive(Debug)]
113struct MemoryPool {
114 mapping: Mmap,
115 image_slots: Vec<Mutex<Option<MemoryImageSlot>>>,
119 memory_size: usize,
122 memory_and_guard_size: usize,
125 max_accessible: usize,
128 initial_memory_offset: usize,
132 max_memories: usize,
133 max_instances: usize,
134}
135
136impl MemoryPool {
137 fn new(instance_limits: &InstanceLimits, tunables: &Tunables) -> Result<Self> {
138 if instance_limits.memory_pages > 0x10000 {
140 bail!(
141 "module memory page limit of {} exceeds the maximum of 65536",
142 instance_limits.memory_pages
143 );
144 }
145
146 let memory_size = instance_limits
153 .memory_pages
154 .max(tunables.static_memory_bound)
155 * u64::from(WASM_PAGE_SIZE);
156
157 let memory_and_guard_size =
158 usize::try_from(memory_size + tunables.static_memory_offset_guard_size)
159 .map_err(|_| anyhow!("memory reservation size exceeds addressable memory"))?;
160
161 assert!(
162 memory_and_guard_size % crate::page_size() == 0,
163 "memory size {} is not a multiple of system page size",
164 memory_and_guard_size
165 );
166
167 let max_instances = instance_limits.count as usize;
168 let max_memories = instance_limits.memories as usize;
169 let initial_memory_offset = if tunables.guard_before_linear_memory {
170 usize::try_from(tunables.static_memory_offset_guard_size).unwrap()
171 } else {
172 0
173 };
174
175 let allocation_size = memory_and_guard_size
189 .checked_mul(max_memories)
190 .and_then(|c| c.checked_mul(max_instances))
191 .and_then(|c| c.checked_add(initial_memory_offset))
192 .ok_or_else(|| {
193 anyhow!("total size of memory reservation exceeds addressable memory")
194 })?;
195
196 let mapping = Mmap::accessible_reserved(0, allocation_size)
198 .context("failed to create memory pool mapping")?;
199
200 let num_image_slots = max_instances * max_memories;
201 let image_slots: Vec<_> = std::iter::repeat_with(|| Mutex::new(None))
202 .take(num_image_slots)
203 .collect();
204
205 let pool = Self {
206 mapping,
207 image_slots,
208 memory_size: memory_size.try_into().unwrap(),
209 memory_and_guard_size,
210 initial_memory_offset,
211 max_memories,
212 max_instances,
213 max_accessible: (instance_limits.memory_pages as usize) * (WASM_PAGE_SIZE as usize),
214 };
215
216 Ok(pool)
217 }
218
219 fn get_base(&self, instance_index: usize, memory_index: DefinedMemoryIndex) -> *mut u8 {
220 assert!(instance_index < self.max_instances);
221 let memory_index = memory_index.as_u32() as usize;
222 assert!(memory_index < self.max_memories);
223 let idx = instance_index * self.max_memories + memory_index;
224 let offset = self.initial_memory_offset + idx * self.memory_and_guard_size;
225 unsafe { self.mapping.as_mut_ptr().offset(offset as isize) }
226 }
227
228 #[cfg(test)]
229 fn get<'a>(&'a self, instance_index: usize) -> impl Iterator<Item = *mut u8> + 'a {
230 (0..self.max_memories)
231 .map(move |i| self.get_base(instance_index, DefinedMemoryIndex::from_u32(i as u32)))
232 }
233
234 fn take_memory_image_slot(
237 &self,
238 instance_index: usize,
239 memory_index: DefinedMemoryIndex,
240 ) -> MemoryImageSlot {
241 let idx = instance_index * self.max_memories + (memory_index.as_u32() as usize);
242 let maybe_slot = self.image_slots[idx].lock().unwrap().take();
243
244 maybe_slot.unwrap_or_else(|| {
245 MemoryImageSlot::create(
246 self.get_base(instance_index, memory_index) as *mut c_void,
247 0,
248 self.max_accessible,
249 )
250 })
251 }
252
253 fn return_memory_image_slot(
255 &self,
256 instance_index: usize,
257 memory_index: DefinedMemoryIndex,
258 slot: MemoryImageSlot,
259 ) {
260 assert!(!slot.is_dirty());
261 let idx = instance_index * self.max_memories + (memory_index.as_u32() as usize);
262 *self.image_slots[idx].lock().unwrap() = Some(slot);
263 }
264
265 fn clear_images(&self, instance_index: usize) {
272 for i in 0..self.max_memories {
273 let index = DefinedMemoryIndex::from_u32(i as u32);
274
275 let mut slot = self.take_memory_image_slot(instance_index, index);
279 if slot.remove_image().is_ok() {
280 self.return_memory_image_slot(instance_index, index, slot);
281 }
282 }
283 }
284}
285
286impl Drop for MemoryPool {
287 fn drop(&mut self) {
288 for mut slot in std::mem::take(&mut self.image_slots) {
293 if let Some(slot) = slot.get_mut().unwrap() {
294 slot.no_clear_on_drop();
295 }
296 }
297 }
298}
299
300#[derive(Debug)]
305struct TablePool {
306 mapping: Mmap,
307 table_size: usize,
308 max_tables: usize,
309 max_instances: usize,
310 page_size: usize,
311 max_elements: u32,
312}
313
314impl TablePool {
315 fn new(instance_limits: &InstanceLimits) -> Result<Self> {
316 let page_size = crate::page_size();
317
318 let table_size = round_up_to_pow2(
319 mem::size_of::<*mut u8>()
320 .checked_mul(instance_limits.table_elements as usize)
321 .ok_or_else(|| anyhow!("table size exceeds addressable memory"))?,
322 page_size,
323 );
324
325 let max_instances = instance_limits.count as usize;
326 let max_tables = instance_limits.tables as usize;
327
328 let allocation_size = table_size
329 .checked_mul(max_tables)
330 .and_then(|c| c.checked_mul(max_instances))
331 .ok_or_else(|| anyhow!("total size of instance tables exceeds addressable memory"))?;
332
333 let mapping = Mmap::accessible_reserved(allocation_size, allocation_size)
334 .context("failed to create table pool mapping")?;
335
336 Ok(Self {
337 mapping,
338 table_size,
339 max_tables,
340 max_instances,
341 page_size,
342 max_elements: instance_limits.table_elements,
343 })
344 }
345
346 fn get(&self, instance_index: usize) -> impl Iterator<Item = *mut u8> {
347 assert!(instance_index < self.max_instances);
348
349 let base: *mut u8 = unsafe {
350 self.mapping
351 .as_mut_ptr()
352 .add(instance_index * self.table_size * self.max_tables) as _
353 };
354
355 let size = self.table_size;
356 (0..self.max_tables).map(move |i| unsafe { base.add(i * size) })
357 }
358}
359
360#[cfg(all(feature = "async", unix))]
371#[derive(Debug)]
372struct StackPool {
373 mapping: Mmap,
374 stack_size: usize,
375 max_instances: usize,
376 page_size: usize,
377 index_allocator: IndexAllocator,
378 async_stack_zeroing: bool,
379 async_stack_keep_resident: usize,
380}
381
382#[cfg(all(feature = "async", unix))]
383impl StackPool {
384 fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
385 use rustix::mm::{mprotect, MprotectFlags};
386
387 let page_size = crate::page_size();
388
389 let stack_size = if config.stack_size == 0 {
391 0
392 } else {
393 round_up_to_pow2(config.stack_size, page_size)
394 .checked_add(page_size)
395 .ok_or_else(|| anyhow!("stack size exceeds addressable memory"))?
396 };
397
398 let max_instances = config.limits.count as usize;
399
400 let allocation_size = stack_size
401 .checked_mul(max_instances)
402 .ok_or_else(|| anyhow!("total size of execution stacks exceeds addressable memory"))?;
403
404 let mapping = Mmap::accessible_reserved(allocation_size, allocation_size)
405 .context("failed to create stack pool mapping")?;
406
407 if allocation_size > 0 {
409 unsafe {
410 for i in 0..max_instances {
411 let bottom_of_stack = mapping.as_mut_ptr().add(i * stack_size);
413 mprotect(bottom_of_stack.cast(), page_size, MprotectFlags::empty())
414 .context("failed to protect stack guard page")?;
415 }
416 }
417 }
418
419 Ok(Self {
420 mapping,
421 stack_size,
422 max_instances,
423 page_size,
424 async_stack_zeroing: config.async_stack_zeroing,
425 async_stack_keep_resident: config.async_stack_keep_resident,
426 index_allocator: IndexAllocator::new(config.limits.count, 0),
430 })
431 }
432
433 fn allocate(&self) -> Result<wasmtime_fiber::FiberStack> {
434 if self.stack_size == 0 {
435 bail!("pooling allocator not configured to enable fiber stack allocation");
436 }
437
438 let index = self
439 .index_allocator
440 .alloc(None)
441 .ok_or_else(|| {
442 anyhow!(
443 "maximum concurrent fiber limit of {} reached",
444 self.max_instances
445 )
446 })?
447 .index();
448
449 assert!(index < self.max_instances);
450
451 unsafe {
452 let size_without_guard = self.stack_size - self.page_size;
454
455 let bottom_of_stack = self
456 .mapping
457 .as_mut_ptr()
458 .add((index * self.stack_size) + self.page_size);
459
460 commit_stack_pages(bottom_of_stack, size_without_guard)?;
461
462 let stack =
463 wasmtime_fiber::FiberStack::from_top_ptr(bottom_of_stack.add(size_without_guard))?;
464 Ok(stack)
465 }
466 }
467
468 fn deallocate(&self, stack: &wasmtime_fiber::FiberStack) {
469 let top = stack
470 .top()
471 .expect("fiber stack not allocated from the pool") as usize;
472
473 let base = self.mapping.as_ptr() as usize;
474 let len = self.mapping.len();
475 assert!(
476 top > base && top <= (base + len),
477 "fiber stack top pointer not in range"
478 );
479
480 let stack_size = self.stack_size - self.page_size;
482 let bottom_of_stack = top - stack_size;
483 let start_of_stack = bottom_of_stack - self.page_size;
484 assert!(start_of_stack >= base && start_of_stack < (base + len));
485 assert!((start_of_stack - base) % self.stack_size == 0);
486
487 let index = (start_of_stack - base) / self.stack_size;
488 assert!(index < self.max_instances);
489
490 if self.async_stack_zeroing {
491 self.zero_stack(bottom_of_stack, stack_size);
492 }
493
494 self.index_allocator.free(SlotId(index as u32));
495 }
496
497 fn zero_stack(&self, bottom: usize, size: usize) {
498 let size_to_memset = size.min(self.async_stack_keep_resident);
506 unsafe {
507 std::ptr::write_bytes(
508 (bottom + size - size_to_memset) as *mut u8,
509 0,
510 size_to_memset,
511 );
512 }
513
514 reset_stack_pages_to_zero(bottom as _, size - size_to_memset).unwrap();
516 }
517}
518
519#[derive(Copy, Clone, Debug)]
522pub struct PoolingInstanceAllocatorConfig {
523 pub max_unused_warm_slots: u32,
525 pub stack_size: usize,
528 pub limits: InstanceLimits,
530 pub async_stack_zeroing: bool,
532 pub async_stack_keep_resident: usize,
537 pub linear_memory_keep_resident: usize,
546 pub table_keep_resident: usize,
548}
549
550impl Default for PoolingInstanceAllocatorConfig {
551 fn default() -> PoolingInstanceAllocatorConfig {
552 PoolingInstanceAllocatorConfig {
553 max_unused_warm_slots: 100,
554 stack_size: 2 << 20,
555 limits: InstanceLimits::default(),
556 async_stack_zeroing: false,
557 async_stack_keep_resident: 0,
558 linear_memory_keep_resident: 0,
559 table_keep_resident: 0,
560 }
561 }
562}
563
564#[derive(Debug)]
570pub struct PoolingInstanceAllocator {
571 instance_size: usize,
572 max_instances: usize,
573 index_allocator: IndexAllocator,
574 memories: MemoryPool,
575 tables: TablePool,
576 linear_memory_keep_resident: usize,
577 table_keep_resident: usize,
578
579 #[cfg(all(feature = "async", unix))]
580 stacks: StackPool,
581 #[cfg(all(feature = "async", windows))]
582 stack_size: usize,
583}
584
585impl PoolingInstanceAllocator {
586 pub fn new(config: &PoolingInstanceAllocatorConfig, tunables: &Tunables) -> Result<Self> {
588 if config.limits.count == 0 {
589 bail!("the instance count limit cannot be zero");
590 }
591
592 let max_instances = config.limits.count as usize;
593
594 Ok(Self {
595 instance_size: round_up_to_pow2(config.limits.size, mem::align_of::<Instance>()),
596 max_instances,
597 index_allocator: IndexAllocator::new(config.limits.count, config.max_unused_warm_slots),
598 memories: MemoryPool::new(&config.limits, tunables)?,
599 tables: TablePool::new(&config.limits)?,
600 linear_memory_keep_resident: config.linear_memory_keep_resident,
601 table_keep_resident: config.table_keep_resident,
602 #[cfg(all(feature = "async", unix))]
603 stacks: StackPool::new(config)?,
604 #[cfg(all(feature = "async", windows))]
605 stack_size: config.stack_size,
606 })
607 }
608
609 fn reset_table_pages_to_zero(&self, base: *mut u8, size: usize) -> Result<()> {
610 let size_to_memset = size.min(self.table_keep_resident);
611 unsafe {
612 std::ptr::write_bytes(base, 0, size_to_memset);
613 decommit_table_pages(base.add(size_to_memset), size - size_to_memset)?;
614 }
615 Ok(())
616 }
617
618 fn validate_table_plans(&self, module: &Module) -> Result<()> {
619 let tables = module.table_plans.len() - module.num_imported_tables;
620 if tables > self.tables.max_tables {
621 bail!(
622 "defined tables count of {} exceeds the limit of {}",
623 tables,
624 self.tables.max_tables,
625 );
626 }
627
628 for (i, plan) in module.table_plans.iter().skip(module.num_imported_tables) {
629 if plan.table.minimum > self.tables.max_elements {
630 bail!(
631 "table index {} has a minimum element size of {} which exceeds the limit of {}",
632 i.as_u32(),
633 plan.table.minimum,
634 self.tables.max_elements,
635 );
636 }
637 }
638 Ok(())
639 }
640
641 fn validate_memory_plans(&self, module: &Module) -> Result<()> {
642 let memories = module.memory_plans.len() - module.num_imported_memories;
643 if memories > self.memories.max_memories {
644 bail!(
645 "defined memories count of {} exceeds the limit of {}",
646 memories,
647 self.memories.max_memories,
648 );
649 }
650
651 for (i, plan) in module
652 .memory_plans
653 .iter()
654 .skip(module.num_imported_memories)
655 {
656 match plan.style {
657 MemoryStyle::Static { bound } => {
658 if (self.memories.memory_size as u64) < bound {
659 bail!(
660 "memory size allocated per-memory is too small to \
661 satisfy static bound of {bound:#x} pages"
662 );
663 }
664 }
665 MemoryStyle::Dynamic { .. } => {}
666 }
667 let max = self.memories.max_accessible / (WASM_PAGE_SIZE as usize);
668 if plan.memory.minimum > (max as u64) {
669 bail!(
670 "memory index {} has a minimum page size of {} which exceeds the limit of {}",
671 i.as_u32(),
672 plan.memory.minimum,
673 max,
674 );
675 }
676 }
677 Ok(())
678 }
679
680 fn validate_instance_size(&self, offsets: &VMOffsets<HostPtr>) -> Result<()> {
681 let layout = Instance::alloc_layout(offsets);
682 if layout.size() <= self.instance_size {
683 return Ok(());
684 }
685
686 let mut message = format!(
694 "instance allocation for this module \
695 requires {} bytes which exceeds the configured maximum \
696 of {} bytes; breakdown of allocation requirement:\n\n",
697 layout.size(),
698 self.instance_size,
699 );
700
701 let mut remaining = layout.size();
702 let mut push = |name: &str, bytes: usize| {
703 assert!(remaining >= bytes);
704 remaining -= bytes;
705
706 if bytes > layout.size() / 20 {
713 message.push_str(&format!(
714 " * {:.02}% - {} bytes - {}\n",
715 ((bytes as f32) / (layout.size() as f32)) * 100.0,
716 bytes,
717 name,
718 ));
719 }
720 };
721
722 push("instance state management", mem::size_of::<Instance>());
724
725 for (desc, size) in offsets.region_sizes() {
728 push(desc, size as usize);
729 }
730
731 assert_eq!(remaining, 0);
733
734 bail!("{}", message)
735 }
736}
737
738unsafe impl InstanceAllocator for PoolingInstanceAllocator {
739 fn validate(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> {
740 self.validate_memory_plans(module)?;
741 self.validate_table_plans(module)?;
742 self.validate_instance_size(offsets)?;
743
744 Ok(())
745 }
746
747 fn allocate_index(&self, req: &InstanceAllocationRequest) -> Result<usize> {
748 self.index_allocator
749 .alloc(req.runtime_info.unique_id())
750 .map(|id| id.index())
751 .ok_or_else(|| {
752 anyhow!(
753 "maximum concurrent instance limit of {} reached",
754 self.max_instances
755 )
756 })
757 }
758
759 fn deallocate_index(&self, index: usize) {
760 self.index_allocator.free(SlotId(index as u32));
761 }
762
763 fn allocate_memories(
764 &self,
765 index: usize,
766 req: &mut InstanceAllocationRequest,
767 memories: &mut PrimaryMap<DefinedMemoryIndex, Memory>,
768 ) -> Result<()> {
769 let module = req.runtime_info.module();
770
771 self.validate_memory_plans(module)?;
772
773 for (memory_index, plan) in module
774 .memory_plans
775 .iter()
776 .skip(module.num_imported_memories)
777 {
778 let defined_index = module
779 .defined_memory_index(memory_index)
780 .expect("should be a defined memory since we skipped imported ones");
781
782 match plan.style {
787 MemoryStyle::Static { bound } => {
788 let bound = bound * u64::from(WASM_PAGE_SIZE);
789 assert!(bound <= (self.memories.memory_size as u64));
790 }
791 MemoryStyle::Dynamic { .. } => {}
792 }
793
794 let memory = unsafe {
795 std::slice::from_raw_parts_mut(
796 self.memories.get_base(index, defined_index),
797 self.memories.max_accessible,
798 )
799 };
800
801 let mut slot = self.memories.take_memory_image_slot(index, defined_index);
802 let image = req.runtime_info.memory_image(defined_index)?;
803 let initial_size = plan.memory.minimum * WASM_PAGE_SIZE as u64;
804
805 slot.instantiate(initial_size as usize, image, &plan)?;
819
820 memories.push(Memory::new_static(
821 plan,
822 memory,
823 slot,
824 self.memories.memory_and_guard_size,
825 unsafe { &mut *req.store.get().unwrap() },
826 )?);
827 }
828
829 Ok(())
830 }
831
832 fn deallocate_memories(&self, index: usize, mems: &mut PrimaryMap<DefinedMemoryIndex, Memory>) {
833 for (def_mem_idx, memory) in mem::take(mems) {
835 let mut image = memory.unwrap_static_image();
836 if image
841 .clear_and_remain_ready(self.linear_memory_keep_resident)
842 .is_ok()
843 {
844 self.memories
845 .return_memory_image_slot(index, def_mem_idx, image);
846 }
847 }
848 }
849
850 fn allocate_tables(
851 &self,
852 index: usize,
853 req: &mut InstanceAllocationRequest,
854 tables: &mut PrimaryMap<DefinedTableIndex, Table>,
855 ) -> Result<()> {
856 let module = req.runtime_info.module();
857
858 self.validate_table_plans(module)?;
859
860 let mut bases = self.tables.get(index);
861 for (_, plan) in module.table_plans.iter().skip(module.num_imported_tables) {
862 let base = bases.next().unwrap() as _;
863
864 commit_table_pages(
865 base as *mut u8,
866 self.tables.max_elements as usize * mem::size_of::<*mut u8>(),
867 )?;
868
869 tables.push(Table::new_static(
870 plan,
871 unsafe { std::slice::from_raw_parts_mut(base, self.tables.max_elements as usize) },
872 unsafe { &mut *req.store.get().unwrap() },
873 )?);
874 }
875
876 Ok(())
877 }
878
879 fn deallocate_tables(&self, index: usize, tables: &mut PrimaryMap<DefinedTableIndex, Table>) {
880 for (table, base) in tables.values_mut().zip(self.tables.get(index)) {
882 let table = mem::take(table);
883 assert!(table.is_static());
884
885 let size = round_up_to_pow2(
886 table.size() as usize * mem::size_of::<*mut u8>(),
887 self.tables.page_size,
888 );
889
890 drop(table);
891 self.reset_table_pages_to_zero(base, size)
892 .expect("failed to decommit table pages");
893 }
894 }
895
896 #[cfg(all(feature = "async", unix))]
897 fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
898 self.stacks.allocate()
899 }
900
901 #[cfg(all(feature = "async", unix))]
902 unsafe fn deallocate_fiber_stack(&self, stack: &wasmtime_fiber::FiberStack) {
903 self.stacks.deallocate(stack);
904 }
905
906 #[cfg(all(feature = "async", windows))]
907 fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
908 if self.stack_size == 0 {
909 bail!("fiber stack allocation not supported")
910 }
911
912 let stack = wasmtime_fiber::FiberStack::new(self.stack_size)?;
914 Ok(stack)
915 }
916
917 #[cfg(all(feature = "async", windows))]
918 unsafe fn deallocate_fiber_stack(&self, _stack: &wasmtime_fiber::FiberStack) {
919 }
921
922 fn purge_module(&self, module: CompiledModuleId) {
923 while let Some(index) = self.index_allocator.alloc_affine_and_clear_affinity(module) {
933 self.memories.clear_images(index.index());
934 self.index_allocator.free(index);
935 }
936 }
937}
938
939#[cfg(test)]
940mod test {
941 use super::*;
942 use crate::{
943 CompiledModuleId, Imports, MemoryImage, ModuleRuntimeInfo, StorePtr, VMFunctionBody,
944 VMSharedSignatureIndex,
945 };
946 use std::sync::Arc;
947 use wasmtime_environ::{DefinedFuncIndex, DefinedMemoryIndex};
948
949 pub(crate) fn empty_runtime_info(
950 module: Arc<wasmtime_environ::Module>,
951 ) -> Arc<dyn ModuleRuntimeInfo> {
952 struct RuntimeInfo(Arc<wasmtime_environ::Module>, VMOffsets<HostPtr>);
953
954 impl ModuleRuntimeInfo for RuntimeInfo {
955 fn module(&self) -> &Arc<wasmtime_environ::Module> {
956 &self.0
957 }
958 fn function(&self, _: DefinedFuncIndex) -> *mut VMFunctionBody {
959 unimplemented!()
960 }
961 fn memory_image(
962 &self,
963 _: DefinedMemoryIndex,
964 ) -> anyhow::Result<Option<&Arc<MemoryImage>>> {
965 Ok(None)
966 }
967
968 fn unique_id(&self) -> Option<CompiledModuleId> {
969 None
970 }
971 fn wasm_data(&self) -> &[u8] {
972 &[]
973 }
974 fn signature_ids(&self) -> &[VMSharedSignatureIndex] {
975 &[]
976 }
977 fn offsets(&self) -> &VMOffsets<HostPtr> {
978 &self.1
979 }
980 }
981
982 let offsets = VMOffsets::new(HostPtr, &module);
983 Arc::new(RuntimeInfo(module, offsets))
984 }
985
986 #[cfg(target_pointer_width = "64")]
987 #[test]
988 fn test_instance_pool() -> Result<()> {
989 let mut config = PoolingInstanceAllocatorConfig::default();
990 config.max_unused_warm_slots = 0;
991 config.limits = InstanceLimits {
992 count: 3,
993 tables: 1,
994 memories: 1,
995 table_elements: 10,
996 size: 1000,
997 memory_pages: 1,
998 ..Default::default()
999 };
1000
1001 let instances = PoolingInstanceAllocator::new(
1002 &config,
1003 &Tunables {
1004 static_memory_bound: 1,
1005 ..Tunables::default()
1006 },
1007 )?;
1008
1009 assert_eq!(instances.instance_size, 1008); assert_eq!(instances.max_instances, 3);
1011
1012 assert_eq!(instances.index_allocator.testing_freelist(), []);
1013
1014 let mut handles = Vec::new();
1015 let module = Arc::new(Module::default());
1016
1017 for _ in (0..3).rev() {
1018 handles.push(
1019 instances
1020 .allocate(InstanceAllocationRequest {
1021 runtime_info: &empty_runtime_info(module.clone()),
1022 imports: Imports {
1023 functions: &[],
1024 tables: &[],
1025 memories: &[],
1026 globals: &[],
1027 },
1028 host_state: Box::new(()),
1029 store: StorePtr::empty(),
1030 })
1031 .expect("allocation should succeed"),
1032 );
1033 }
1034
1035 assert_eq!(instances.index_allocator.testing_freelist(), []);
1036
1037 match instances.allocate(InstanceAllocationRequest {
1038 runtime_info: &empty_runtime_info(module),
1039 imports: Imports {
1040 functions: &[],
1041 tables: &[],
1042 memories: &[],
1043 globals: &[],
1044 },
1045 host_state: Box::new(()),
1046 store: StorePtr::empty(),
1047 }) {
1048 Err(_) => {}
1049 _ => panic!("unexpected error"),
1050 };
1051
1052 for mut handle in handles.drain(..) {
1053 instances.deallocate(&mut handle);
1054 }
1055
1056 assert_eq!(
1057 instances.index_allocator.testing_freelist(),
1058 [SlotId(0), SlotId(1), SlotId(2)]
1059 );
1060
1061 Ok(())
1062 }
1063
1064 #[cfg(target_pointer_width = "64")]
1065 #[test]
1066 fn test_memory_pool() -> Result<()> {
1067 let pool = MemoryPool::new(
1068 &InstanceLimits {
1069 count: 5,
1070 tables: 0,
1071 memories: 3,
1072 table_elements: 0,
1073 memory_pages: 1,
1074 ..Default::default()
1075 },
1076 &Tunables {
1077 static_memory_bound: 1,
1078 static_memory_offset_guard_size: 0,
1079 ..Tunables::default()
1080 },
1081 )?;
1082
1083 assert_eq!(pool.memory_and_guard_size, WASM_PAGE_SIZE as usize);
1084 assert_eq!(pool.max_memories, 3);
1085 assert_eq!(pool.max_instances, 5);
1086 assert_eq!(pool.max_accessible, WASM_PAGE_SIZE as usize);
1087
1088 let base = pool.mapping.as_ptr() as usize;
1089
1090 for i in 0..5 {
1091 let mut iter = pool.get(i);
1092
1093 for j in 0..3 {
1094 assert_eq!(
1095 iter.next().unwrap() as usize - base,
1096 ((i * 3) + j) * pool.memory_and_guard_size
1097 );
1098 }
1099
1100 assert_eq!(iter.next(), None);
1101 }
1102
1103 Ok(())
1104 }
1105
1106 #[cfg(target_pointer_width = "64")]
1107 #[test]
1108 fn test_table_pool() -> Result<()> {
1109 let pool = TablePool::new(&InstanceLimits {
1110 count: 7,
1111 table_elements: 100,
1112 memory_pages: 0,
1113 tables: 4,
1114 memories: 0,
1115 ..Default::default()
1116 })?;
1117
1118 let host_page_size = crate::page_size();
1119
1120 assert_eq!(pool.table_size, host_page_size);
1121 assert_eq!(pool.max_tables, 4);
1122 assert_eq!(pool.max_instances, 7);
1123 assert_eq!(pool.page_size, host_page_size);
1124 assert_eq!(pool.max_elements, 100);
1125
1126 let base = pool.mapping.as_ptr() as usize;
1127
1128 for i in 0..7 {
1129 let mut iter = pool.get(i);
1130
1131 for j in 0..4 {
1132 assert_eq!(
1133 iter.next().unwrap() as usize - base,
1134 ((i * 4) + j) * pool.table_size
1135 );
1136 }
1137
1138 assert_eq!(iter.next(), None);
1139 }
1140
1141 Ok(())
1142 }
1143
1144 #[cfg(all(unix, target_pointer_width = "64", feature = "async"))]
1145 #[test]
1146 fn test_stack_pool() -> Result<()> {
1147 let config = PoolingInstanceAllocatorConfig {
1148 limits: InstanceLimits {
1149 count: 10,
1150 ..Default::default()
1151 },
1152 stack_size: 1,
1153 async_stack_zeroing: true,
1154 ..PoolingInstanceAllocatorConfig::default()
1155 };
1156 let pool = StackPool::new(&config)?;
1157
1158 let native_page_size = crate::page_size();
1159 assert_eq!(pool.stack_size, 2 * native_page_size);
1160 assert_eq!(pool.max_instances, 10);
1161 assert_eq!(pool.page_size, native_page_size);
1162
1163 assert_eq!(pool.index_allocator.testing_freelist(), []);
1164
1165 let base = pool.mapping.as_ptr() as usize;
1166
1167 let mut stacks = Vec::new();
1168 for i in 0..10 {
1169 let stack = pool.allocate().expect("allocation should succeed");
1170 assert_eq!(
1171 ((stack.top().unwrap() as usize - base) / pool.stack_size) - 1,
1172 i
1173 );
1174 stacks.push(stack);
1175 }
1176
1177 assert_eq!(pool.index_allocator.testing_freelist(), []);
1178
1179 pool.allocate().unwrap_err();
1180
1181 for stack in stacks {
1182 pool.deallocate(&stack);
1183 }
1184
1185 assert_eq!(
1186 pool.index_allocator.testing_freelist(),
1187 [
1188 SlotId(0),
1189 SlotId(1),
1190 SlotId(2),
1191 SlotId(3),
1192 SlotId(4),
1193 SlotId(5),
1194 SlotId(6),
1195 SlotId(7),
1196 SlotId(8),
1197 SlotId(9)
1198 ],
1199 );
1200
1201 Ok(())
1202 }
1203
1204 #[test]
1205 fn test_pooling_allocator_with_zero_instance_count() {
1206 let config = PoolingInstanceAllocatorConfig {
1207 limits: InstanceLimits {
1208 count: 0,
1209 ..Default::default()
1210 },
1211 ..PoolingInstanceAllocatorConfig::default()
1212 };
1213 assert_eq!(
1214 PoolingInstanceAllocator::new(&config, &Tunables::default(),)
1215 .map_err(|e| e.to_string())
1216 .expect_err("expected a failure constructing instance allocator"),
1217 "the instance count limit cannot be zero"
1218 );
1219 }
1220
1221 #[test]
1222 fn test_pooling_allocator_with_memory_pages_exceeded() {
1223 let config = PoolingInstanceAllocatorConfig {
1224 limits: InstanceLimits {
1225 count: 1,
1226 memory_pages: 0x10001,
1227 ..Default::default()
1228 },
1229 ..PoolingInstanceAllocatorConfig::default()
1230 };
1231 assert_eq!(
1232 PoolingInstanceAllocator::new(
1233 &config,
1234 &Tunables {
1235 static_memory_bound: 1,
1236 ..Tunables::default()
1237 },
1238 )
1239 .map_err(|e| e.to_string())
1240 .expect_err("expected a failure constructing instance allocator"),
1241 "module memory page limit of 65537 exceeds the maximum of 65536"
1242 );
1243 }
1244
1245 #[test]
1246 fn test_pooling_allocator_with_reservation_size_exceeded() {
1247 let config = PoolingInstanceAllocatorConfig {
1248 limits: InstanceLimits {
1249 count: 1,
1250 memory_pages: 2,
1251 ..Default::default()
1252 },
1253 ..PoolingInstanceAllocatorConfig::default()
1254 };
1255 let pool = PoolingInstanceAllocator::new(
1256 &config,
1257 &Tunables {
1258 static_memory_bound: 1,
1259 static_memory_offset_guard_size: 0,
1260 ..Tunables::default()
1261 },
1262 )
1263 .unwrap();
1264 assert_eq!(pool.memories.memory_size, 2 * 65536);
1265 }
1266
1267 #[cfg(all(unix, target_pointer_width = "64", feature = "async"))]
1268 #[test]
1269 fn test_stack_zeroed() -> Result<()> {
1270 let config = PoolingInstanceAllocatorConfig {
1271 max_unused_warm_slots: 0,
1272 limits: InstanceLimits {
1273 count: 1,
1274 table_elements: 0,
1275 memory_pages: 0,
1276 tables: 0,
1277 memories: 0,
1278 ..Default::default()
1279 },
1280 stack_size: 128,
1281 async_stack_zeroing: true,
1282 ..PoolingInstanceAllocatorConfig::default()
1283 };
1284 let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default())?;
1285
1286 unsafe {
1287 for _ in 0..255 {
1288 let stack = allocator.allocate_fiber_stack()?;
1289
1290 let addr = stack.top().unwrap().sub(1);
1292
1293 assert_eq!(*addr, 0);
1294 *addr = 1;
1295
1296 allocator.deallocate_fiber_stack(&stack);
1297 }
1298 }
1299
1300 Ok(())
1301 }
1302
1303 #[cfg(all(unix, target_pointer_width = "64", feature = "async"))]
1304 #[test]
1305 fn test_stack_unzeroed() -> Result<()> {
1306 let config = PoolingInstanceAllocatorConfig {
1307 max_unused_warm_slots: 0,
1308 limits: InstanceLimits {
1309 count: 1,
1310 table_elements: 0,
1311 memory_pages: 0,
1312 tables: 0,
1313 memories: 0,
1314 ..Default::default()
1315 },
1316 stack_size: 128,
1317 async_stack_zeroing: false,
1318 ..PoolingInstanceAllocatorConfig::default()
1319 };
1320 let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default())?;
1321
1322 unsafe {
1323 for i in 0..255 {
1324 let stack = allocator.allocate_fiber_stack()?;
1325
1326 let addr = stack.top().unwrap().sub(1);
1328
1329 assert_eq!(*addr, i);
1330 *addr = i + 1;
1331
1332 allocator.deallocate_fiber_stack(&stack);
1333 }
1334 }
1335
1336 Ok(())
1337 }
1338}