1 #[cfg(feature = "gc")] 2 mod enabled; 3 #[cfg(feature = "gc")] 4 pub use enabled::*; 5 6 #[cfg(not(feature = "gc"))] 7 mod disabled; 8 #[cfg(not(feature = "gc"))] 9 pub use disabled::*; 10 11 mod func_ref; 12 mod gc_ref; 13 mod gc_runtime; 14 mod host_data; 15 mod i31; 16 17 pub use func_ref::*; 18 pub use gc_ref::*; 19 pub use gc_runtime::*; 20 pub use host_data::*; 21 pub use i31::*; 22 23 use crate::prelude::*; 24 use crate::runtime::vm::{GcHeapAllocationIndex, VMMemoryDefinition}; 25 use crate::store::Asyncness; 26 use core::any::Any; 27 use core::mem::MaybeUninit; 28 use core::{alloc::Layout, num::NonZeroU32}; 29 use wasmtime_environ::{GcArrayLayout, GcStructLayout, VMGcKind, VMSharedTypeIndex}; 30 31 /// GC-related data that is one-to-one with a `wasmtime::Store`. 32 /// 33 /// Contains everything we need to do collections, invoke barriers, etc... 34 /// 35 /// In general, exposes a very similar interface to `GcHeap`, but fills in some 36 /// of the context arguments for callers (such as the `ExternRefHostDataTable`) 37 /// since they are all stored together inside `GcStore`. 38 pub struct GcStore { 39 /// This GC heap's allocation index (primarily used for integrating with the 40 /// pooling allocator). 41 pub allocation_index: GcHeapAllocationIndex, 42 43 /// The actual GC heap. 44 pub gc_heap: Box<dyn GcHeap>, 45 46 /// The `externref` host data table for this GC heap. 47 pub host_data_table: ExternRefHostDataTable, 48 49 /// The function-references table for this GC heap. 50 pub func_ref_table: FuncRefTable, 51 52 /// An allocation counter that triggers GC when it reaches zero. 53 /// 54 /// Initialized from the `WASMTIME_GC_ZEAL_ALLOC_COUNTER` environment 55 /// variable. Decremented on every allocation and when it hits zero, a GC is 56 /// forced and the counter is reset. 57 #[cfg(all(gc_zeal, feature = "std"))] 58 gc_zeal_alloc_counter: Option<NonZeroU32>, 59 60 /// The initial value to reset the counter to after it triggers. 61 #[cfg(all(gc_zeal, feature = "std"))] 62 gc_zeal_alloc_counter_init: Option<NonZeroU32>, 63 } 64 65 impl GcStore { 66 /// Create a new `GcStore`. new(allocation_index: GcHeapAllocationIndex, gc_heap: Box<dyn GcHeap>) -> Self67 pub fn new(allocation_index: GcHeapAllocationIndex, gc_heap: Box<dyn GcHeap>) -> Self { 68 let host_data_table = ExternRefHostDataTable::default(); 69 let func_ref_table = FuncRefTable::default(); 70 71 #[cfg(all(gc_zeal, feature = "std"))] 72 let gc_zeal_alloc_counter_init = 73 std::env::var("WASMTIME_GC_ZEAL_ALLOC_COUNTER") 74 .ok() 75 .map(|v| { 76 v.parse::<NonZeroU32>().unwrap_or_else(|_| { 77 panic!( 78 "`WASMTIME_GC_ZEAL_ALLOC_COUNTER` must be a non-zero \ 79 `u32` value, got: {v}" 80 ) 81 }) 82 }); 83 84 Self { 85 allocation_index, 86 gc_heap, 87 host_data_table, 88 func_ref_table, 89 #[cfg(all(gc_zeal, feature = "std"))] 90 gc_zeal_alloc_counter: gc_zeal_alloc_counter_init, 91 #[cfg(all(gc_zeal, feature = "std"))] 92 gc_zeal_alloc_counter_init, 93 } 94 } 95 96 /// Get the `VMMemoryDefinition` for this GC heap. vmmemory_definition(&self) -> VMMemoryDefinition97 pub fn vmmemory_definition(&self) -> VMMemoryDefinition { 98 self.gc_heap.vmmemory() 99 } 100 101 /// Asynchronously perform garbage collection within this heap. gc(&mut self, asyncness: Asyncness, roots: GcRootsIter<'_>)102 pub async fn gc(&mut self, asyncness: Asyncness, roots: GcRootsIter<'_>) { 103 let collection = self.gc_heap.gc(roots, &mut self.host_data_table); 104 collect_async(collection, asyncness).await; 105 } 106 107 /// Get the kind of the given GC reference. kind(&self, gc_ref: &VMGcRef) -> VMGcKind108 pub fn kind(&self, gc_ref: &VMGcRef) -> VMGcKind { 109 debug_assert!(!gc_ref.is_i31()); 110 self.header(gc_ref).kind() 111 } 112 113 /// Get the header of the given GC reference. header(&self, gc_ref: &VMGcRef) -> &VMGcHeader114 pub fn header(&self, gc_ref: &VMGcRef) -> &VMGcHeader { 115 debug_assert!(!gc_ref.is_i31()); 116 self.gc_heap.header(gc_ref) 117 } 118 119 /// Clone a GC reference, calling GC write barriers as necessary. clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef120 pub fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef { 121 if gc_ref.is_i31() { 122 gc_ref.copy_i31() 123 } else { 124 self.gc_heap.clone_gc_ref(gc_ref) 125 } 126 } 127 128 /// Write the `source` GC reference into the uninitialized `destination` 129 /// slot, performing write barriers as necessary. init_gc_ref( &mut self, destination: &mut MaybeUninit<Option<VMGcRef>>, source: Option<&VMGcRef>, )130 pub fn init_gc_ref( 131 &mut self, 132 destination: &mut MaybeUninit<Option<VMGcRef>>, 133 source: Option<&VMGcRef>, 134 ) { 135 // Initialize the destination to `None`, at which point the regular GC 136 // write barrier is safe to reuse. 137 let destination = destination.write(None); 138 self.write_gc_ref(destination, source); 139 } 140 141 /// Dynamically tests whether a `init_gc_ref` is needed to write `gc_ref` 142 /// into an uninitialized destination. needs_init_barrier(gc_ref: Option<&VMGcRef>) -> bool143 pub(crate) fn needs_init_barrier(gc_ref: Option<&VMGcRef>) -> bool { 144 assert!(cfg!(feature = "gc") || gc_ref.is_none()); 145 gc_ref.is_some_and(|r| !r.is_i31()) 146 } 147 148 /// Dynamically tests whether a `write_gc_ref` is needed to write `gc_ref` 149 /// into `dest`. needs_write_barrier( dest: &mut Option<VMGcRef>, gc_ref: Option<&VMGcRef>, ) -> bool150 pub(crate) fn needs_write_barrier( 151 dest: &mut Option<VMGcRef>, 152 gc_ref: Option<&VMGcRef>, 153 ) -> bool { 154 assert!(cfg!(feature = "gc") || gc_ref.is_none()); 155 assert!(cfg!(feature = "gc") || dest.is_none()); 156 dest.as_ref().is_some_and(|r| !r.is_i31()) || gc_ref.is_some_and(|r| !r.is_i31()) 157 } 158 159 /// Same as [`Self::write_gc_ref`] but doesn't require a `store` when 160 /// possible. 161 /// 162 /// # Panics 163 /// 164 /// Panics if `store` is `None` and one of `dest` or `gc_ref` requires a 165 /// write barrier. write_gc_ref_optional_store( store: Option<&mut Self>, dest: &mut Option<VMGcRef>, gc_ref: Option<&VMGcRef>, )166 pub(crate) fn write_gc_ref_optional_store( 167 store: Option<&mut Self>, 168 dest: &mut Option<VMGcRef>, 169 gc_ref: Option<&VMGcRef>, 170 ) { 171 if Self::needs_write_barrier(dest, gc_ref) { 172 store.unwrap().write_gc_ref(dest, gc_ref) 173 } else { 174 *dest = gc_ref.map(|r| r.copy_i31()); 175 } 176 } 177 178 /// Write the `source` GC reference into the `destination` slot, performing 179 /// write barriers as necessary. write_gc_ref(&mut self, destination: &mut Option<VMGcRef>, source: Option<&VMGcRef>)180 pub fn write_gc_ref(&mut self, destination: &mut Option<VMGcRef>, source: Option<&VMGcRef>) { 181 // If neither the source nor destination actually point to a GC object 182 // (that is, they are both either null or `i31ref`s) then we can skip 183 // the GC barrier. 184 if Self::needs_write_barrier(destination, source) { 185 self.gc_heap 186 .write_gc_ref(&mut self.host_data_table, destination, source); 187 } else { 188 *destination = source.map(|s| s.copy_i31()); 189 } 190 } 191 192 /// Drop the given GC reference, performing drop barriers as necessary. drop_gc_ref(&mut self, gc_ref: VMGcRef)193 pub fn drop_gc_ref(&mut self, gc_ref: VMGcRef) { 194 if !gc_ref.is_i31() { 195 self.gc_heap.drop_gc_ref(&mut self.host_data_table, gc_ref); 196 } 197 } 198 199 /// Hook to call whenever a GC reference is about to be exposed to Wasm. 200 /// 201 /// Returns the raw representation of this GC ref, ready to be passed to 202 /// Wasm. 203 #[must_use] expose_gc_ref_to_wasm(&mut self, gc_ref: VMGcRef) -> NonZeroU32204 pub fn expose_gc_ref_to_wasm(&mut self, gc_ref: VMGcRef) -> NonZeroU32 { 205 let raw = gc_ref.as_raw_non_zero_u32(); 206 if !gc_ref.is_i31() { 207 log::trace!("exposing GC ref to Wasm: {gc_ref:p}"); 208 self.gc_heap.expose_gc_ref_to_wasm(gc_ref); 209 } 210 raw 211 } 212 213 /// Allocate a new `externref`. 214 /// 215 /// Returns: 216 /// 217 /// * `Ok(Ok(_))`: Successfully allocated the `externref`. 218 /// 219 /// * `Ok(Err((value, n)))`: Failed to allocate the `externref`, but doing a GC 220 /// and then trying again may succeed. Returns the given `value` as the 221 /// error payload, along with the size of the failed allocation. 222 /// 223 /// * `Err(_)`: Unrecoverable allocation failure. alloc_externref( &mut self, value: Box<dyn Any + Send + Sync>, ) -> Result<Result<VMExternRef, (Box<dyn Any + Send + Sync>, u64)>>224 pub fn alloc_externref( 225 &mut self, 226 value: Box<dyn Any + Send + Sync>, 227 ) -> Result<Result<VMExternRef, (Box<dyn Any + Send + Sync>, u64)>> { 228 let host_data_id = self.host_data_table.alloc(value); 229 match self.gc_heap.alloc_externref(host_data_id)? { 230 Ok(x) => Ok(Ok(x)), 231 Err(n) => Ok(Err((self.host_data_table.dealloc(host_data_id), n))), 232 } 233 } 234 235 /// Get a shared borrow of the given `externref`'s host data. 236 /// 237 /// Passing invalid `VMExternRef`s (eg garbage values or `externref`s 238 /// associated with a different heap is memory safe but will lead to general 239 /// incorrectness such as panics and wrong results. externref_host_data(&self, externref: &VMExternRef) -> &(dyn Any + Send + Sync)240 pub fn externref_host_data(&self, externref: &VMExternRef) -> &(dyn Any + Send + Sync) { 241 let host_data_id = self.gc_heap.externref_host_data(externref); 242 self.host_data_table.get(host_data_id) 243 } 244 245 /// Get a mutable borrow of the given `externref`'s host data. 246 /// 247 /// Passing invalid `VMExternRef`s (eg garbage values or `externref`s 248 /// associated with a different heap is memory safe but will lead to general 249 /// incorrectness such as panics and wrong results. externref_host_data_mut( &mut self, externref: &VMExternRef, ) -> &mut (dyn Any + Send + Sync)250 pub fn externref_host_data_mut( 251 &mut self, 252 externref: &VMExternRef, 253 ) -> &mut (dyn Any + Send + Sync) { 254 let host_data_id = self.gc_heap.externref_host_data(externref); 255 self.host_data_table.get_mut(host_data_id) 256 } 257 258 /// Allocate a raw object with the given header and layout. alloc_raw( &mut self, header: VMGcHeader, layout: Layout, ) -> Result<Result<VMGcRef, u64>>259 pub fn alloc_raw( 260 &mut self, 261 header: VMGcHeader, 262 layout: Layout, 263 ) -> Result<Result<VMGcRef, u64>> { 264 // When gc_zeal is enabled with an allocation counter, decrement it and 265 // force a GC cycle when it reaches zero by returning a fake OOM. 266 #[cfg(all(gc_zeal, feature = "std"))] 267 if let Some(counter) = self.gc_zeal_alloc_counter.take() { 268 match NonZeroU32::new(counter.get() - 1) { 269 Some(c) => self.gc_zeal_alloc_counter = Some(c), 270 None => { 271 log::trace!("gc_zeal: allocation counter reached zero, forcing GC"); 272 self.gc_zeal_alloc_counter = self.gc_zeal_alloc_counter_init; 273 return Ok(Err(0)); 274 } 275 } 276 } 277 278 self.gc_heap.alloc_raw(header, layout) 279 } 280 281 /// Allocate an uninitialized struct with the given type index and layout. 282 /// 283 /// This does NOT check that the index is currently allocated in the types 284 /// registry or that the layout matches the index's type. Failure to uphold 285 /// those invariants is memory safe, but will lead to general incorrectness 286 /// such as panics and wrong results. alloc_uninit_struct( &mut self, ty: VMSharedTypeIndex, layout: &GcStructLayout, ) -> Result<Result<VMStructRef, u64>>287 pub fn alloc_uninit_struct( 288 &mut self, 289 ty: VMSharedTypeIndex, 290 layout: &GcStructLayout, 291 ) -> Result<Result<VMStructRef, u64>> { 292 self.gc_heap 293 .alloc_uninit_struct_or_exn(ty, layout) 294 .map(|r| r.map(|r| r.into_structref_unchecked())) 295 } 296 297 /// Deallocate an uninitialized struct. dealloc_uninit_struct(&mut self, structref: VMStructRef)298 pub fn dealloc_uninit_struct(&mut self, structref: VMStructRef) { 299 self.gc_heap.dealloc_uninit_struct_or_exn(structref.into()) 300 } 301 302 /// Get the data for the given object reference. 303 /// 304 /// Panics when the structref and its size is out of the GC heap bounds. gc_object_data(&mut self, gc_ref: &VMGcRef) -> &mut VMGcObjectData305 pub fn gc_object_data(&mut self, gc_ref: &VMGcRef) -> &mut VMGcObjectData { 306 self.gc_heap.gc_object_data_mut(gc_ref) 307 } 308 309 /// Get the object datas for the given pair of object references. 310 /// 311 /// Panics if `a` and `b` are the same reference or either is out of bounds. gc_object_data_pair( &mut self, a: &VMGcRef, b: &VMGcRef, ) -> (&mut VMGcObjectData, &mut VMGcObjectData)312 pub fn gc_object_data_pair( 313 &mut self, 314 a: &VMGcRef, 315 b: &VMGcRef, 316 ) -> (&mut VMGcObjectData, &mut VMGcObjectData) { 317 assert_ne!(a, b); 318 self.gc_heap.gc_object_data_pair(a, b) 319 } 320 321 /// Allocate an uninitialized array with the given type index. 322 /// 323 /// This does NOT check that the index is currently allocated in the types 324 /// registry or that the layout matches the index's type. Failure to uphold 325 /// those invariants is memory safe, but will lead to general incorrectness 326 /// such as panics and wrong results. alloc_uninit_array( &mut self, ty: VMSharedTypeIndex, len: u32, layout: &GcArrayLayout, ) -> Result<Result<VMArrayRef, u64>>327 pub fn alloc_uninit_array( 328 &mut self, 329 ty: VMSharedTypeIndex, 330 len: u32, 331 layout: &GcArrayLayout, 332 ) -> Result<Result<VMArrayRef, u64>> { 333 self.gc_heap.alloc_uninit_array(ty, len, layout) 334 } 335 336 /// Deallocate an uninitialized array. dealloc_uninit_array(&mut self, arrayref: VMArrayRef)337 pub fn dealloc_uninit_array(&mut self, arrayref: VMArrayRef) { 338 self.gc_heap.dealloc_uninit_array(arrayref); 339 } 340 341 /// Get the length of the given array. array_len(&self, arrayref: &VMArrayRef) -> u32342 pub fn array_len(&self, arrayref: &VMArrayRef) -> u32 { 343 self.gc_heap.array_len(arrayref) 344 } 345 346 /// Allocate an uninitialized exception object with the given type 347 /// index. 348 /// 349 /// This does NOT check that the index is currently allocated in the types 350 /// registry or that the layout matches the index's type. Failure to uphold 351 /// those invariants is memory safe, but will lead to general incorrectness 352 /// such as panics and wrong results. alloc_uninit_exn( &mut self, ty: VMSharedTypeIndex, layout: &GcStructLayout, ) -> Result<Result<VMExnRef, u64>>353 pub fn alloc_uninit_exn( 354 &mut self, 355 ty: VMSharedTypeIndex, 356 layout: &GcStructLayout, 357 ) -> Result<Result<VMExnRef, u64>> { 358 self.gc_heap 359 .alloc_uninit_struct_or_exn(ty, layout) 360 .map(|r| r.map(|r| r.into_exnref_unchecked())) 361 } 362 363 /// Deallocate an uninitialized exception object. dealloc_uninit_exn(&mut self, exnref: VMExnRef)364 pub fn dealloc_uninit_exn(&mut self, exnref: VMExnRef) { 365 self.gc_heap.dealloc_uninit_struct_or_exn(exnref.into()); 366 } 367 } 368