Skip to main content

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;
7use wasmtime_environ::Tunables;
8
9enum HeapSlot {
10    /// The is available for use, and we may or may not have lazily allocated
11    /// its associated GC heap yet.
12    Free(Option<Box<dyn GcHeap>>),
13
14    /// The slot's heap is currently in use, and it is backed by this memory
15    /// allocation index.
16    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
42/// A pool of reusable GC heaps.
43pub 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    /// Create a new `GcHeapPool` with the given configuration.
61    pub fn new(config: &PoolingInstanceAllocatorConfig, tunables: &Tunables) -> Result<Self> {
62        // When using the pooling allocator, GC heaps and linear memories share
63        // the same underlying memory pool, so they must be configured the same.
64        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        // Each individual GC heap in the pool is lazily allocated. See the
85        // `allocate` method.
86        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    /// Are there zero slots in use right now?
96    pub fn is_empty(&self) -> bool {
97        self.index_allocator.is_empty()
98    }
99
100    /// Allocate a single table for the given instance allocation request.
101    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            // If we already have a heap at this slot, reuse it.
125            Some(heap) => heap,
126            // Otherwise, we haven't forced this slot's lazily allocated heap
127            // yet. So do that now.
128            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    /// Deallocate a previously-allocated GC heap.
138    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        // NB: Replace the heap before freeing the index. If we did it in the
148        // opposite order, a concurrent allocation request could reallocate the
149        // index before we have replaced the heap.
150
151        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}