wasmtime/runtime/vm/instance/allocator/pooling/
gc_heap_pool.rs

1use 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;
7
8enum HeapSlot {
9    /// The is available for use, and we may or may not have lazily allocated
10    /// its associated GC heap yet.
11    Free(Option<Box<dyn GcHeap>>),
12
13    /// The slot's heap is currently in use, and it is backed by this memory
14    /// allocation index.
15    InUse(MemoryAllocationIndex),
16}
17
18impl HeapSlot {
19    fn alloc(&mut self, memory_alloc_index: MemoryAllocationIndex) -> Option<Box<dyn GcHeap>> {
20        match self {
21            HeapSlot::Free(gc_heap) => {
22                let gc_heap = gc_heap.take();
23                *self = HeapSlot::InUse(memory_alloc_index);
24                gc_heap
25            }
26            HeapSlot::InUse(_) => panic!("already in use"),
27        }
28    }
29
30    fn dealloc(&mut self, heap: Box<dyn GcHeap>) -> MemoryAllocationIndex {
31        match *self {
32            HeapSlot::Free(_) => panic!("already free"),
33            HeapSlot::InUse(memory_alloc_index) => {
34                *self = HeapSlot::Free(Some(heap));
35                memory_alloc_index
36            }
37        }
38    }
39}
40
41/// A pool of reusable GC heaps.
42pub struct GcHeapPool {
43    max_gc_heaps: usize,
44    index_allocator: SimpleIndexAllocator,
45    heaps: Mutex<Box<[HeapSlot]>>,
46}
47
48impl std::fmt::Debug for GcHeapPool {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.debug_struct("GcHeapPool")
51            .field("max_gc_heaps", &self.max_gc_heaps)
52            .field("index_allocator", &self.index_allocator)
53            .field("heaps", &"..")
54            .finish()
55    }
56}
57
58impl GcHeapPool {
59    /// Create a new `GcHeapPool` with the given configuration.
60    pub fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
61        let index_allocator = SimpleIndexAllocator::new(config.limits.total_gc_heaps);
62        let max_gc_heaps = usize::try_from(config.limits.total_gc_heaps).unwrap();
63
64        // Each individual GC heap in the pool is lazily allocated. See the
65        // `allocate` method.
66        let heaps = Mutex::new((0..max_gc_heaps).map(|_| HeapSlot::Free(None)).collect());
67
68        Ok(Self {
69            max_gc_heaps,
70            index_allocator,
71            heaps,
72        })
73    }
74
75    /// Are there zero slots in use right now?
76    #[allow(unused)] // some cfgs don't use this
77    pub fn is_empty(&self) -> bool {
78        self.index_allocator.is_empty()
79    }
80
81    /// Allocate a single table for the given instance allocation request.
82    pub fn allocate(
83        &self,
84        engine: &Engine,
85        gc_runtime: &dyn GcRuntime,
86        memory_alloc_index: MemoryAllocationIndex,
87        memory: Memory,
88    ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> {
89        let allocation_index = self
90            .index_allocator
91            .alloc()
92            .map(|slot| GcHeapAllocationIndex(slot.0))
93            .ok_or_else(|| {
94                anyhow!(
95                    "maximum concurrent GC heap limit of {} reached",
96                    self.max_gc_heaps
97                )
98            })?;
99        debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
100
101        let mut heap = match {
102            let mut heaps = self.heaps.lock().unwrap();
103            heaps[allocation_index.index()].alloc(memory_alloc_index)
104        } {
105            // If we already have a heap at this slot, reuse it.
106            Some(heap) => heap,
107            // Otherwise, we haven't forced this slot's lazily allocated heap
108            // yet. So do that now.
109            None => gc_runtime.new_gc_heap(engine)?,
110        };
111
112        debug_assert!(!heap.is_attached());
113        heap.attach(memory);
114
115        Ok((allocation_index, heap))
116    }
117
118    /// Deallocate a previously-allocated GC heap.
119    pub fn deallocate(
120        &self,
121        allocation_index: GcHeapAllocationIndex,
122        mut heap: Box<dyn GcHeap>,
123    ) -> (MemoryAllocationIndex, Memory) {
124        debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
125
126        let memory = heap.detach();
127
128        // NB: Replace the heap before freeing the index. If we did it in the
129        // opposite order, a concurrent allocation request could reallocate the
130        // index before we have replaced the heap.
131
132        let memory_alloc_index = {
133            let mut heaps = self.heaps.lock().unwrap();
134            heaps[allocation_index.index()].dealloc(heap)
135        };
136
137        self.index_allocator.free(SlotId(allocation_index.0));
138
139        (memory_alloc_index, memory)
140    }
141}