1use crate::store::{
4 Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter, yield_now,
5};
6use crate::type_registry::RegisteredType;
7use crate::vm::{self, Backtrace, Frame, GcRootsList, GcStore, SendSyncPtr, VMGcRef};
8use crate::{
9 ExnRef, GcHeapOutOfMemory, Result, Rooted, Store, StoreContextMut, ThrownException, bail,
10 format_err,
11};
12use core::mem::ManuallyDrop;
13use core::num::NonZeroU32;
14use core::ops::{Deref, DerefMut};
15use core::ptr::NonNull;
16use wasmtime_environ::DefinedTagIndex;
17
18impl<T> Store<T> {
19 pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> {
37 StoreContextMut(&mut self.inner).gc(why)
38 }
39
40 pub fn gc_heap_capacity(&self) -> usize {
43 self.inner.gc_heap_capacity()
44 }
45
46 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
78 self.inner.throw_impl(exception)
79 }
80
81 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
98 self.inner.take_pending_exception_rooted()
99 }
100}
101
102impl<'a, T> StoreContextMut<'a, T> {
103 pub fn gc(&mut self, why: Option<&GcHeapOutOfMemory<()>>) -> Result<()> {
107 let (mut limiter, store) = self.0.validate_sync_resource_limiter_and_store_opaque()?;
108 vm::assert_ready(store.gc(
109 limiter.as_mut(),
110 None,
111 why.map(|e| e.bytes_needed()),
112 Asyncness::No,
113 ));
114 Ok(())
115 }
116
117 #[cfg(feature = "gc")]
122 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
123 self.0.inner.throw_impl(exception)
124 }
125
126 #[cfg(feature = "gc")]
131 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
132 self.0.inner.take_pending_exception_rooted()
133 }
134}
135
136impl StoreOpaque {
137 pub(crate) async fn gc(
148 &mut self,
149 limiter: Option<&mut StoreResourceLimiter<'_>>,
150 root: Option<VMGcRef>,
151 bytes_needed: Option<u64>,
152 asyncness: Asyncness,
153 ) -> Option<VMGcRef> {
154 let mut scope = crate::OpaqueRootScope::new(self);
155 scope.trim_gc_liveness_flags(true);
156 let store_id = scope.id();
157 let root = root.map(|r| scope.gc_roots_mut().push_lifo_root(store_id, r));
158
159 scope
160 .collect_and_maybe_grow_gc_heap(limiter, bytes_needed, asyncness)
161 .await;
162
163 root.map(|r| {
164 let r = r
165 .get_gc_ref(&scope)
166 .expect("still in scope")
167 .unchecked_copy();
168 scope.clone_gc_ref(&r)
169 })
170 }
171
172 pub(crate) fn trim_gc_liveness_flags(&mut self, eager: bool) {
177 if let Some(gc_store) = self.gc_store.as_mut() {
178 self.gc_roots.trim_liveness_flags(gc_store, eager);
179 }
180 }
181
182 async fn collect_and_maybe_grow_gc_heap(
185 &mut self,
186 limiter: Option<&mut StoreResourceLimiter<'_>>,
187 bytes_needed: Option<u64>,
188 asyncness: Asyncness,
189 ) {
190 log::trace!("collect_and_maybe_grow_gc_heap(bytes_needed = {bytes_needed:#x?})");
191 self.do_gc(asyncness).await;
192 if let Some(n) = bytes_needed
193 && n > u64::try_from(self.gc_heap_capacity())
194 .unwrap()
195 .saturating_sub(self.gc_store.as_ref().map_or(0, |gc| {
196 u64::try_from(gc.last_post_gc_allocated_bytes.unwrap_or(0)).unwrap()
197 }))
198 {
199 let _ = self.grow_gc_heap(limiter, n, asyncness).await;
200 }
201 }
202
203 pub(crate) async fn grow_gc_heap(
207 &mut self,
208 limiter: Option<&mut StoreResourceLimiter<'_>>,
209 bytes_needed: u64,
210 asyncness: Asyncness,
211 ) -> Result<()> {
212 log::trace!("Attempting to grow the GC heap by at least {bytes_needed:#x} bytes");
213
214 if bytes_needed == 0 {
215 return Ok(());
216 }
217
218 if self
221 .gc_store
222 .as_ref()
223 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth())
224 {
225 self.do_gc(asyncness).await;
226 debug_assert!(
227 !self
228 .gc_store
229 .as_ref()
230 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth()),
231 "needs_gc_before_next_growth should return false after a GC"
232 );
233 }
234
235 let page_size = self.engine().tunables().gc_heap_memory_type().page_size();
236
237 let mut heap = TakenGcHeap::new(self);
240
241 let current_size_in_bytes = u64::try_from(heap.memory.byte_size()).unwrap();
242 let current_size_in_pages = current_size_in_bytes / page_size;
243
244 let doubled_size_in_pages = current_size_in_pages.saturating_mul(2);
246 assert!(doubled_size_in_pages >= current_size_in_pages);
247 let delta_pages_for_doubling = doubled_size_in_pages - current_size_in_pages;
248
249 let max_size_in_bytes = 1 << 32;
255 let max_size_in_pages = max_size_in_bytes / page_size;
256 let delta_to_max_size_in_pages = max_size_in_pages - current_size_in_pages;
257 let delta_pages_for_alloc = delta_pages_for_doubling.min(delta_to_max_size_in_pages);
258
259 let pages_needed = bytes_needed.div_ceil(page_size);
266 assert!(pages_needed > 0);
267 let delta_pages_for_alloc = delta_pages_for_alloc.max(pages_needed);
268 assert!(delta_pages_for_alloc > 0);
269
270 unsafe {
274 heap.memory
275 .grow(delta_pages_for_alloc, limiter)
276 .await?
277 .ok_or_else(|| format_err!("failed to grow GC heap"))?;
278 }
279 heap.store.vm_store_context.gc_heap = heap.memory.vmmemory();
280
281 let new_size_in_bytes = u64::try_from(heap.memory.byte_size()).unwrap();
282 assert!(new_size_in_bytes > current_size_in_bytes);
283 heap.delta_bytes_grown = new_size_in_bytes - current_size_in_bytes;
284 let delta_bytes_for_alloc = delta_pages_for_alloc.checked_mul(page_size).unwrap();
285 assert!(
286 heap.delta_bytes_grown >= delta_bytes_for_alloc,
287 "{} should be greater than or equal to {delta_bytes_for_alloc}",
288 heap.delta_bytes_grown,
289 );
290 log::trace!(
291 " -> grew GC heap by {:#x} bytes: new size is {new_size_in_bytes:#x} bytes",
292 heap.delta_bytes_grown
293 );
294 return Ok(());
295
296 struct TakenGcHeap<'a> {
297 store: &'a mut StoreOpaque,
298 memory: ManuallyDrop<vm::Memory>,
299 delta_bytes_grown: u64,
300 }
301
302 impl<'a> TakenGcHeap<'a> {
303 fn new(store: &'a mut StoreOpaque) -> TakenGcHeap<'a> {
304 TakenGcHeap {
305 memory: ManuallyDrop::new(store.unwrap_gc_store_mut().gc_heap.take_memory()),
306 store,
307 delta_bytes_grown: 0,
308 }
309 }
310 }
311
312 impl Drop for TakenGcHeap<'_> {
313 fn drop(&mut self) {
314 unsafe {
320 self.store.unwrap_gc_store_mut().gc_heap.replace_memory(
321 ManuallyDrop::take(&mut self.memory),
322 self.delta_bytes_grown,
323 );
324 }
325 }
326 }
327 }
328
329 fn replace_gc_zeal_alloc_counter(
330 &mut self,
331 new_value: Option<NonZeroU32>,
332 ) -> Option<NonZeroU32> {
333 if let Some(gc_store) = &mut self.gc_store {
334 gc_store.replace_gc_zeal_alloc_counter(new_value)
335 } else {
336 None
337 }
338 }
339
340 pub(crate) async fn retry_after_gc_async<T, U>(
349 &mut self,
350 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
351 value: T,
352 asyncness: Asyncness,
353 alloc_func: impl Fn(&mut Self, T) -> Result<U>,
354 ) -> Result<U>
355 where
356 T: Send + Sync + 'static,
357 {
358 self.ensure_gc_store(limiter.as_deref_mut()).await?;
359
360 match alloc_func(self, value) {
361 Ok(x) => Ok(x),
362 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
363 Ok(oom) => {
364 log::trace!("Got GC heap OOM: {oom}");
365
366 let (value, oom) = oom.take_inner();
367 let bytes_needed = oom.bytes_needed();
368
369 let mut store = WithoutGcZealAllocCounter::new(self);
370
371 let gc_heap_capacity = store
372 .gc_store
373 .as_ref()
374 .map_or(0, |gc_store| gc_store.gc_heap_capacity());
375 let last_gc_heap_usage = store.gc_store.as_ref().map_or(0, |gc_store| {
376 gc_store.last_post_gc_allocated_bytes.unwrap_or(0)
377 });
378
379 if should_collect_first(bytes_needed, gc_heap_capacity, last_gc_heap_usage) {
380 log::trace!(
381 "Collecting first, then retrying; growing GC heap if collecting didn't \
382 free up enough space, then retrying again"
383 );
384 store
385 .gc(limiter.as_deref_mut(), None, None, asyncness)
386 .await;
387
388 match alloc_func(&mut store, value) {
389 Ok(x) => Ok(x),
390 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
391 Ok(oom2) => {
392 let (value, _) = oom2.take_inner();
395 let _ =
399 store.grow_gc_heap(limiter, bytes_needed, asyncness).await;
400
401 alloc_func(&mut store, value)
402 }
403 Err(e) => Err(e),
404 },
405 }
406 } else {
407 log::trace!(
408 "Grow GC heap first, collecting if growth failed, then retrying"
409 );
410
411 if let Err(e) = store
412 .grow_gc_heap(limiter.as_deref_mut(), bytes_needed.max(1), asyncness)
413 .await
414 {
415 log::trace!("growing GC heap failed: {e}");
416 store.gc(limiter, None, None, asyncness).await;
417 }
418
419 alloc_func(&mut store, value)
420 }
421 }
422 Err(e) => Err(e),
423 },
424 }
425 }
426
427 pub(crate) fn set_pending_exception(&mut self, exnref: &VMGcRef) -> ThrownException {
435 debug_assert!(exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
436 let gc_store = self.gc_store.as_mut().unwrap();
437 gc_store.write_gc_ref(&mut self.pending_exception, Some(exnref));
438 ThrownException
439 }
440
441 pub(crate) fn expose_pending_exception_to_wasm(&mut self) -> Option<NonZeroU32> {
444 let exnref = self.pending_exception.take()?;
445 let gc_store = self.unwrap_gc_store_mut();
446 debug_assert!(exnref.is_exnref(&*gc_store.gc_heap));
447 Some(gc_store.expose_gc_ref_to_wasm(exnref))
448 }
449
450 fn take_pending_exception_rooted(&mut self) -> Option<Rooted<ExnRef>> {
453 let vmexnref = self.pending_exception.take()?;
454 debug_assert!(vmexnref.is_exnref(&*self.unwrap_gc_store().gc_heap));
455 let mut nogc = AutoAssertNoGc::new(self);
456 Some(Rooted::new(&mut nogc, vmexnref))
457 }
458
459 pub(crate) fn pending_exception_tag_and_instance(
462 &mut self,
463 ) -> Option<(InstanceId, DefinedTagIndex)> {
464 let pending_exnref = self.pending_exception.as_ref()?.unchecked_copy();
465 debug_assert!(pending_exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
466 let mut store = AutoAssertNoGc::new(self);
467 Some(
468 pending_exnref
469 .into_exnref_unchecked()
470 .tag(&mut store)
471 .expect("cannot read tag"),
472 )
473 }
474
475 #[cfg(feature = "debug")]
478 pub(crate) fn pending_exception_owned_rooted(
479 &mut self,
480 ) -> Result<Option<crate::OwnedRooted<ExnRef>>, crate::OutOfMemory> {
481 let pending = match &self.pending_exception {
482 Some(r) => r,
483 None => return Ok(None),
484 };
485 let cloned = self.gc_store.as_mut().unwrap().clone_gc_ref(pending);
486 let mut nogc = AutoAssertNoGc::new(self);
487 Ok(Some(crate::OwnedRooted::new(&mut nogc, cloned)?))
488 }
489
490 fn throw_impl<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
495 let exception = exception.try_gc_ref(self)?.unchecked_copy();
496 Err(self.set_pending_exception(&exception).into())
497 }
498
499 #[inline]
513 pub(crate) fn require_gc_store(&self) -> Result<&GcStore> {
514 match &self.gc_store {
515 Some(gc_store) => Ok(gc_store),
516 None => bail!("GC heap not initialized yet"),
517 }
518 }
519
520 #[inline]
522 pub(crate) fn require_gc_store_mut(&mut self) -> Result<&mut GcStore> {
523 match &mut self.gc_store {
524 Some(gc_store) => Ok(gc_store),
525 None => bail!("GC heap not initialized yet"),
526 }
527 }
528
529 pub(crate) fn gc_heap_capacity(&self) -> usize {
532 match self.gc_store.as_ref() {
533 Some(gc_store) => gc_store.gc_heap_capacity(),
534 None => 0,
535 }
536 }
537
538 async fn do_gc(&mut self, asyncness: Asyncness) {
539 if self.gc_store.is_none() {
541 return;
542 }
543
544 log::trace!("============ Begin GC ===========");
545
546 let mut roots = core::mem::take(&mut self.gc_roots_list);
549
550 self.trace_roots(&mut roots, asyncness).await;
551 self.unwrap_gc_store_mut()
552 .gc(
553 asyncness,
554 unsafe { roots.iter() },
555 yield_now,
560 )
561 .await;
562
563 roots.clear();
565 self.gc_roots_list = roots;
566
567 log::trace!("============ End GC ===========");
568 }
569
570 async fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList, asyncness: Asyncness) {
571 log::trace!("Begin trace GC roots");
572
573 assert!(gc_roots_list.is_empty());
575
576 self.trace_wasm_stack_roots(gc_roots_list);
577 if asyncness != Asyncness::No {
578 self.yield_now().await;
579 }
580
581 #[cfg(feature = "stack-switching")]
582 {
583 self.trace_wasm_continuation_roots(gc_roots_list);
584 if asyncness != Asyncness::No {
585 self.yield_now().await;
586 }
587 }
588
589 self.trace_vmctx_roots(gc_roots_list);
590 if asyncness != Asyncness::No {
591 self.yield_now().await;
592 }
593
594 self.trace_instance_roots(gc_roots_list);
595 if asyncness != Asyncness::No {
596 self.yield_now().await;
597 }
598
599 self.trace_user_roots(gc_roots_list);
600 if asyncness != Asyncness::No {
601 self.yield_now().await;
602 }
603
604 self.trace_pending_exception_roots(gc_roots_list);
605
606 log::trace!("End trace GC roots")
607 }
608
609 fn trace_wasm_stack_frame(&self, gc_roots_list: &mut GcRootsList, frame: Frame) {
610 let pc = frame.pc();
611 debug_assert!(pc != 0, "we should always get a valid PC for Wasm frames");
612
613 let fp = frame.fp() as *mut usize;
614 debug_assert!(
615 !fp.is_null(),
616 "we should always get a valid frame pointer for Wasm frames"
617 );
618
619 let (module_with_code, _offset) = self
620 .modules()
621 .module_and_code_by_pc(pc)
622 .expect("should have module info for Wasm frame");
623
624 if let Some(stack_map) = module_with_code.lookup_stack_map(pc) {
625 log::trace!(
626 "We have a stack map that maps {} bytes in this Wasm frame",
627 stack_map.frame_size()
628 );
629
630 let sp = unsafe { stack_map.sp(fp) };
631 for stack_slot in unsafe { stack_map.live_gc_refs(sp) } {
632 unsafe {
633 self.trace_wasm_stack_slot(gc_roots_list, stack_slot);
634 }
635 }
636 }
637
638 #[cfg(feature = "debug")]
639 if let Some(frame_table) = module_with_code.module().frame_table() {
640 let relpc = module_with_code
641 .text_offset(pc)
642 .expect("PC should be within module");
643 for stack_slot in crate::debug::gc_refs_in_frame(frame_table, relpc, fp) {
644 unsafe {
645 self.trace_wasm_stack_slot(gc_roots_list, stack_slot);
646 }
647 }
648 }
649 }
650
651 unsafe fn trace_wasm_stack_slot(&self, gc_roots_list: &mut GcRootsList, stack_slot: *mut u32) {
652 let raw: u32 = unsafe { core::ptr::read(stack_slot) };
653 log::trace!("Stack slot @ {stack_slot:p} = {raw:#x}");
654
655 let gc_ref = vm::VMGcRef::from_raw_u32(raw);
656 if gc_ref.is_some() {
657 unsafe {
658 gc_roots_list
659 .add_wasm_stack_root(SendSyncPtr::new(NonNull::new(stack_slot).unwrap()));
660 }
661 }
662 }
663
664 fn trace_wasm_stack_roots(&mut self, gc_roots_list: &mut GcRootsList) {
665 log::trace!("Begin trace GC roots :: Wasm stack");
666
667 Backtrace::trace(self, |frame| {
668 self.trace_wasm_stack_frame(gc_roots_list, frame);
669 core::ops::ControlFlow::Continue(())
670 });
671
672 log::trace!("End trace GC roots :: Wasm stack");
673 }
674
675 #[cfg(feature = "stack-switching")]
676 fn trace_wasm_continuation_roots(&mut self, gc_roots_list: &mut GcRootsList) {
677 use crate::vm::VMStackState;
678
679 log::trace!("Begin trace GC roots :: continuations");
680
681 for continuation in &self.continuations {
682 let state = continuation.common_stack_information.state;
683
684 match state {
692 VMStackState::Suspended => {
693 Backtrace::trace_suspended_continuation(self, continuation.deref(), |frame| {
694 self.trace_wasm_stack_frame(gc_roots_list, frame);
695 core::ops::ControlFlow::Continue(())
696 });
697 }
698 VMStackState::Running => {
699 }
701 VMStackState::Parent => {
702 }
706 VMStackState::Fresh | VMStackState::Returned => {
707 }
709 }
710 }
711
712 log::trace!("End trace GC roots :: continuations");
713 }
714
715 fn trace_vmctx_roots(&mut self, gc_roots_list: &mut GcRootsList) {
716 log::trace!("Begin trace GC roots :: vmctx");
717 self.for_each_global(|store, global| global.trace_root(store, gc_roots_list));
718 self.for_each_table(|store, table| table.trace_roots(store, gc_roots_list));
719 log::trace!("End trace GC roots :: vmctx");
720 }
721
722 fn trace_instance_roots(&mut self, gc_roots_list: &mut GcRootsList) {
723 log::trace!("Begin trace GC roots :: instance");
724 for (_id, instance) in &mut self.instances {
725 unsafe {
728 instance
729 .handle
730 .get_mut()
731 .trace_element_segment_roots(gc_roots_list);
732 }
733 }
734 log::trace!("End trace GC roots :: instance");
735 }
736
737 fn trace_user_roots(&mut self, gc_roots_list: &mut GcRootsList) {
738 log::trace!("Begin trace GC roots :: user");
739 self.gc_roots.trace_roots(gc_roots_list);
740 log::trace!("End trace GC roots :: user");
741 }
742
743 fn trace_pending_exception_roots(&mut self, gc_roots_list: &mut GcRootsList) {
744 log::trace!("Begin trace GC roots :: pending exception");
745 if let Some(pending_exception) = self.pending_exception.as_mut() {
746 unsafe {
747 gc_roots_list.add_vmgcref_root(pending_exception.into(), "Pending exception");
748 }
749 }
750 log::trace!("End trace GC roots :: pending exception");
751 }
752
753 pub(crate) fn insert_gc_host_alloc_type(&mut self, ty: RegisteredType) {
760 if let Some(gc_store) = self.optional_gc_store_mut() {
764 gc_store.ensure_trace_info(ty.index());
765 }
766 self.gc_host_alloc_types.insert(ty);
767 }
768}
769
770struct WithoutGcZealAllocCounter<'a> {
772 store: &'a mut StoreOpaque,
773 counter: Option<NonZeroU32>,
774}
775
776impl Deref for WithoutGcZealAllocCounter<'_> {
777 type Target = StoreOpaque;
778
779 fn deref(&self) -> &Self::Target {
780 &self.store
781 }
782}
783
784impl DerefMut for WithoutGcZealAllocCounter<'_> {
785 fn deref_mut(&mut self) -> &mut Self::Target {
786 &mut self.store
787 }
788}
789
790impl Drop for WithoutGcZealAllocCounter<'_> {
791 fn drop(&mut self) {
792 self.store.replace_gc_zeal_alloc_counter(self.counter);
793 }
794}
795
796impl<'a> WithoutGcZealAllocCounter<'a> {
797 pub fn new(store: &'a mut StoreOpaque) -> Self {
798 let counter = store.replace_gc_zeal_alloc_counter(None);
799 WithoutGcZealAllocCounter { store, counter }
800 }
801}
802
803#[track_caller]
812fn should_collect_first(
813 bytes_needed: u64,
814 gc_heap_capacity: usize,
815 last_gc_heap_usage: usize,
816) -> bool {
817 debug_assert!(last_gc_heap_usage <= gc_heap_capacity);
818
819 if gc_heap_capacity == 0 {
826 return false;
827 }
828
829 if bytes_needed == 0 {
832 return true;
833 }
834
835 let Ok(bytes_needed) = usize::try_from(bytes_needed) else {
836 return false;
839 };
840
841 if bytes_needed > isize::MAX.cast_unsigned() {
842 return false;
846 }
847
848 let Some(predicted_usage) = last_gc_heap_usage.checked_add(bytes_needed) else {
849 return true;
853 };
854
855 predicted_usage < gc_heap_capacity / 2
860}
861
862#[cfg(test)]
863mod tests {
864 use super::should_collect_first;
865
866 #[test]
867 fn test_should_collect_first() {
868 for bytes_needed in 0..256 {
870 assert_eq!(should_collect_first(bytes_needed, 0, 0), false);
871 }
872
873 for cap in 1..256 {
875 for usage in 0..=cap {
876 assert_eq!(should_collect_first(0, cap, usage), true);
877 }
878 }
879
880 let max_alloc_usize = isize::MAX.cast_unsigned();
881 let max_alloc_u64 = u64::try_from(max_alloc_usize).unwrap();
882
883 assert_eq!(
886 should_collect_first(max_alloc_u64 + 1, max_alloc_usize, 0),
887 false,
888 );
889
890 assert_eq!(should_collect_first(1, usize::MAX, usize::MAX), true);
892
893 assert_eq!(should_collect_first(16, 1024, 64), true);
896
897 assert_eq!(should_collect_first(16, 1024, 512), false);
901 }
902}