wasmtime/runtime/vm/instance/allocator/pooling/
gc_heap_pool.rs1use super::GcHeapAllocationIndex;
2use super::index_allocator::{SimpleIndexAllocator, SlotId};
3use crate::runtime::vm::{GcHeap, GcRuntime, PoolingInstanceAllocatorConfig, Result};
4use crate::vm::{Memory, MemoryAllocationIndex};
5use crate::{Engine, prelude::*};
6use std::sync::Mutex;
7use wasmtime_environ::Tunables;
8
9enum HeapSlot {
10 Free(Option<Box<dyn GcHeap>>),
13
14 InUse(MemoryAllocationIndex),
17}
18
19impl HeapSlot {
20 fn alloc(&mut self, memory_alloc_index: MemoryAllocationIndex) -> Option<Box<dyn GcHeap>> {
21 match self {
22 HeapSlot::Free(gc_heap) => {
23 let gc_heap = gc_heap.take();
24 *self = HeapSlot::InUse(memory_alloc_index);
25 gc_heap
26 }
27 HeapSlot::InUse(_) => panic!("already in use"),
28 }
29 }
30
31 fn dealloc(&mut self, heap: Box<dyn GcHeap>) -> MemoryAllocationIndex {
32 match *self {
33 HeapSlot::Free(_) => panic!("already free"),
34 HeapSlot::InUse(memory_alloc_index) => {
35 *self = HeapSlot::Free(Some(heap));
36 memory_alloc_index
37 }
38 }
39 }
40}
41
42pub struct GcHeapPool {
44 max_gc_heaps: usize,
45 index_allocator: SimpleIndexAllocator,
46 heaps: Mutex<Box<[HeapSlot]>>,
47}
48
49impl std::fmt::Debug for GcHeapPool {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("GcHeapPool")
52 .field("max_gc_heaps", &self.max_gc_heaps)
53 .field("index_allocator", &self.index_allocator)
54 .field("heaps", &"..")
55 .finish()
56 }
57}
58
59impl GcHeapPool {
60 pub fn new(config: &PoolingInstanceAllocatorConfig, tunables: &Tunables) -> Result<Self> {
62 debug_assert_eq!(
65 tunables.memory_reservation, tunables.gc_heap_reservation,
66 "pooling allocator requires memory_reservation == gc_heap_reservation"
67 );
68 debug_assert_eq!(
69 tunables.memory_guard_size, tunables.gc_heap_guard_size,
70 "pooling allocator requires memory_guard_size == gc_heap_guard_size"
71 );
72 debug_assert_eq!(
73 tunables.memory_reservation_for_growth, tunables.gc_heap_reservation_for_growth,
74 "pooling allocator requires memory_reservation_for_growth == gc_heap_reservation_for_growth"
75 );
76 debug_assert_eq!(
77 tunables.memory_may_move, tunables.gc_heap_may_move,
78 "pooling allocator requires memory_may_move == gc_heap_may_move"
79 );
80
81 let index_allocator = SimpleIndexAllocator::new(config.limits.total_gc_heaps);
82 let max_gc_heaps = usize::try_from(config.limits.total_gc_heaps).unwrap();
83
84 let heaps = Mutex::new((0..max_gc_heaps).map(|_| HeapSlot::Free(None)).collect());
87
88 Ok(Self {
89 max_gc_heaps,
90 index_allocator,
91 heaps,
92 })
93 }
94
95 pub fn is_empty(&self) -> bool {
97 self.index_allocator.is_empty()
98 }
99
100 pub fn allocate(
102 &self,
103 engine: &Engine,
104 gc_runtime: &dyn GcRuntime,
105 memory_alloc_index: MemoryAllocationIndex,
106 memory: Memory,
107 ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> {
108 let allocation_index = self
109 .index_allocator
110 .alloc()
111 .map(|slot| GcHeapAllocationIndex(slot.0))
112 .ok_or_else(|| {
113 format_err!(
114 "maximum concurrent GC heap limit of {} reached",
115 self.max_gc_heaps
116 )
117 })?;
118 debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
119
120 let mut heap = match {
121 let mut heaps = self.heaps.lock().unwrap();
122 heaps[allocation_index.index()].alloc(memory_alloc_index)
123 } {
124 Some(heap) => heap,
126 None => gc_runtime.new_gc_heap(engine)?,
129 };
130
131 debug_assert!(!heap.is_attached());
132 heap.attach(memory);
133
134 Ok((allocation_index, heap))
135 }
136
137 pub fn deallocate(
139 &self,
140 allocation_index: GcHeapAllocationIndex,
141 mut heap: Box<dyn GcHeap>,
142 ) -> (MemoryAllocationIndex, Memory) {
143 debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
144
145 let memory = heap.detach();
146
147 let memory_alloc_index = {
152 let mut heaps = self.heaps.lock().unwrap();
153 heaps[allocation_index.index()].dealloc(heap)
154 };
155
156 self.index_allocator.free(SlotId(allocation_index.0), 0);
157
158 (memory_alloc_index, memory)
159 }
160}