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