1 //! The null collector.
2 //!
3 //! The null collector bump allocates objects until it runs out of space, at
4 //! which point it returns an out-of-memory error. It never collects garbage.
5 //! It does not require any GC barriers.
6 
7 use super::*;
8 use crate::{
9     Engine,
10     prelude::*,
11     vm::{
12         ExternRefHostDataId, ExternRefHostDataTable, GarbageCollection, GcHeap, GcHeapObject,
13         GcProgress, GcRootsIter, GcRuntime, SendSyncUnsafeCell, TypedGcRef, VMGcHeader, VMGcRef,
14         VMMemoryDefinition,
15     },
16 };
17 use core::ptr::NonNull;
18 use core::{alloc::Layout, any::Any, num::NonZeroU32};
19 use wasmtime_environ::{
20     GcArrayLayout, GcStructLayout, GcTypeLayouts, VMGcKind, VMSharedTypeIndex,
21     null::NullTypeLayouts,
22 };
23 
24 /// The null collector.
25 #[derive(Default)]
26 pub struct NullCollector {
27     layouts: NullTypeLayouts,
28 }
29 
30 unsafe impl GcRuntime for NullCollector {
layouts(&self) -> &dyn GcTypeLayouts31     fn layouts(&self) -> &dyn GcTypeLayouts {
32         &self.layouts
33     }
34 
new_gc_heap(&self, _: &Engine) -> Result<Box<dyn GcHeap>>35     fn new_gc_heap(&self, _: &Engine) -> Result<Box<dyn GcHeap>> {
36         let heap = NullHeap::new()?;
37         Ok(Box::new(heap) as _)
38     }
39 }
40 
41 /// A GC heap for the null collector.
42 #[repr(C)]
43 struct NullHeap {
44     /// Bump-allocation finger indexing within `1..self.heap.len()`.
45     ///
46     /// NB: this is an `UnsafeCell` because it is written to by compiled Wasm
47     /// code.
48     next: SendSyncUnsafeCell<NonZeroU32>,
49 
50     /// The number of active no-gc scopes at the current moment.
51     no_gc_count: usize,
52 
53     /// The actual storage for the GC heap.
54     memory: Option<crate::vm::Memory>,
55 }
56 
57 /// The common header for all arrays in the null collector.
58 #[repr(C)]
59 struct VMNullArrayHeader {
60     header: VMGcHeader,
61     length: u32,
62 }
63 
64 unsafe impl GcHeapObject for VMNullArrayHeader {
65     #[inline]
is(header: &VMGcHeader) -> bool66     fn is(header: &VMGcHeader) -> bool {
67         header.kind() == VMGcKind::ArrayRef
68     }
69 }
70 
71 impl VMNullArrayHeader {
typed_ref<'a>( gc_heap: &NullHeap, array: &'a VMArrayRef, ) -> &'a TypedGcRef<VMNullArrayHeader>72     fn typed_ref<'a>(
73         gc_heap: &NullHeap,
74         array: &'a VMArrayRef,
75     ) -> &'a TypedGcRef<VMNullArrayHeader> {
76         let gc_ref = array.as_gc_ref();
77         debug_assert!(gc_ref.is_typed::<VMNullArrayHeader>(gc_heap));
78         gc_ref.as_typed_unchecked()
79     }
80 }
81 
82 /// The representation of an `externref` in the null collector.
83 #[repr(C)]
84 struct VMNullExternRef {
85     header: VMGcHeader,
86     host_data: ExternRefHostDataId,
87 }
88 
89 unsafe impl GcHeapObject for VMNullExternRef {
90     #[inline]
is(header: &VMGcHeader) -> bool91     fn is(header: &VMGcHeader) -> bool {
92         header.kind() == VMGcKind::ExternRef
93     }
94 }
95 
96 impl VMNullExternRef {
97     /// Convert a generic `externref` to a typed reference to our concrete
98     /// `externref` type.
typed_ref<'a>( gc_heap: &NullHeap, externref: &'a VMExternRef, ) -> &'a TypedGcRef<VMNullExternRef>99     fn typed_ref<'a>(
100         gc_heap: &NullHeap,
101         externref: &'a VMExternRef,
102     ) -> &'a TypedGcRef<VMNullExternRef> {
103         let gc_ref = externref.as_gc_ref();
104         debug_assert!(gc_ref.is_typed::<VMNullExternRef>(gc_heap));
105         gc_ref.as_typed_unchecked()
106     }
107 }
108 
109 impl NullHeap {
110     /// Construct a new, default heap for the null collector.
new() -> Result<Self>111     fn new() -> Result<Self> {
112         Ok(Self {
113             no_gc_count: 0,
114             next: SendSyncUnsafeCell::new(NonZeroU32::new(u32::MAX).unwrap()),
115             memory: None,
116         })
117     }
118 
119     /// Attempt to bump-allocate an object with the given layout and
120     /// header.
121     ///
122     /// Returns `Ok(Ok(r))` on success, `Ok(Err(bytes_needed))` when we don't
123     /// have enough heap space but growing the GC heap could make it
124     /// allocatable, and `Err(_)` when we don't have enough space and growing
125     /// the GC heap won't help.
alloc(&mut self, mut header: VMGcHeader, layout: Layout) -> Result<Result<VMGcRef, u64>>126     fn alloc(&mut self, mut header: VMGcHeader, layout: Layout) -> Result<Result<VMGcRef, u64>> {
127         debug_assert!(layout.size() >= core::mem::size_of::<VMGcHeader>());
128         debug_assert!(layout.align() >= core::mem::align_of::<VMGcHeader>());
129 
130         // Make sure that the requested allocation's size fits in the GC
131         // header's unused bits.
132         let size = match u32::try_from(layout.size()).ok().and_then(|size| {
133             if VMGcKind::value_fits_in_unused_bits(size) {
134                 Some(size)
135             } else {
136                 None
137             }
138         }) {
139             Some(size) => size,
140             None => return Err(crate::Trap::AllocationTooLarge.into()),
141         };
142 
143         let next = *self.next.get_mut();
144 
145         // Increment the bump pointer to the layout's requested alignment.
146         let aligned = match u32::try_from(layout.align())
147             .ok()
148             .and_then(|align| next.get().checked_next_multiple_of(align))
149         {
150             Some(aligned) => aligned,
151             None => return Err(crate::Trap::AllocationTooLarge.into()),
152         };
153 
154         // Check whether the allocation fits in the heap space we have left.
155         let end_of_object = match aligned.checked_add(size) {
156             Some(end) => end,
157             None => return Err(crate::Trap::AllocationTooLarge.into()),
158         };
159         let len = self.memory.as_ref().unwrap().byte_size();
160         let len = u32::try_from(len).unwrap_or(u32::MAX);
161         if end_of_object > len {
162             return Ok(Err(u64::try_from(layout.size()).unwrap()));
163         }
164 
165         // Update the bump pointer, write the header, and return the GC ref.
166         *self.next.get_mut() = NonZeroU32::new(end_of_object).unwrap();
167 
168         let aligned = NonZeroU32::new(aligned).unwrap();
169         let gc_ref = VMGcRef::from_heap_index(aligned).unwrap();
170 
171         debug_assert_eq!(header.reserved_u26(), 0);
172         header.set_reserved_u26(size);
173         *self.header_mut(&gc_ref) = header;
174 
175         Ok(Ok(gc_ref))
176     }
177 }
178 
179 unsafe impl GcHeap for NullHeap {
is_attached(&self) -> bool180     fn is_attached(&self) -> bool {
181         self.memory.is_some()
182     }
183 
attach(&mut self, memory: crate::vm::Memory)184     fn attach(&mut self, memory: crate::vm::Memory) {
185         assert!(!self.is_attached());
186         self.memory = Some(memory);
187         self.next = SendSyncUnsafeCell::new(NonZeroU32::new(1).unwrap());
188     }
189 
detach(&mut self) -> crate::vm::Memory190     fn detach(&mut self) -> crate::vm::Memory {
191         assert!(self.is_attached());
192 
193         let NullHeap {
194             next,
195             no_gc_count,
196             memory,
197         } = self;
198 
199         *next.get_mut() = NonZeroU32::new(1).unwrap();
200         *no_gc_count = 0;
201 
202         self.next = SendSyncUnsafeCell::new(NonZeroU32::new(u32::MAX).unwrap());
203         memory.take().unwrap()
204     }
205 
as_any(&self) -> &dyn Any206     fn as_any(&self) -> &dyn Any {
207         self as _
208     }
209 
as_any_mut(&mut self) -> &mut dyn Any210     fn as_any_mut(&mut self) -> &mut dyn Any {
211         self as _
212     }
213 
enter_no_gc_scope(&mut self)214     fn enter_no_gc_scope(&mut self) {
215         self.no_gc_count += 1;
216     }
217 
exit_no_gc_scope(&mut self)218     fn exit_no_gc_scope(&mut self) {
219         self.no_gc_count -= 1;
220     }
221 
take_memory(&mut self) -> crate::vm::Memory222     fn take_memory(&mut self) -> crate::vm::Memory {
223         debug_assert!(self.is_attached());
224         self.memory.take().unwrap()
225     }
226 
replace_memory(&mut self, memory: crate::vm::Memory, _delta_bytes_grown: u64)227     unsafe fn replace_memory(&mut self, memory: crate::vm::Memory, _delta_bytes_grown: u64) {
228         debug_assert!(self.memory.is_none());
229         self.memory = Some(memory);
230     }
231 
vmmemory(&self) -> VMMemoryDefinition232     fn vmmemory(&self) -> VMMemoryDefinition {
233         debug_assert!(self.is_attached());
234         self.memory.as_ref().unwrap().vmmemory()
235     }
236 
clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef237     fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef {
238         gc_ref.unchecked_copy()
239     }
240 
write_gc_ref( &mut self, _host_data_table: &mut ExternRefHostDataTable, destination: &mut Option<VMGcRef>, source: Option<&VMGcRef>, )241     fn write_gc_ref(
242         &mut self,
243         _host_data_table: &mut ExternRefHostDataTable,
244         destination: &mut Option<VMGcRef>,
245         source: Option<&VMGcRef>,
246     ) {
247         *destination = source.map(|s| s.unchecked_copy());
248     }
249 
expose_gc_ref_to_wasm(&mut self, _gc_ref: VMGcRef)250     fn expose_gc_ref_to_wasm(&mut self, _gc_ref: VMGcRef) {
251         // Don't need to do anything special here.
252     }
253 
alloc_externref( &mut self, host_data: ExternRefHostDataId, ) -> Result<Result<VMExternRef, u64>>254     fn alloc_externref(
255         &mut self,
256         host_data: ExternRefHostDataId,
257     ) -> Result<Result<VMExternRef, u64>> {
258         let gc_ref = match self.alloc(VMGcHeader::externref(), Layout::new::<VMNullExternRef>())? {
259             Ok(r) => r,
260             Err(bytes_needed) => return Ok(Err(bytes_needed)),
261         };
262         self.index_mut::<VMNullExternRef>(gc_ref.as_typed_unchecked())
263             .host_data = host_data;
264         Ok(Ok(gc_ref.into_externref_unchecked()))
265     }
266 
externref_host_data(&self, externref: &VMExternRef) -> ExternRefHostDataId267     fn externref_host_data(&self, externref: &VMExternRef) -> ExternRefHostDataId {
268         let typed_ref = VMNullExternRef::typed_ref(self, externref);
269         self.index(typed_ref).host_data
270     }
271 
object_size(&self, gc_ref: &VMGcRef) -> usize272     fn object_size(&self, gc_ref: &VMGcRef) -> usize {
273         let size = self.header(gc_ref).reserved_u26();
274         usize::try_from(size).unwrap()
275     }
276 
header(&self, gc_ref: &VMGcRef) -> &VMGcHeader277     fn header(&self, gc_ref: &VMGcRef) -> &VMGcHeader {
278         self.index(gc_ref.as_typed_unchecked())
279     }
280 
header_mut(&mut self, gc_ref: &VMGcRef) -> &mut VMGcHeader281     fn header_mut(&mut self, gc_ref: &VMGcRef) -> &mut VMGcHeader {
282         self.index_mut(gc_ref.as_typed_unchecked())
283     }
284 
alloc_raw(&mut self, header: VMGcHeader, layout: Layout) -> Result<Result<VMGcRef, u64>>285     fn alloc_raw(&mut self, header: VMGcHeader, layout: Layout) -> Result<Result<VMGcRef, u64>> {
286         self.alloc(header, layout)
287     }
288 
alloc_uninit_struct_or_exn( &mut self, ty: VMSharedTypeIndex, layout: &GcStructLayout, ) -> Result<Result<VMGcRef, u64>>289     fn alloc_uninit_struct_or_exn(
290         &mut self,
291         ty: VMSharedTypeIndex,
292         layout: &GcStructLayout,
293     ) -> Result<Result<VMGcRef, u64>> {
294         let kind = if layout.is_exception {
295             VMGcKind::ExnRef
296         } else {
297             VMGcKind::StructRef
298         };
299         self.alloc(VMGcHeader::from_kind_and_index(kind, ty), layout.layout())
300     }
301 
dealloc_uninit_struct_or_exn(&mut self, _struct_ref: VMGcRef)302     fn dealloc_uninit_struct_or_exn(&mut self, _struct_ref: VMGcRef) {}
303 
alloc_uninit_array( &mut self, ty: VMSharedTypeIndex, length: u32, layout: &GcArrayLayout, ) -> Result<Result<VMArrayRef, u64>>304     fn alloc_uninit_array(
305         &mut self,
306         ty: VMSharedTypeIndex,
307         length: u32,
308         layout: &GcArrayLayout,
309     ) -> Result<Result<VMArrayRef, u64>> {
310         self.alloc(
311             VMGcHeader::from_kind_and_index(VMGcKind::ArrayRef, ty),
312             layout.layout(length),
313         )
314         .map(|r| {
315             r.map(|r| {
316                 self.index_mut::<VMNullArrayHeader>(r.as_typed_unchecked())
317                     .length = length;
318                 r.into_arrayref_unchecked()
319             })
320         })
321     }
322 
dealloc_uninit_array(&mut self, _array_ref: VMArrayRef)323     fn dealloc_uninit_array(&mut self, _array_ref: VMArrayRef) {}
324 
array_len(&self, arrayref: &VMArrayRef) -> u32325     fn array_len(&self, arrayref: &VMArrayRef) -> u32 {
326         let arrayref = VMNullArrayHeader::typed_ref(self, arrayref);
327         self.index(arrayref).length
328     }
329 
gc<'a>( &'a mut self, _roots: GcRootsIter<'a>, _host_data_table: &'a mut ExternRefHostDataTable, ) -> Box<dyn GarbageCollection<'a> + 'a>330     fn gc<'a>(
331         &'a mut self,
332         _roots: GcRootsIter<'a>,
333         _host_data_table: &'a mut ExternRefHostDataTable,
334     ) -> Box<dyn GarbageCollection<'a> + 'a> {
335         assert_eq!(self.no_gc_count, 0, "Cannot GC inside a no-GC scope!");
336         Box::new(NullCollection {})
337     }
338 
vmctx_gc_heap_data(&self) -> NonNull<u8>339     unsafe fn vmctx_gc_heap_data(&self) -> NonNull<u8> {
340         let ptr_to_next: *mut NonZeroU32 = unsafe { self.next.get() };
341         NonNull::new(ptr_to_next).unwrap().cast()
342     }
343 }
344 
345 struct NullCollection {}
346 
347 impl<'a> GarbageCollection<'a> for NullCollection {
collect_increment(&mut self) -> GcProgress348     fn collect_increment(&mut self) -> GcProgress {
349         GcProgress::Complete
350     }
351 }
352 
353 #[cfg(test)]
354 mod tests {
355     use super::*;
356 
357     #[test]
vm_gc_null_header_size_align()358     fn vm_gc_null_header_size_align() {
359         assert_eq!(
360             (wasmtime_environ::null::HEADER_SIZE as usize),
361             core::mem::size_of::<VMGcHeader>()
362         );
363         assert_eq!(
364             (wasmtime_environ::null::HEADER_ALIGN as usize),
365             core::mem::align_of::<VMGcHeader>()
366         );
367     }
368 
369     #[test]
vm_null_array_header_length_offset()370     fn vm_null_array_header_length_offset() {
371         assert_eq!(
372             wasmtime_environ::null::ARRAY_LENGTH_OFFSET,
373             u32::try_from(core::mem::offset_of!(VMNullArrayHeader, length)).unwrap(),
374         );
375     }
376 }
377