1 use super::GcHeapAllocationIndex;
2 use super::index_allocator::{SimpleIndexAllocator, SlotId};
3 use crate::runtime::vm::{GcHeap, GcRuntime, PoolingInstanceAllocatorConfig, Result};
4 use crate::vm::{Memory, MemoryAllocationIndex};
5 use crate::{Engine, prelude::*};
6 use std::sync::Mutex;
7 
8 enum 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 
18 impl HeapSlot {
alloc(&mut self, memory_alloc_index: MemoryAllocationIndex) -> Option<Box<dyn GcHeap>>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 
dealloc(&mut self, heap: Box<dyn GcHeap>) -> MemoryAllocationIndex30     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.
42 pub struct GcHeapPool {
43     max_gc_heaps: usize,
44     index_allocator: SimpleIndexAllocator,
45     heaps: Mutex<Box<[HeapSlot]>>,
46 }
47 
48 impl std::fmt::Debug for GcHeapPool {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result49     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 
58 impl GcHeapPool {
59     /// Create a new `GcHeapPool` with the given configuration.
new(config: &PoolingInstanceAllocatorConfig) -> Result<Self>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?
is_empty(&self) -> bool76     pub fn is_empty(&self) -> bool {
77         self.index_allocator.is_empty()
78     }
79 
80     /// Allocate a single table for the given instance allocation request.
allocate( &self, engine: &Engine, gc_runtime: &dyn GcRuntime, memory_alloc_index: MemoryAllocationIndex, memory: Memory, ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)>81     pub fn allocate(
82         &self,
83         engine: &Engine,
84         gc_runtime: &dyn GcRuntime,
85         memory_alloc_index: MemoryAllocationIndex,
86         memory: Memory,
87     ) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> {
88         let allocation_index = self
89             .index_allocator
90             .alloc()
91             .map(|slot| GcHeapAllocationIndex(slot.0))
92             .ok_or_else(|| {
93                 format_err!(
94                     "maximum concurrent GC heap limit of {} reached",
95                     self.max_gc_heaps
96                 )
97             })?;
98         debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
99 
100         let mut heap = match {
101             let mut heaps = self.heaps.lock().unwrap();
102             heaps[allocation_index.index()].alloc(memory_alloc_index)
103         } {
104             // If we already have a heap at this slot, reuse it.
105             Some(heap) => heap,
106             // Otherwise, we haven't forced this slot's lazily allocated heap
107             // yet. So do that now.
108             None => gc_runtime.new_gc_heap(engine)?,
109         };
110 
111         debug_assert!(!heap.is_attached());
112         heap.attach(memory);
113 
114         Ok((allocation_index, heap))
115     }
116 
117     /// Deallocate a previously-allocated GC heap.
deallocate( &self, allocation_index: GcHeapAllocationIndex, mut heap: Box<dyn GcHeap>, ) -> (MemoryAllocationIndex, Memory)118     pub fn deallocate(
119         &self,
120         allocation_index: GcHeapAllocationIndex,
121         mut heap: Box<dyn GcHeap>,
122     ) -> (MemoryAllocationIndex, Memory) {
123         debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
124 
125         let memory = heap.detach();
126 
127         // NB: Replace the heap before freeing the index. If we did it in the
128         // opposite order, a concurrent allocation request could reallocate the
129         // index before we have replaced the heap.
130 
131         let memory_alloc_index = {
132             let mut heaps = self.heaps.lock().unwrap();
133             heaps[allocation_index.index()].dealloc(heap)
134         };
135 
136         self.index_allocator.free(SlotId(allocation_index.0), 0);
137 
138         (memory_alloc_index, memory)
139     }
140 }
141