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