1use crate::RootSet;
4use crate::error::{Context, ensure};
5use crate::hash_map::HashMap;
6use crate::module::ModuleRegistry;
7use crate::store::{
8 Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter, yield_now,
9};
10use crate::type_registry::RegisteredType;
11use crate::vm::{
12 self, Backtrace, Frame, GcRootsList, GcStore, InstanceAllocationRequest, NopHasher,
13 SendSyncPtr, StoreGcHostAllocTypes, TraceInfo, VMGcRef,
14};
15use crate::{
16 ExnRef, GcHeapOutOfMemory, Result, Rooted, Store, StoreContextMut, ThrownException, bail,
17};
18use core::fmt;
19use core::mem::ManuallyDrop;
20use core::num::NonZeroU32;
21use core::ops::{Deref, DerefMut};
22use core::ptr::NonNull;
23use wasmtime_environ::DefinedTagIndex;
24use wasmtime_environ::packed_option::ReservedValue;
25
26#[derive(Default)]
27pub(crate) struct StoreGcData {
28 gc_roots: RootSet,
29 gc_roots_list: GcRootsList,
30 gc_host_alloc_types: StoreGcHostAllocTypes,
32 pending_exception: Option<VMGcRef>,
46
47 subtype_check_cache: HashMap<u64, bool, NopHasher>,
67}
68
69impl<T> Store<T> {
70 pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> {
88 StoreContextMut(&mut self.inner).gc(why)
89 }
90
91 pub fn gc_heap_capacity(&self) -> usize {
94 self.inner.gc_heap_capacity()
95 }
96
97 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
129 self.inner.throw_impl(exception)
130 }
131
132 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
149 self.inner.take_pending_exception_rooted()
150 }
151}
152
153impl<'a, T> StoreContextMut<'a, T> {
154 pub fn gc(&mut self, why: Option<&GcHeapOutOfMemory<()>>) -> Result<()> {
158 let (mut limiter, store) = self.0.validate_sync_resource_limiter_and_store_opaque()?;
159 vm::assert_ready(store.gc(
160 limiter.as_mut(),
161 None,
162 why.map(|e| e.bytes_needed()),
163 Asyncness::No,
164 ))?;
165 Ok(())
166 }
167
168 #[cfg(feature = "gc")]
173 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
174 self.0.inner.throw_impl(exception)
175 }
176
177 #[cfg(feature = "gc")]
182 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
183 self.0.inner.take_pending_exception_rooted()
184 }
185}
186
187#[derive(Debug)]
188struct GcHeapGrowthFailed;
189
190impl fmt::Display for GcHeapGrowthFailed {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.write_str("GC heap growth failed")
193 }
194}
195
196impl core::error::Error for GcHeapGrowthFailed {}
197
198impl StoreOpaque {
199 pub(crate) async fn gc(
210 &mut self,
211 limiter: Option<&mut StoreResourceLimiter<'_>>,
212 root: Option<VMGcRef>,
213 bytes_needed: Option<u64>,
214 asyncness: Asyncness,
215 ) -> Result<Option<VMGcRef>> {
216 let mut scope = crate::OpaqueRootScope::new(self);
217 scope.trim_gc_liveness_flags(true);
218 let store_id = scope.id();
219 let root = root.map(|r| scope.gc_roots_mut().push_lifo_root(store_id, r));
220
221 scope
222 .collect_and_maybe_grow_gc_heap(limiter, bytes_needed, asyncness)
223 .await?;
224
225 Ok(root.map(|r| {
226 let r = r
227 .get_gc_ref(&scope)
228 .expect("still in scope")
229 .unchecked_copy();
230 scope.clone_gc_ref(&r)
231 }))
232 }
233
234 pub(crate) fn trim_gc_liveness_flags(&mut self, eager: bool) {
239 if let Some(gc_store) = self.gc_store.as_mut() {
240 self.gc_data.gc_roots.trim_liveness_flags(gc_store, eager);
241 }
242 }
243
244 async fn collect_and_maybe_grow_gc_heap(
247 &mut self,
248 limiter: Option<&mut StoreResourceLimiter<'_>>,
249 bytes_needed: Option<u64>,
250 asyncness: Asyncness,
251 ) -> Result<()> {
252 log::trace!("collect_and_maybe_grow_gc_heap(bytes_needed = {bytes_needed:#x?})");
253 self.do_gc(asyncness).await?;
254 if let Some(n) = bytes_needed
255 && n > u64::try_from(self.gc_heap_capacity())?.saturating_sub(
256 self.gc_store.as_ref().map_or(0, |gc| {
257 u64::try_from(gc.last_post_gc_allocated_bytes.unwrap_or(0)).unwrap()
258 }),
259 )
260 {
261 if let Err(e) = self.grow_gc_heap(limiter, n, asyncness).await {
262 if e.is::<GcHeapGrowthFailed>() {
263 log::trace!("ignoring GC heap growth failure: {e}");
264 } else {
265 return Err(e);
266 }
267 }
268 }
269 Ok(())
270 }
271
272 pub(crate) async fn grow_gc_heap(
276 &mut self,
277 limiter: Option<&mut StoreResourceLimiter<'_>>,
278 bytes_needed: u64,
279 asyncness: Asyncness,
280 ) -> Result<()> {
281 log::trace!("Attempting to grow the GC heap by at least {bytes_needed:#x} bytes");
282
283 if bytes_needed == 0 {
284 return Ok(());
285 }
286
287 if self
290 .gc_store
291 .as_ref()
292 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth())
293 {
294 self.do_gc(asyncness).await?;
295 debug_assert!(
296 !self
297 .gc_store
298 .as_ref()
299 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth()),
300 "needs_gc_before_next_growth should return false after a GC"
301 );
302 }
303
304 let page_size = self.engine().tunables().gc_heap_memory_type().page_size();
305
306 let mut heap = TakenGcHeap::new(self);
309
310 let current_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
311 let current_size_in_pages = current_size_in_bytes / page_size;
312
313 let doubled_size_in_pages = current_size_in_pages.saturating_mul(2);
315 assert!(doubled_size_in_pages >= current_size_in_pages);
316 let delta_pages_for_doubling = doubled_size_in_pages - current_size_in_pages;
317
318 let max_size_in_bytes = 1 << 32;
324 let max_size_in_pages = max_size_in_bytes / page_size;
325 let delta_to_max_size_in_pages = max_size_in_pages - current_size_in_pages;
326 let delta_pages_for_alloc = delta_pages_for_doubling.min(delta_to_max_size_in_pages);
327
328 let pages_needed = bytes_needed.div_ceil(page_size);
335 assert!(pages_needed > 0);
336 let delta_pages_for_alloc = delta_pages_for_alloc.max(pages_needed);
337 assert!(delta_pages_for_alloc > 0);
338
339 unsafe {
343 heap.memory
344 .grow(delta_pages_for_alloc, limiter)
345 .await
346 .context(GcHeapGrowthFailed)?
347 .ok_or(GcHeapGrowthFailed)?;
348 }
349 *heap.store.vm_store_context.gc_heap.get_mut() = heap.memory.vmmemory();
350
351 let new_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
352 assert!(new_size_in_bytes > current_size_in_bytes);
353 heap.delta_bytes_grown = new_size_in_bytes - current_size_in_bytes;
354 let delta_bytes_for_alloc = delta_pages_for_alloc.checked_mul(page_size).unwrap();
355 assert!(
356 heap.delta_bytes_grown >= delta_bytes_for_alloc,
357 "{} should be greater than or equal to {delta_bytes_for_alloc}",
358 heap.delta_bytes_grown,
359 );
360 log::trace!(
361 " -> grew GC heap by {:#x} bytes: new size is {new_size_in_bytes:#x} bytes",
362 heap.delta_bytes_grown
363 );
364 return Ok(());
365
366 struct TakenGcHeap<'a> {
367 store: &'a mut StoreOpaque,
368 memory: ManuallyDrop<vm::Memory>,
369 delta_bytes_grown: u64,
370 }
371
372 impl<'a> TakenGcHeap<'a> {
373 fn new(store: &'a mut StoreOpaque) -> TakenGcHeap<'a> {
374 TakenGcHeap {
375 memory: ManuallyDrop::new(store.unwrap_gc_store_mut().gc_heap.take_memory()),
376 store,
377 delta_bytes_grown: 0,
378 }
379 }
380 }
381
382 impl Drop for TakenGcHeap<'_> {
383 fn drop(&mut self) {
384 unsafe {
390 self.store.unwrap_gc_store_mut().gc_heap.replace_memory(
391 ManuallyDrop::take(&mut self.memory),
392 self.delta_bytes_grown,
393 );
394 }
395 }
396 }
397 }
398
399 fn replace_gc_zeal_alloc_counter(
400 &mut self,
401 new_value: Option<NonZeroU32>,
402 ) -> Option<NonZeroU32> {
403 if let Some(gc_store) = &mut self.gc_store {
404 gc_store.replace_gc_zeal_alloc_counter(new_value)
405 } else {
406 None
407 }
408 }
409
410 pub(crate) async fn retry_after_gc_async<T, U>(
419 &mut self,
420 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
421 value: T,
422 asyncness: Asyncness,
423 alloc_func: impl Fn(&mut Self, T) -> Result<U>,
424 ) -> Result<U>
425 where
426 T: Send + Sync + 'static,
427 {
428 self.ensure_gc_store(limiter.as_deref_mut()).await?;
429
430 match alloc_func(self, value) {
431 Ok(x) => Ok(x),
432 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
433 Ok(oom) => {
434 log::trace!("Got GC heap OOM: {oom}");
435
436 let (value, oom) = oom.take_inner();
437 let bytes_needed = oom.bytes_needed();
438
439 let mut store = WithoutGcZealAllocCounter::new(self);
440
441 let gc_heap_capacity = store
442 .gc_store
443 .as_ref()
444 .map_or(0, |gc_store| gc_store.gc_heap_capacity());
445 let last_gc_heap_usage = store.gc_store.as_ref().map_or(0, |gc_store| {
446 gc_store.last_post_gc_allocated_bytes.unwrap_or(0)
447 });
448
449 if should_collect_first(bytes_needed, gc_heap_capacity, last_gc_heap_usage) {
450 log::trace!(
451 "Collecting first, then retrying; growing GC heap if collecting didn't \
452 free up enough space, then retrying again"
453 );
454 store
455 .gc(limiter.as_deref_mut(), None, None, asyncness)
456 .await?;
457
458 match alloc_func(&mut store, value) {
459 Ok(x) => Ok(x),
460 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
461 Ok(oom2) => {
462 let (value, _) = oom2.take_inner();
465 let _ =
469 store.grow_gc_heap(limiter, bytes_needed, asyncness).await;
470
471 alloc_func(&mut store, value)
472 }
473 Err(e) => Err(e),
474 },
475 }
476 } else {
477 log::trace!(
478 "Grow GC heap first, collecting if growth failed, then retrying"
479 );
480
481 if let Err(e) = store
482 .grow_gc_heap(limiter.as_deref_mut(), bytes_needed.max(1), asyncness)
483 .await
484 {
485 log::trace!("growing GC heap failed: {e}");
486 store.gc(limiter, None, None, asyncness).await?;
487 }
488
489 alloc_func(&mut store, value)
490 }
491 }
492 Err(e) => Err(e),
493 },
494 }
495 }
496
497 pub(crate) fn set_pending_exception(&mut self, exnref: &VMGcRef) -> crate::Error {
505 debug_assert!(exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
506 let gc_store = self.gc_store.as_mut().unwrap();
507 match gc_store.write_gc_ref(&mut self.gc_data.pending_exception, Some(exnref)) {
508 Ok(()) => ThrownException.into(),
509 Err(e) => e,
510 }
511 }
512
513 pub(crate) fn expose_pending_exception_to_wasm(&mut self) -> Option<NonZeroU32> {
516 let exnref = self.gc_data.pending_exception.take()?;
517 let gc_store = self.unwrap_gc_store_mut();
518 debug_assert!(exnref.is_exnref(&*gc_store.gc_heap));
519 Some(gc_store.expose_gc_ref_to_wasm(exnref).unwrap())
520 }
521
522 fn take_pending_exception_rooted(&mut self) -> Option<Rooted<ExnRef>> {
525 let vmexnref = self.gc_data.pending_exception.take()?;
526 debug_assert!(vmexnref.is_exnref(&*self.unwrap_gc_store().gc_heap));
527 let mut nogc = AutoAssertNoGc::new(self);
528 Some(Rooted::new(&mut nogc, vmexnref))
529 }
530
531 pub(crate) fn pending_exception_tag_and_instance(
534 &mut self,
535 ) -> Option<(InstanceId, DefinedTagIndex)> {
536 let pending_exnref = self.gc_data.pending_exception.as_ref()?.unchecked_copy();
537 debug_assert!(pending_exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
538 let mut store = AutoAssertNoGc::new(self);
539
540 pending_exnref.into_exnref_unchecked().tag(&mut store).ok()
547 }
548
549 #[cfg(feature = "debug")]
552 pub(crate) fn pending_exception_owned_rooted(
553 &mut self,
554 ) -> Result<Option<crate::OwnedRooted<ExnRef>>, crate::OutOfMemory> {
555 let pending = match &self.gc_data.pending_exception {
556 Some(r) => r,
557 None => return Ok(None),
558 };
559 let cloned = self.gc_store.as_mut().unwrap().clone_gc_ref(pending);
560 let mut nogc = AutoAssertNoGc::new(self);
561 Ok(Some(crate::OwnedRooted::new(&mut nogc, cloned)?))
562 }
563
564 fn throw_impl<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
569 let exception = exception.try_gc_ref(self)?.unchecked_copy();
570 Err(self.set_pending_exception(&exception))
571 }
572
573 #[inline]
587 pub(crate) fn require_gc_store(&self) -> Result<&GcStore> {
588 match &self.gc_store {
589 Some(gc_store) => Ok(gc_store),
590 None => bail!("GC heap not initialized yet"),
591 }
592 }
593
594 #[inline]
596 pub(crate) fn require_gc_store_mut(&mut self) -> Result<&mut GcStore> {
597 match &mut self.gc_store {
598 Some(gc_store) => Ok(gc_store),
599 None => bail!("GC heap not initialized yet"),
600 }
601 }
602
603 pub(crate) fn gc_heap_capacity(&self) -> usize {
606 match self.gc_store.as_ref() {
607 Some(gc_store) => gc_store.gc_heap_capacity(),
608 None => 0,
609 }
610 }
611
612 async fn do_gc(&mut self, asyncness: Asyncness) -> Result<()> {
613 if self.gc_store.is_none() {
615 return Ok(());
616 }
617
618 if log::log_enabled!(log::Level::Trace) {
619 let gc_store = self.gc_store.as_ref().unwrap();
620 let capacity = gc_store.gc_heap_capacity();
621 let live_set_size = gc_store.last_post_gc_allocated_bytes.unwrap_or(0);
622 let utilization = live_set_size as f64 / capacity as f64 * 100.0;
623 log::trace!(
624 "============ Begin GC ===========\n\
625 \t GC heap capacity = {capacity:#010x} bytes\n\
626 \tlast post-GC live-set size = {live_set_size:#010x} bytes\n\
627 \t GC heap utilization = {utilization:.02}%",
628 );
629 }
630
631 let mut roots = core::mem::take(&mut self.gc_data.gc_roots_list);
634
635 self.trace_roots(&mut roots, asyncness).await;
636 self.gc_store
637 .as_mut()
638 .unwrap()
639 .gc(
640 asyncness,
641 unsafe { roots.iter() },
642 &self.modules,
643 &self.gc_data.gc_host_alloc_types,
644 yield_now,
649 )
650 .await?;
651
652 roots.clear();
654 self.gc_data.gc_roots_list = roots;
655
656 if log::log_enabled!(log::Level::Trace) {
657 let gc_store = self.gc_store.as_ref().unwrap();
658 let capacity = gc_store.gc_heap_capacity();
659 let live_set_size = gc_store.last_post_gc_allocated_bytes.unwrap_or(0);
660 let utilization = live_set_size as f64 / capacity as f64 * 100.0;
661 log::trace!(
662 "============ End GC ===========\n\
663 \t GC heap capacity = {capacity:#010x} bytes\n\
664 \tpost-GC live-set size = {live_set_size:#010x} bytes\n\
665 \t GC heap utilization = {utilization:.02}%",
666 );
667 }
668 Ok(())
669 }
670
671 async fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList, asyncness: Asyncness) {
672 log::trace!("Begin trace GC roots");
673
674 assert!(gc_roots_list.is_empty());
676
677 self.trace_wasm_stack_roots(gc_roots_list);
678 if asyncness != Asyncness::No {
679 self.yield_now().await;
680 }
681
682 #[cfg(feature = "stack-switching")]
683 {
684 self.trace_wasm_continuation_roots(gc_roots_list);
685 if asyncness != Asyncness::No {
686 self.yield_now().await;
687 }
688 }
689
690 self.trace_vmctx_roots(gc_roots_list);
691 if asyncness != Asyncness::No {
692 self.yield_now().await;
693 }
694
695 self.trace_instance_roots(gc_roots_list);
696 if asyncness != Asyncness::No {
697 self.yield_now().await;
698 }
699
700 self.trace_user_roots(gc_roots_list);
701 if asyncness != Asyncness::No {
702 self.yield_now().await;
703 }
704
705 self.trace_pending_exception_roots(gc_roots_list);
706
707 log::trace!("End trace GC roots")
708 }
709
710 pub(crate) fn trace_wasm_stack_frame(
711 modules: &ModuleRegistry,
712 gc_roots_list: &mut GcRootsList,
713 frame: Frame,
714 ) {
715 let pc = frame.pc();
716 debug_assert!(pc != 0, "we should always get a valid PC for Wasm frames");
717
718 let fp = frame.fp() as *mut usize;
719 debug_assert!(
720 !fp.is_null(),
721 "we should always get a valid frame pointer for Wasm frames"
722 );
723
724 let (store_code, offset) = modules
725 .store_code_by_pc(pc)
726 .expect("should have store code for Wasm frame");
727 let offset = u32::try_from(offset).unwrap();
728
729 let stack_map =
730 wasmtime_environ::StackMap::lookup(offset, store_code.code_memory().stack_map_data());
731
732 if let Some(stack_map) = stack_map {
733 log::trace!(
734 "We have a stack map that maps {} bytes in this Wasm frame",
735 stack_map.frame_size()
736 );
737
738 let sp = unsafe { stack_map.sp(fp) };
739 for stack_slot in unsafe { stack_map.live_gc_refs(sp) } {
740 unsafe {
741 Self::trace_wasm_stack_slot(gc_roots_list, stack_slot);
742 }
743 }
744 }
745
746 #[cfg(feature = "debug")]
747 if let Some(frame_table) = store_code.code_memory().frame_table() {
748 for stack_slot in crate::debug::gc_refs_in_frame(frame_table, offset, fp) {
749 unsafe {
750 Self::trace_wasm_stack_slot(gc_roots_list, stack_slot);
751 }
752 }
753 }
754 }
755
756 unsafe fn trace_wasm_stack_slot(gc_roots_list: &mut GcRootsList, stack_slot: *mut u32) {
757 let raw: u32 = unsafe { core::ptr::read(stack_slot) };
758 log::trace!("Stack slot @ {stack_slot:p} = {raw:#x}");
759
760 let gc_ref = vm::VMGcRef::from_raw_u32(raw);
761 if gc_ref.is_some() {
762 unsafe {
763 gc_roots_list
764 .add_wasm_stack_root(SendSyncPtr::new(NonNull::new(stack_slot).unwrap()));
765 }
766 }
767 }
768
769 fn trace_wasm_stack_roots(&mut self, gc_roots_list: &mut GcRootsList) {
770 log::trace!("Begin trace GC roots :: Wasm stack");
771
772 Backtrace::trace(self, |frame| {
773 Self::trace_wasm_stack_frame(self.modules(), gc_roots_list, frame);
774 core::ops::ControlFlow::Continue(())
775 });
776
777 #[cfg(feature = "component-model-async")]
778 if self.concurrency_support() {
779 let unwind = self.unwinder();
780 let StoreOpaque {
781 modules,
782 store_data,
783 ..
784 } = self;
785 store_data
786 .components
787 .task_state_mut()
788 .concurrent_state_mut()
789 .trace_fiber_roots(modules, unwind, gc_roots_list);
790 }
791
792 log::trace!("End trace GC roots :: Wasm stack");
793 }
794
795 #[cfg(feature = "stack-switching")]
796 fn trace_wasm_continuation_roots(&mut self, gc_roots_list: &mut GcRootsList) {
797 use crate::vm::VMStackState;
798
799 log::trace!("Begin trace GC roots :: continuations");
800
801 for continuation in &self.continuations {
802 let state = continuation.common_stack_information.state;
803
804 match state {
812 VMStackState::Suspended => {
813 Backtrace::trace_suspended_continuation(self, continuation.deref(), |frame| {
814 Self::trace_wasm_stack_frame(self.modules(), gc_roots_list, frame);
815 core::ops::ControlFlow::Continue(())
816 });
817 }
818 VMStackState::Running => {
819 }
821 VMStackState::Parent => {
822 }
826 VMStackState::Fresh | VMStackState::Returned => {
827 }
829 }
830 }
831
832 log::trace!("End trace GC roots :: continuations");
833 }
834
835 fn trace_vmctx_roots(&mut self, gc_roots_list: &mut GcRootsList) {
836 log::trace!("Begin trace GC roots :: vmctx");
837 self.for_each_global(|store, global| global.trace_root(store, gc_roots_list));
838 self.for_each_table(|store, table| table.trace_roots(store, gc_roots_list));
839 log::trace!("End trace GC roots :: vmctx");
840 }
841
842 fn trace_instance_roots(&mut self, gc_roots_list: &mut GcRootsList) {
843 log::trace!("Begin trace GC roots :: instance");
844 for (_id, instance) in &mut self.instances {
845 unsafe {
848 instance
849 .handle
850 .get_mut()
851 .trace_element_segment_roots(gc_roots_list);
852 }
853 }
854 log::trace!("End trace GC roots :: instance");
855 }
856
857 fn trace_user_roots(&mut self, gc_roots_list: &mut GcRootsList) {
858 log::trace!("Begin trace GC roots :: user");
859 self.gc_data.gc_roots.trace_roots(gc_roots_list);
860 log::trace!("End trace GC roots :: user");
861 }
862
863 fn trace_pending_exception_roots(&mut self, gc_roots_list: &mut GcRootsList) {
864 log::trace!("Begin trace GC roots :: pending exception");
865 if let Some(pending_exception) = self.gc_data.pending_exception.as_mut() {
866 unsafe {
867 gc_roots_list.add_vmgcref_root(pending_exception.into(), "Pending exception");
868 }
869 }
870 log::trace!("End trace GC roots :: pending exception");
871 }
872
873 pub(crate) fn insert_gc_host_alloc_type(&mut self, ty: RegisteredType) {
880 let trace_info = ty.layout().map(TraceInfo::new);
881 self.gc_data
882 .gc_host_alloc_types
883 .insert(ty.index(), (ty, trace_info));
884 }
885
886 #[inline]
894 pub(crate) async fn ensure_gc_store(
895 &mut self,
896 limiter: Option<&mut StoreResourceLimiter<'_>>,
897 ) -> Result<&mut GcStore> {
898 if self.gc_store.is_some() {
899 return Ok(self.gc_store.as_mut().unwrap());
900 }
901 self.allocate_gc_store(limiter).await
902 }
903
904 #[inline(never)]
905 async fn allocate_gc_store(
906 &mut self,
907 limiter: Option<&mut StoreResourceLimiter<'_>>,
908 ) -> Result<&mut GcStore> {
909 log::trace!("allocating GC heap for store {:?}", self.id());
910
911 assert!(self.gc_store.is_none());
912 assert_eq!(
913 self.vm_store_context.gc_heap.get_mut().base.as_non_null(),
914 NonNull::dangling(),
915 );
916 assert_eq!(self.vm_store_context.gc_heap.get_mut().current_length(), 0);
917
918 let engine = self.engine();
919 let mem_ty = engine.tunables().gc_heap_memory_type();
920 ensure!(
921 engine.features().gc_types(),
922 "cannot allocate a GC store when GC is disabled at configuration time"
923 );
924 let gc_runtime = engine
925 .gc_runtime()
926 .context("no GC runtime: GC disabled at compile time or configuration time")?;
927
928 let mut request = InstanceAllocationRequest {
930 id: InstanceId::reserved_value(),
931 runtime_info: engine.empty_module_runtime_info(),
932 imports: vm::Imports::default(),
933 store: self,
934 limiter,
935 };
936
937 let (mem_alloc_index, mem) = engine
938 .allocator()
939 .allocate_memory(
940 &mut request,
941 &mem_ty,
942 None,
943 wasmtime_environ::MemoryKind::GcHeap,
944 )
945 .await?;
946
947 let (index, mut heap) =
950 match engine
951 .allocator()
952 .allocate_gc_heap(engine, &**gc_runtime, mem_alloc_index)
953 {
954 Ok(pair) => pair,
955 Err(e) => unsafe {
956 engine
957 .allocator()
958 .deallocate_memory(None, mem_alloc_index, mem);
959 return Err(e);
960 },
961 };
962 heap.attach(mem);
963
964 let gc_store = GcStore::new(index, heap, engine.tunables().gc_zeal_alloc_counter);
965 *self.vm_store_context.gc_heap.get_mut() = gc_store.vmmemory_definition();
966 Ok(self.gc_store.insert(gc_store))
967 }
968
969 pub fn has_pending_exception(&self) -> bool {
971 self.gc_data.pending_exception.is_some()
972 }
973
974 #[inline]
975 pub(crate) fn gc_roots(&self) -> &RootSet {
976 &self.gc_data.gc_roots
977 }
978
979 #[inline]
980 pub(crate) fn gc_roots_mut(&mut self) -> &mut RootSet {
981 &mut self.gc_data.gc_roots
982 }
983
984 #[inline]
985 pub(crate) fn enter_gc_lifo_scope(&self) -> usize {
986 self.gc_data.gc_roots.enter_lifo_scope()
987 }
988
989 #[inline]
990 pub(crate) fn exit_gc_lifo_scope(&mut self, scope: usize) {
991 self.gc_data
992 .gc_roots
993 .exit_lifo_scope(self.gc_store.as_mut(), scope);
994 }
995
996 pub(crate) fn is_subtype_cached(
1003 &mut self,
1004 sub: wasmtime_environ::VMSharedTypeIndex,
1005 sup: wasmtime_environ::VMSharedTypeIndex,
1006 ) -> bool {
1007 const MAX_SIZE: usize = 1 << 16; let key = (u64::from(sub.as_u32()) << 32) | u64::from(sup.as_u32());
1010 let engine_answer = || self.engine.signatures().is_subtype(sub, sup);
1011 if self.gc_data.subtype_check_cache.len() < MAX_SIZE {
1012 *self
1013 .gc_data
1014 .subtype_check_cache
1015 .entry(key)
1016 .or_insert_with(engine_answer)
1017 } else {
1018 self.gc_data
1019 .subtype_check_cache
1020 .get(&key)
1021 .copied()
1022 .unwrap_or_else(engine_answer)
1023 }
1024 }
1025}
1026
1027struct WithoutGcZealAllocCounter<'a> {
1029 store: &'a mut StoreOpaque,
1030 counter: Option<NonZeroU32>,
1031}
1032
1033impl Deref for WithoutGcZealAllocCounter<'_> {
1034 type Target = StoreOpaque;
1035
1036 fn deref(&self) -> &Self::Target {
1037 &self.store
1038 }
1039}
1040
1041impl DerefMut for WithoutGcZealAllocCounter<'_> {
1042 fn deref_mut(&mut self) -> &mut Self::Target {
1043 &mut self.store
1044 }
1045}
1046
1047impl Drop for WithoutGcZealAllocCounter<'_> {
1048 fn drop(&mut self) {
1049 self.store.replace_gc_zeal_alloc_counter(self.counter);
1050 }
1051}
1052
1053impl<'a> WithoutGcZealAllocCounter<'a> {
1054 pub fn new(store: &'a mut StoreOpaque) -> Self {
1055 let counter = store.replace_gc_zeal_alloc_counter(None);
1056 WithoutGcZealAllocCounter { store, counter }
1057 }
1058}
1059
1060#[track_caller]
1069fn should_collect_first(
1070 bytes_needed: u64,
1071 gc_heap_capacity: usize,
1072 last_gc_heap_usage: usize,
1073) -> bool {
1074 debug_assert!(last_gc_heap_usage <= gc_heap_capacity);
1075
1076 if gc_heap_capacity == 0 {
1083 return false;
1084 }
1085
1086 if bytes_needed == 0 {
1089 return true;
1090 }
1091
1092 let Ok(bytes_needed) = usize::try_from(bytes_needed) else {
1093 return false;
1096 };
1097
1098 if bytes_needed > isize::MAX.cast_unsigned() {
1099 return false;
1103 }
1104
1105 let Some(predicted_usage) = last_gc_heap_usage.checked_add(bytes_needed) else {
1106 return true;
1110 };
1111
1112 predicted_usage < gc_heap_capacity / 2
1117}
1118
1119#[cfg(test)]
1120mod tests {
1121 use super::should_collect_first;
1122 use crate::{AsContextMut, Config, Engine, ExternRef, Result, Store};
1123
1124 #[test]
1125 fn test_should_collect_first() {
1126 for bytes_needed in 0..256 {
1128 assert_eq!(should_collect_first(bytes_needed, 0, 0), false);
1129 }
1130
1131 for cap in 1..256 {
1133 for usage in 0..=cap {
1134 assert_eq!(should_collect_first(0, cap, usage), true);
1135 }
1136 }
1137
1138 let max_alloc_usize = isize::MAX.cast_unsigned();
1139 let max_alloc_u64 = u64::try_from(max_alloc_usize).unwrap();
1140
1141 assert_eq!(
1144 should_collect_first(max_alloc_u64 + 1, max_alloc_usize, 0),
1145 false,
1146 );
1147
1148 assert_eq!(should_collect_first(1, usize::MAX, usize::MAX), true);
1150
1151 assert_eq!(should_collect_first(16, 1024, 64), true);
1154
1155 assert_eq!(should_collect_first(16, 1024, 512), false);
1159 }
1160
1161 #[test]
1162 fn gc_heap_initial_size() -> Result<()> {
1163 let mut config = Config::new();
1164 config.gc_heap_initial_size(1 << 20);
1165 let engine = Engine::new(&config)?;
1166 let mut store = Store::new(&engine, ());
1167 ExternRef::new(&mut store, 1)?;
1168
1169 let gc_store = store.as_context_mut().0.unwrap_gc_store();
1170 assert_eq!(gc_store.gc_heap_capacity(), 1 << 20);
1171 Ok(())
1172 }
1173}