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 Engine, ExnRef, GcHeapOutOfMemory, Result, Rooted, Store, StoreContextMut, Tag,
17 ThrownException, bail,
18};
19use core::fmt;
20use core::mem::ManuallyDrop;
21use core::num::NonZeroU32;
22use core::ops::{Deref, DerefMut};
23use core::ptr::NonNull;
24use wasmtime_environ::DefinedTagIndex;
25use wasmtime_environ::packed_option::ReservedValue;
26
27#[derive(Default)]
28pub(crate) struct StoreGcData {
29 gc_roots: RootSet,
30 gc_roots_list: GcRootsList,
31 gc_host_alloc_types: StoreGcHostAllocTypes,
33 pending_exception: Option<VMGcRef>,
47
48 subtype_check_cache: HashMap<u64, bool, NopHasher>,
68}
69
70impl<T> Store<T> {
71 pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> {
89 StoreContextMut(&mut self.inner).gc(why)
90 }
91
92 pub fn gc_heap_capacity(&self) -> usize {
95 self.inner.gc_heap_capacity()
96 }
97
98 pub fn gc_heap_grow(&mut self, bytes: u64) -> Result<()> {
125 StoreContextMut(&mut self.inner).gc_heap_grow(bytes)
126 }
127
128 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
160 self.inner.throw_impl(exception)
161 }
162
163 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
180 self.inner.take_pending_exception_rooted()
181 }
182}
183
184impl<'a, T> StoreContextMut<'a, T> {
185 pub fn gc(&mut self, why: Option<&GcHeapOutOfMemory<()>>) -> Result<()> {
189 let (mut limiter, store) = self.0.validate_sync_resource_limiter_and_store_opaque()?;
190 vm::assert_ready(store.gc(
191 limiter.as_mut(),
192 None,
193 why.map(|e| e.bytes_needed()),
194 Asyncness::No,
195 ))?;
196 Ok(())
197 }
198
199 pub fn gc_heap_grow(&mut self, bytes: u64) -> Result<()> {
203 let (mut limiter, store) = self.0.validate_sync_resource_limiter_and_store_opaque()?;
204 vm::assert_ready(store.grow_gc_heap(limiter.as_mut(), bytes, crate::store::Asyncness::No))
205 }
206
207 #[cfg(feature = "gc")]
212 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
213 self.0.inner.throw_impl(exception)
214 }
215
216 #[cfg(feature = "gc")]
221 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
222 self.0.inner.take_pending_exception_rooted()
223 }
224}
225
226#[derive(Debug)]
227struct GcHeapGrowthFailed;
228
229impl fmt::Display for GcHeapGrowthFailed {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 f.write_str("GC heap growth failed")
232 }
233}
234
235impl core::error::Error for GcHeapGrowthFailed {}
236
237impl StoreOpaque {
238 pub(crate) async fn gc(
249 &mut self,
250 limiter: Option<&mut StoreResourceLimiter<'_>>,
251 root: Option<VMGcRef>,
252 bytes_needed: Option<u64>,
253 asyncness: Asyncness,
254 ) -> Result<Option<VMGcRef>> {
255 let mut scope = crate::OpaqueRootScope::new(self);
256 scope.trim_gc_liveness_flags(true);
257 let store_id = scope.id();
258 let root = root.map(|r| scope.gc_roots_mut().push_lifo_root(store_id, r));
259
260 scope
261 .collect_and_maybe_grow_gc_heap(limiter, bytes_needed, asyncness)
262 .await?;
263
264 Ok(root.map(|r| {
265 let r = r
266 .get_gc_ref(&scope)
267 .expect("still in scope")
268 .unchecked_copy();
269 scope.clone_gc_ref(&r)
270 }))
271 }
272
273 pub(crate) fn trim_gc_liveness_flags(&mut self, eager: bool) {
278 if let Some(gc_store) = self.gc_store.as_mut() {
279 self.gc_data.gc_roots.trim_liveness_flags(gc_store, eager);
280 }
281 }
282
283 async fn collect_and_maybe_grow_gc_heap(
286 &mut self,
287 limiter: Option<&mut StoreResourceLimiter<'_>>,
288 bytes_needed: Option<u64>,
289 asyncness: Asyncness,
290 ) -> Result<()> {
291 log::trace!("collect_and_maybe_grow_gc_heap(bytes_needed = {bytes_needed:#x?})");
292 self.do_gc(asyncness).await?;
293 if let Some(n) = bytes_needed
294 && n > u64::try_from(self.gc_heap_capacity())?.saturating_sub(
295 self.gc_store.as_ref().map_or(0, |gc| {
296 u64::try_from(gc.last_post_gc_allocated_bytes.unwrap_or(0)).unwrap()
297 }),
298 )
299 {
300 if let Err(e) = self.grow_gc_heap(limiter, n, asyncness).await {
301 if e.is::<GcHeapGrowthFailed>() {
302 log::trace!("ignoring GC heap growth failure: {e}");
303 } else {
304 return Err(e);
305 }
306 }
307 }
308 Ok(())
309 }
310
311 pub(crate) async fn grow_gc_heap(
315 &mut self,
316 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
317 bytes_needed: u64,
318 asyncness: Asyncness,
319 ) -> Result<()> {
320 log::trace!("Attempting to grow the GC heap by at least {bytes_needed:#x} bytes");
321
322 if bytes_needed == 0 {
323 return Ok(());
324 }
325
326 if self
329 .gc_store
330 .as_ref()
331 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth())
332 {
333 self.do_gc(asyncness).await?;
334 debug_assert!(
335 !self
336 .gc_store
337 .as_ref()
338 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth()),
339 "needs_gc_before_next_growth should return false after a GC"
340 );
341 }
342
343 self.ensure_gc_store(limiter.as_deref_mut()).await?;
345
346 let page_size = self.engine().tunables().gc_heap_memory_type().page_size();
347
348 let mut heap = TakenGcHeap::new(self);
351
352 let current_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
353 let current_size_in_pages = current_size_in_bytes / page_size;
354
355 let doubled_size_in_pages = current_size_in_pages.saturating_mul(2);
357 assert!(doubled_size_in_pages >= current_size_in_pages);
358 let delta_pages_for_doubling = doubled_size_in_pages - current_size_in_pages;
359
360 let max_size_in_bytes = 1 << 32;
366 let max_size_in_pages = max_size_in_bytes / page_size;
367 let delta_to_max_size_in_pages = max_size_in_pages - current_size_in_pages;
368 let delta_pages_for_alloc = delta_pages_for_doubling.min(delta_to_max_size_in_pages);
369
370 let pages_needed = bytes_needed.div_ceil(page_size);
377 assert!(pages_needed > 0);
378 let delta_pages_for_alloc = delta_pages_for_alloc.max(pages_needed);
379 assert!(delta_pages_for_alloc > 0);
380
381 unsafe {
385 heap.memory
386 .grow(delta_pages_for_alloc, limiter)
387 .await
388 .context(GcHeapGrowthFailed)?
389 .ok_or(GcHeapGrowthFailed)?;
390 }
391 *heap.store.vm_store_context.gc_heap.get_mut() = heap.memory.vmmemory();
392
393 let new_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
394 assert!(new_size_in_bytes > current_size_in_bytes);
395 heap.delta_bytes_grown = new_size_in_bytes - current_size_in_bytes;
396 let delta_bytes_for_alloc = delta_pages_for_alloc.checked_mul(page_size).unwrap();
397 assert!(
398 heap.delta_bytes_grown >= delta_bytes_for_alloc,
399 "{} should be greater than or equal to {delta_bytes_for_alloc}",
400 heap.delta_bytes_grown,
401 );
402 log::trace!(
403 " -> grew GC heap by {:#x} bytes: new size is {new_size_in_bytes:#x} bytes",
404 heap.delta_bytes_grown
405 );
406 return Ok(());
407
408 struct TakenGcHeap<'a> {
409 store: &'a mut StoreOpaque,
410 memory: ManuallyDrop<vm::Memory>,
411 delta_bytes_grown: u64,
412 }
413
414 impl<'a> TakenGcHeap<'a> {
415 fn new(store: &'a mut StoreOpaque) -> TakenGcHeap<'a> {
416 TakenGcHeap {
417 memory: ManuallyDrop::new(store.unwrap_gc_store_mut().gc_heap.take_memory()),
418 store,
419 delta_bytes_grown: 0,
420 }
421 }
422 }
423
424 impl Drop for TakenGcHeap<'_> {
425 fn drop(&mut self) {
426 unsafe {
432 self.store.unwrap_gc_store_mut().gc_heap.replace_memory(
433 ManuallyDrop::take(&mut self.memory),
434 self.delta_bytes_grown,
435 );
436 }
437 }
438 }
439 }
440
441 fn replace_gc_zeal_alloc_counter(
442 &mut self,
443 new_value: Option<NonZeroU32>,
444 ) -> Option<NonZeroU32> {
445 if let Some(gc_store) = &mut self.gc_store {
446 gc_store.replace_gc_zeal_alloc_counter(new_value)
447 } else {
448 None
449 }
450 }
451
452 pub(crate) async fn retry_after_gc_async<T, U>(
461 &mut self,
462 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
463 value: T,
464 asyncness: Asyncness,
465 alloc_func: impl Fn(&mut Self, T) -> Result<U>,
466 ) -> Result<U>
467 where
468 T: Send + Sync + 'static,
469 {
470 self.ensure_gc_store(limiter.as_deref_mut()).await?;
471
472 match alloc_func(self, value) {
473 Ok(x) => Ok(x),
474 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
475 Ok(oom) => {
476 log::trace!("Got GC heap OOM: {oom}");
477
478 let (value, oom) = oom.take_inner();
479 let bytes_needed = oom.bytes_needed();
480
481 let mut store = WithoutGcZealAllocCounter::new(self);
482
483 let gc_heap_capacity = store
484 .gc_store
485 .as_ref()
486 .map_or(0, |gc_store| gc_store.gc_heap_capacity());
487 let last_gc_heap_usage = store.gc_store.as_ref().map_or(0, |gc_store| {
488 gc_store.last_post_gc_allocated_bytes.unwrap_or(0)
489 });
490
491 if should_collect_first(bytes_needed, gc_heap_capacity, last_gc_heap_usage) {
492 log::trace!(
493 "Collecting first, then retrying; growing GC heap if collecting didn't \
494 free up enough space, then retrying again"
495 );
496 store
497 .gc(limiter.as_deref_mut(), None, None, asyncness)
498 .await?;
499
500 match alloc_func(&mut store, value) {
501 Ok(x) => Ok(x),
502 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
503 Ok(oom2) => {
504 let (value, _) = oom2.take_inner();
507 let _ =
511 store.grow_gc_heap(limiter, bytes_needed, asyncness).await;
512
513 alloc_func(&mut store, value)
514 }
515 Err(e) => Err(e),
516 },
517 }
518 } else {
519 log::trace!(
520 "Grow GC heap first, collecting if growth failed, then retrying"
521 );
522
523 if let Err(e) = store
524 .grow_gc_heap(limiter.as_deref_mut(), bytes_needed.max(1), asyncness)
525 .await
526 {
527 log::trace!("growing GC heap failed: {e}");
528 store.gc(limiter, None, None, asyncness).await?;
529 }
530
531 alloc_func(&mut store, value)
532 }
533 }
534 Err(e) => Err(e),
535 },
536 }
537 }
538
539 pub(crate) fn set_pending_exception(&mut self, exnref: &VMGcRef) -> crate::Error {
547 debug_assert!(exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
548 let gc_store = self.gc_store.as_mut().unwrap();
549 match gc_store.write_gc_ref(&mut self.gc_data.pending_exception, Some(exnref)) {
550 Ok(()) => ThrownException.into(),
551 Err(e) => e,
552 }
553 }
554
555 pub(crate) fn expose_pending_exception_to_wasm(&mut self) -> Option<NonZeroU32> {
558 let exnref = self.gc_data.pending_exception.take()?;
559 let gc_store = self.unwrap_gc_store_mut();
560 debug_assert!(exnref.is_exnref(&*gc_store.gc_heap));
561 Some(gc_store.expose_gc_ref_to_wasm(exnref).unwrap())
562 }
563
564 fn take_pending_exception_rooted(&mut self) -> Option<Rooted<ExnRef>> {
567 let vmexnref = self.gc_data.pending_exception.take()?;
568 debug_assert!(vmexnref.is_exnref(&*self.unwrap_gc_store().gc_heap));
569 let mut nogc = AutoAssertNoGc::new(self);
570 Some(Rooted::new(&mut nogc, vmexnref))
571 }
572
573 pub(crate) fn pending_exception_tag_and_instance(
576 &mut self,
577 ) -> Option<(InstanceId, DefinedTagIndex)> {
578 let pending_exnref = self.gc_data.pending_exception.as_ref()?.unchecked_copy();
579 debug_assert!(pending_exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
580 let mut store = AutoAssertNoGc::new(self);
581
582 let (instance, index) = pending_exnref
589 .into_exnref_unchecked()
590 .tag(&mut store)
591 .ok()?;
592
593 Some(Tag::from_raw_indices(&store, instance, index)?.to_raw_indices())
597 }
598
599 #[cfg(feature = "debug")]
602 pub(crate) fn pending_exception_owned_rooted(
603 &mut self,
604 ) -> Result<Option<crate::OwnedRooted<ExnRef>>, crate::OutOfMemory> {
605 let pending = match &self.gc_data.pending_exception {
606 Some(r) => r,
607 None => return Ok(None),
608 };
609 let cloned = self.gc_store.as_mut().unwrap().clone_gc_ref(pending);
610 let mut nogc = AutoAssertNoGc::new(self);
611 Ok(Some(crate::OwnedRooted::new(&mut nogc, cloned)?))
612 }
613
614 fn throw_impl<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
619 let exception = exception.try_gc_ref(self)?.unchecked_copy();
620 Err(self.set_pending_exception(&exception))
621 }
622
623 #[inline]
637 pub(crate) fn require_gc_store(&self) -> Result<&GcStore> {
638 match &self.gc_store {
639 Some(gc_store) => Ok(gc_store),
640 None => bail!("GC heap not initialized yet"),
641 }
642 }
643
644 #[inline]
646 pub(crate) fn require_gc_store_mut(&mut self) -> Result<&mut GcStore> {
647 match &mut self.gc_store {
648 Some(gc_store) => Ok(gc_store),
649 None => bail!("GC heap not initialized yet"),
650 }
651 }
652
653 pub(crate) fn gc_heap_capacity(&self) -> usize {
656 match self.gc_store.as_ref() {
657 Some(gc_store) => gc_store.gc_heap_capacity(),
658 None => 0,
659 }
660 }
661
662 async fn do_gc(&mut self, asyncness: Asyncness) -> Result<()> {
663 if self.gc_store.is_none() {
665 return Ok(());
666 }
667
668 if log::log_enabled!(log::Level::Trace) {
669 let gc_store = self.gc_store.as_ref().unwrap();
670 let capacity = gc_store.gc_heap_capacity();
671 let live_set_size = gc_store.last_post_gc_allocated_bytes.unwrap_or(0);
672 let utilization = live_set_size as f64 / capacity as f64 * 100.0;
673 log::trace!(
674 "============ Begin GC ===========\n\
675 \t GC heap capacity = {capacity:#010x} bytes\n\
676 \tlast post-GC live-set size = {live_set_size:#010x} bytes\n\
677 \t GC heap utilization = {utilization:.02}%",
678 );
679 }
680
681 let mut roots = core::mem::take(&mut self.gc_data.gc_roots_list);
684
685 self.trace_roots(&mut roots, asyncness).await;
686 self.gc_store
687 .as_mut()
688 .unwrap()
689 .gc(
690 asyncness,
691 unsafe { roots.iter() },
692 &self.modules,
693 &self.gc_data.gc_host_alloc_types,
694 yield_now,
699 )
700 .await?;
701
702 roots.clear();
704 self.gc_data.gc_roots_list = roots;
705
706 if log::log_enabled!(log::Level::Trace) {
707 let gc_store = self.gc_store.as_ref().unwrap();
708 let capacity = gc_store.gc_heap_capacity();
709 let live_set_size = gc_store.last_post_gc_allocated_bytes.unwrap_or(0);
710 let utilization = live_set_size as f64 / capacity as f64 * 100.0;
711 log::trace!(
712 "============ End GC ===========\n\
713 \t GC heap capacity = {capacity:#010x} bytes\n\
714 \tpost-GC live-set size = {live_set_size:#010x} bytes\n\
715 \t GC heap utilization = {utilization:.02}%",
716 );
717 }
718 Ok(())
719 }
720
721 async fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList, asyncness: Asyncness) {
722 log::trace!("Begin trace GC roots");
723
724 assert!(gc_roots_list.is_empty());
726
727 self.trace_wasm_stack_roots(gc_roots_list);
728 if asyncness != Asyncness::No {
729 self.yield_now().await;
730 }
731
732 #[cfg(feature = "stack-switching")]
733 {
734 self.trace_wasm_continuation_roots(gc_roots_list);
735 if asyncness != Asyncness::No {
736 self.yield_now().await;
737 }
738 }
739
740 self.trace_vmctx_roots(gc_roots_list);
741 if asyncness != Asyncness::No {
742 self.yield_now().await;
743 }
744
745 self.trace_instance_roots(gc_roots_list);
746 if asyncness != Asyncness::No {
747 self.yield_now().await;
748 }
749
750 self.trace_user_roots(gc_roots_list);
751 if asyncness != Asyncness::No {
752 self.yield_now().await;
753 }
754
755 self.trace_pending_exception_roots(gc_roots_list);
756
757 log::trace!("End trace GC roots")
758 }
759
760 pub(crate) fn trace_wasm_stack_frame(
761 modules: &ModuleRegistry,
762 gc_roots_list: &mut GcRootsList,
763 frame: Frame,
764 ) {
765 let pc = frame.pc();
766 debug_assert!(pc != 0, "we should always get a valid PC for Wasm frames");
767
768 let fp = frame.fp() as *mut usize;
769 debug_assert!(
770 !fp.is_null(),
771 "we should always get a valid frame pointer for Wasm frames"
772 );
773
774 let (store_code, offset) = modules
775 .store_code_by_pc(pc)
776 .expect("should have store code for Wasm frame");
777 let offset = u32::try_from(offset).unwrap();
778
779 let stack_map =
780 wasmtime_environ::StackMap::lookup(offset, store_code.code_memory().stack_map_data());
781
782 if let Some(stack_map) = stack_map {
783 log::trace!(
784 "We have a stack map that maps {} bytes in this Wasm frame",
785 stack_map.frame_size()
786 );
787
788 let sp = unsafe { stack_map.sp(fp) };
789 for stack_slot in unsafe { stack_map.live_gc_refs(sp) } {
790 unsafe {
791 Self::trace_wasm_stack_slot(gc_roots_list, stack_slot);
792 }
793 }
794 }
795
796 #[cfg(feature = "debug")]
797 if let Some(frame_table) = store_code.code_memory().frame_table() {
798 for stack_slot in crate::debug::gc_refs_in_frame(frame_table, offset, fp) {
799 unsafe {
800 Self::trace_wasm_stack_slot(gc_roots_list, stack_slot);
801 }
802 }
803 }
804 }
805
806 unsafe fn trace_wasm_stack_slot(gc_roots_list: &mut GcRootsList, stack_slot: *mut u32) {
807 let raw: u32 = unsafe { core::ptr::read(stack_slot) };
808 log::trace!("Stack slot @ {stack_slot:p} = {raw:#x}");
809
810 let gc_ref = vm::VMGcRef::from_raw_u32(raw);
811 if gc_ref.is_some() {
812 unsafe {
813 gc_roots_list
814 .add_wasm_stack_root(SendSyncPtr::new(NonNull::new(stack_slot).unwrap()));
815 }
816 }
817 }
818
819 fn trace_wasm_stack_roots(&mut self, gc_roots_list: &mut GcRootsList) {
820 log::trace!("Begin trace GC roots :: Wasm stack");
821
822 Backtrace::trace(self, |frame| {
823 Self::trace_wasm_stack_frame(self.modules(), gc_roots_list, frame);
824 core::ops::ControlFlow::Continue(())
825 });
826
827 #[cfg(feature = "component-model-async")]
828 if self.concurrency_support() {
829 let unwind = self.unwinder();
830 let StoreOpaque {
831 modules,
832 store_data,
833 ..
834 } = self;
835 store_data
836 .components
837 .task_state_mut()
838 .concurrent_state_mut()
839 .trace_fiber_roots(modules, unwind, gc_roots_list);
840 }
841
842 log::trace!("End trace GC roots :: Wasm stack");
843 }
844
845 #[cfg(feature = "stack-switching")]
846 fn trace_wasm_continuation_roots(&mut self, gc_roots_list: &mut GcRootsList) {
847 use crate::vm::{VMPayloads, VMStackState, ValRaw};
848
849 unsafe fn trace_payload_roots(gc_roots_list: &mut GcRootsList, payloads: &VMPayloads) {
850 let gc_ref_data = payloads.gc_ref_data;
851 let payloads = &payloads.buffer;
852 assert!(payloads.length <= payloads.capacity);
853 let Some(gc_ref_data) = gc_ref_data else {
854 return;
855 };
856 let gc_ref_data = gc_ref_data.as_ptr();
857 let data = payloads
858 .data
859 .expect("payload GC metadata requires an allocated payload buffer")
860 .as_ptr();
861
862 for index in 0..usize::try_from(payloads.length).unwrap() {
863 let marker = unsafe { gc_ref_data.add(index).read() };
864 if marker == wasmtime_environ::CONTINUATION_PAYLOAD_GC_REF {
865 let slot = unsafe { data.cast::<ValRaw>().add(index).cast::<u32>() };
866 unsafe {
867 StoreOpaque::trace_wasm_stack_slot(gc_roots_list, slot);
868 }
869 }
870 }
871 }
872
873 log::trace!("Begin trace GC roots :: continuations");
874
875 for continuation in &self.continuations {
876 let state = continuation.common_stack_information.state;
877
878 match state {
879 VMStackState::Suspended => {
880 unsafe {
881 trace_payload_roots(gc_roots_list, &continuation.values);
882 }
883 Backtrace::trace_suspended_continuation(self, continuation.deref(), |frame| {
884 Self::trace_wasm_stack_frame(self.modules(), gc_roots_list, frame);
885 core::ops::ControlFlow::Continue(())
886 });
887 }
888 VMStackState::Running => {
889 }
891 VMStackState::Parent => {
892 }
896 VMStackState::Fresh => unsafe {
897 trace_payload_roots(gc_roots_list, &continuation.args);
898 },
899 VMStackState::Returned | VMStackState::Trapped => {
900 }
902 }
903 }
904
905 log::trace!("End trace GC roots :: continuations");
906 }
907
908 fn trace_vmctx_roots(&mut self, gc_roots_list: &mut GcRootsList) {
909 log::trace!("Begin trace GC roots :: vmctx");
910 self.for_each_global(|store, global| global.trace_root(store, gc_roots_list));
911 self.for_each_table(|store, table| table.trace_roots(store, gc_roots_list));
912 log::trace!("End trace GC roots :: vmctx");
913 }
914
915 fn trace_instance_roots(&mut self, gc_roots_list: &mut GcRootsList) {
916 log::trace!("Begin trace GC roots :: instance");
917 for (_id, instance) in &mut self.instances {
918 unsafe {
921 instance
922 .handle
923 .get_mut()
924 .trace_element_segment_roots(gc_roots_list);
925 }
926 }
927 log::trace!("End trace GC roots :: instance");
928 }
929
930 fn trace_user_roots(&mut self, gc_roots_list: &mut GcRootsList) {
931 log::trace!("Begin trace GC roots :: user");
932 self.gc_data.gc_roots.trace_roots(gc_roots_list);
933 log::trace!("End trace GC roots :: user");
934 }
935
936 fn trace_pending_exception_roots(&mut self, gc_roots_list: &mut GcRootsList) {
937 log::trace!("Begin trace GC roots :: pending exception");
938 if let Some(pending_exception) = self.gc_data.pending_exception.as_mut() {
939 unsafe {
940 gc_roots_list.add_vmgcref_root(pending_exception.into(), "Pending exception");
941 }
942 }
943 log::trace!("End trace GC roots :: pending exception");
944 }
945
946 pub(crate) fn insert_gc_host_alloc_type(&mut self, ty: RegisteredType) {
959 assert!(
960 Engine::same(self.engine(), ty.engine()),
961 "type used with wrong engine"
962 );
963 let trace_info = ty.layout().map(TraceInfo::new);
964 self.gc_data
965 .gc_host_alloc_types
966 .insert(ty.index(), (ty, trace_info));
967 }
968
969 #[inline]
977 pub(crate) async fn ensure_gc_store(
978 &mut self,
979 limiter: Option<&mut StoreResourceLimiter<'_>>,
980 ) -> Result<&mut GcStore> {
981 if self.gc_store.is_some() {
982 return Ok(self.gc_store.as_mut().unwrap());
983 }
984 self.allocate_gc_store(limiter).await
985 }
986
987 #[inline(never)]
988 async fn allocate_gc_store(
989 &mut self,
990 limiter: Option<&mut StoreResourceLimiter<'_>>,
991 ) -> Result<&mut GcStore> {
992 log::trace!("allocating GC heap for store {:?}", self.id());
993
994 assert!(self.gc_store.is_none());
995 assert_eq!(
996 self.vm_store_context.gc_heap.get_mut().base.as_non_null(),
997 NonNull::dangling(),
998 );
999 assert_eq!(self.vm_store_context.gc_heap.get_mut().current_length(), 0);
1000
1001 let engine = self.engine();
1002 let mem_ty = engine.tunables().gc_heap_memory_type();
1003 ensure!(
1004 engine.features().gc_types(),
1005 "cannot allocate a GC store when GC is disabled at configuration time"
1006 );
1007 let gc_runtime = engine
1008 .gc_runtime()
1009 .context("no GC runtime: GC disabled at compile time or configuration time")?;
1010
1011 let mut request = InstanceAllocationRequest {
1013 id: InstanceId::reserved_value(),
1014 runtime_info: engine.empty_module_runtime_info(),
1015 imports: vm::Imports::default(),
1016 store: self,
1017 limiter,
1018 };
1019
1020 let (mem_alloc_index, mem) = engine
1021 .allocator()
1022 .allocate_memory(
1023 &mut request,
1024 &mem_ty,
1025 None,
1026 wasmtime_environ::MemoryKind::GcHeap,
1027 )
1028 .await?;
1029
1030 let (index, mut heap) =
1033 match engine
1034 .allocator()
1035 .allocate_gc_heap(engine, &**gc_runtime, mem_alloc_index)
1036 {
1037 Ok(pair) => pair,
1038 Err(e) => unsafe {
1039 engine
1040 .allocator()
1041 .deallocate_memory(None, mem_alloc_index, mem);
1042 return Err(e);
1043 },
1044 };
1045 heap.attach(mem);
1046
1047 let gc_store = GcStore::new(index, heap, engine.tunables().gc_zeal_alloc_counter);
1048 *self.vm_store_context.gc_heap.get_mut() = gc_store.vmmemory_definition();
1049 Ok(self.gc_store.insert(gc_store))
1050 }
1051
1052 pub fn has_pending_exception(&self) -> bool {
1054 self.gc_data.pending_exception.is_some()
1055 }
1056
1057 #[inline]
1058 pub(crate) fn gc_roots(&self) -> &RootSet {
1059 &self.gc_data.gc_roots
1060 }
1061
1062 #[inline]
1063 pub(crate) fn gc_roots_mut(&mut self) -> &mut RootSet {
1064 &mut self.gc_data.gc_roots
1065 }
1066
1067 #[inline]
1068 pub(crate) fn enter_gc_lifo_scope(&self) -> usize {
1069 self.gc_data.gc_roots.enter_lifo_scope()
1070 }
1071
1072 #[inline]
1073 pub(crate) fn exit_gc_lifo_scope(&mut self, scope: usize) {
1074 self.gc_data
1075 .gc_roots
1076 .exit_lifo_scope(self.gc_store.as_mut(), scope);
1077 }
1078
1079 pub(crate) fn is_subtype_cached(
1086 &mut self,
1087 sub: wasmtime_environ::VMSharedTypeIndex,
1088 sup: wasmtime_environ::VMSharedTypeIndex,
1089 ) -> bool {
1090 const MAX_SIZE: usize = 1 << 16; let key = (u64::from(sub.as_u32()) << 32) | u64::from(sup.as_u32());
1093 let engine_answer = || self.engine.signatures().is_subtype(sub, sup);
1094 if self.gc_data.subtype_check_cache.len() < MAX_SIZE {
1095 *self
1096 .gc_data
1097 .subtype_check_cache
1098 .entry(key)
1099 .or_insert_with(engine_answer)
1100 } else {
1101 self.gc_data
1102 .subtype_check_cache
1103 .get(&key)
1104 .copied()
1105 .unwrap_or_else(engine_answer)
1106 }
1107 }
1108}
1109
1110struct WithoutGcZealAllocCounter<'a> {
1112 store: &'a mut StoreOpaque,
1113 counter: Option<NonZeroU32>,
1114}
1115
1116impl Deref for WithoutGcZealAllocCounter<'_> {
1117 type Target = StoreOpaque;
1118
1119 fn deref(&self) -> &Self::Target {
1120 &self.store
1121 }
1122}
1123
1124impl DerefMut for WithoutGcZealAllocCounter<'_> {
1125 fn deref_mut(&mut self) -> &mut Self::Target {
1126 &mut self.store
1127 }
1128}
1129
1130impl Drop for WithoutGcZealAllocCounter<'_> {
1131 fn drop(&mut self) {
1132 self.store.replace_gc_zeal_alloc_counter(self.counter);
1133 }
1134}
1135
1136impl<'a> WithoutGcZealAllocCounter<'a> {
1137 pub fn new(store: &'a mut StoreOpaque) -> Self {
1138 let counter = store.replace_gc_zeal_alloc_counter(None);
1139 WithoutGcZealAllocCounter { store, counter }
1140 }
1141}
1142
1143#[track_caller]
1152fn should_collect_first(
1153 bytes_needed: u64,
1154 gc_heap_capacity: usize,
1155 last_gc_heap_usage: usize,
1156) -> bool {
1157 debug_assert!(last_gc_heap_usage <= gc_heap_capacity);
1158
1159 if gc_heap_capacity == 0 {
1166 return false;
1167 }
1168
1169 if bytes_needed == 0 {
1172 return true;
1173 }
1174
1175 let Ok(bytes_needed) = usize::try_from(bytes_needed) else {
1176 return false;
1179 };
1180
1181 if bytes_needed > isize::MAX.cast_unsigned() {
1182 return false;
1186 }
1187
1188 let Some(predicted_usage) = last_gc_heap_usage.checked_add(bytes_needed) else {
1189 return true;
1193 };
1194
1195 predicted_usage < gc_heap_capacity / 2
1200}
1201
1202#[cfg(test)]
1203mod tests {
1204 use super::should_collect_first;
1205 use crate::{AsContextMut, Config, Engine, ExternRef, Result, Store};
1206
1207 #[test]
1208 fn test_should_collect_first() {
1209 for bytes_needed in 0..256 {
1211 assert_eq!(should_collect_first(bytes_needed, 0, 0), false);
1212 }
1213
1214 for cap in 1..256 {
1216 for usage in 0..=cap {
1217 assert_eq!(should_collect_first(0, cap, usage), true);
1218 }
1219 }
1220
1221 let max_alloc_usize = isize::MAX.cast_unsigned();
1222 let max_alloc_u64 = u64::try_from(max_alloc_usize).unwrap();
1223
1224 assert_eq!(
1227 should_collect_first(max_alloc_u64 + 1, max_alloc_usize, 0),
1228 false,
1229 );
1230
1231 assert_eq!(should_collect_first(1, usize::MAX, usize::MAX), true);
1233
1234 assert_eq!(should_collect_first(16, 1024, 64), true);
1237
1238 assert_eq!(should_collect_first(16, 1024, 512), false);
1242 }
1243
1244 #[test]
1245 fn gc_heap_initial_size() -> Result<()> {
1246 let mut config = Config::new();
1247 config.gc_heap_initial_size(1 << 20);
1248 let engine = Engine::new(&config)?;
1249 let mut store = Store::new(&engine, ());
1250 ExternRef::new(&mut store, 1)?;
1251
1252 let gc_store = store.as_context_mut().0.unwrap_gc_store();
1253 assert_eq!(gc_store.gc_heap_capacity(), 1 << 20);
1254 Ok(())
1255 }
1256}