1use crate::error::Context;
4use crate::store::{
5 Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter, yield_now,
6};
7use crate::type_registry::RegisteredType;
8use crate::vm::{self, Backtrace, Frame, GcRootsList, GcStore, SendSyncPtr, VMGcRef};
9use crate::{
10 ExnRef, GcHeapOutOfMemory, Result, Rooted, Store, StoreContextMut, ThrownException, bail,
11};
12use core::fmt;
13use core::mem::ManuallyDrop;
14use core::num::NonZeroU32;
15use core::ops::{Deref, DerefMut};
16use core::ptr::NonNull;
17use wasmtime_environ::DefinedTagIndex;
18
19impl<T> Store<T> {
20 pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> {
38 StoreContextMut(&mut self.inner).gc(why)
39 }
40
41 pub fn gc_heap_capacity(&self) -> usize {
44 self.inner.gc_heap_capacity()
45 }
46
47 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
79 self.inner.throw_impl(exception)
80 }
81
82 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
99 self.inner.take_pending_exception_rooted()
100 }
101}
102
103impl<'a, T> StoreContextMut<'a, T> {
104 pub fn gc(&mut self, why: Option<&GcHeapOutOfMemory<()>>) -> Result<()> {
108 let (mut limiter, store) = self.0.validate_sync_resource_limiter_and_store_opaque()?;
109 vm::assert_ready(store.gc(
110 limiter.as_mut(),
111 None,
112 why.map(|e| e.bytes_needed()),
113 Asyncness::No,
114 ))?;
115 Ok(())
116 }
117
118 #[cfg(feature = "gc")]
123 pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
124 self.0.inner.throw_impl(exception)
125 }
126
127 #[cfg(feature = "gc")]
132 pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
133 self.0.inner.take_pending_exception_rooted()
134 }
135}
136
137#[derive(Debug)]
138struct GcHeapGrowthFailed;
139
140impl fmt::Display for GcHeapGrowthFailed {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.write_str("GC heap growth failed")
143 }
144}
145
146impl core::error::Error for GcHeapGrowthFailed {}
147
148impl StoreOpaque {
149 pub(crate) async fn gc(
160 &mut self,
161 limiter: Option<&mut StoreResourceLimiter<'_>>,
162 root: Option<VMGcRef>,
163 bytes_needed: Option<u64>,
164 asyncness: Asyncness,
165 ) -> Result<Option<VMGcRef>> {
166 let mut scope = crate::OpaqueRootScope::new(self);
167 scope.trim_gc_liveness_flags(true);
168 let store_id = scope.id();
169 let root = root.map(|r| scope.gc_roots_mut().push_lifo_root(store_id, r));
170
171 scope
172 .collect_and_maybe_grow_gc_heap(limiter, bytes_needed, asyncness)
173 .await?;
174
175 Ok(root.map(|r| {
176 let r = r
177 .get_gc_ref(&scope)
178 .expect("still in scope")
179 .unchecked_copy();
180 scope.clone_gc_ref(&r)
181 }))
182 }
183
184 pub(crate) fn trim_gc_liveness_flags(&mut self, eager: bool) {
189 if let Some(gc_store) = self.gc_store.as_mut() {
190 self.gc_roots.trim_liveness_flags(gc_store, eager);
191 }
192 }
193
194 async fn collect_and_maybe_grow_gc_heap(
197 &mut self,
198 limiter: Option<&mut StoreResourceLimiter<'_>>,
199 bytes_needed: Option<u64>,
200 asyncness: Asyncness,
201 ) -> Result<()> {
202 log::trace!("collect_and_maybe_grow_gc_heap(bytes_needed = {bytes_needed:#x?})");
203 self.do_gc(asyncness).await?;
204 if let Some(n) = bytes_needed
205 && n > u64::try_from(self.gc_heap_capacity())?.saturating_sub(
206 self.gc_store.as_ref().map_or(0, |gc| {
207 u64::try_from(gc.last_post_gc_allocated_bytes.unwrap_or(0)).unwrap()
208 }),
209 )
210 {
211 if let Err(e) = self.grow_gc_heap(limiter, n, asyncness).await {
212 if e.is::<GcHeapGrowthFailed>() {
213 log::trace!("ignoring GC heap growth failure: {e}");
214 } else {
215 return Err(e);
216 }
217 }
218 }
219 Ok(())
220 }
221
222 pub(crate) async fn grow_gc_heap(
226 &mut self,
227 limiter: Option<&mut StoreResourceLimiter<'_>>,
228 bytes_needed: u64,
229 asyncness: Asyncness,
230 ) -> Result<()> {
231 log::trace!("Attempting to grow the GC heap by at least {bytes_needed:#x} bytes");
232
233 if bytes_needed == 0 {
234 return Ok(());
235 }
236
237 if self
240 .gc_store
241 .as_ref()
242 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth())
243 {
244 self.do_gc(asyncness).await?;
245 debug_assert!(
246 !self
247 .gc_store
248 .as_ref()
249 .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth()),
250 "needs_gc_before_next_growth should return false after a GC"
251 );
252 }
253
254 let page_size = self.engine().tunables().gc_heap_memory_type().page_size();
255
256 let mut heap = TakenGcHeap::new(self);
259
260 let current_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
261 let current_size_in_pages = current_size_in_bytes / page_size;
262
263 let doubled_size_in_pages = current_size_in_pages.saturating_mul(2);
265 assert!(doubled_size_in_pages >= current_size_in_pages);
266 let delta_pages_for_doubling = doubled_size_in_pages - current_size_in_pages;
267
268 let max_size_in_bytes = 1 << 32;
274 let max_size_in_pages = max_size_in_bytes / page_size;
275 let delta_to_max_size_in_pages = max_size_in_pages - current_size_in_pages;
276 let delta_pages_for_alloc = delta_pages_for_doubling.min(delta_to_max_size_in_pages);
277
278 let pages_needed = bytes_needed.div_ceil(page_size);
285 assert!(pages_needed > 0);
286 let delta_pages_for_alloc = delta_pages_for_alloc.max(pages_needed);
287 assert!(delta_pages_for_alloc > 0);
288
289 unsafe {
293 heap.memory
294 .grow(delta_pages_for_alloc, limiter)
295 .await
296 .context(GcHeapGrowthFailed)?
297 .ok_or(GcHeapGrowthFailed)?;
298 }
299 *heap.store.vm_store_context.gc_heap.get_mut() = heap.memory.vmmemory();
300
301 let new_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
302 assert!(new_size_in_bytes > current_size_in_bytes);
303 heap.delta_bytes_grown = new_size_in_bytes - current_size_in_bytes;
304 let delta_bytes_for_alloc = delta_pages_for_alloc.checked_mul(page_size).unwrap();
305 assert!(
306 heap.delta_bytes_grown >= delta_bytes_for_alloc,
307 "{} should be greater than or equal to {delta_bytes_for_alloc}",
308 heap.delta_bytes_grown,
309 );
310 log::trace!(
311 " -> grew GC heap by {:#x} bytes: new size is {new_size_in_bytes:#x} bytes",
312 heap.delta_bytes_grown
313 );
314 return Ok(());
315
316 struct TakenGcHeap<'a> {
317 store: &'a mut StoreOpaque,
318 memory: ManuallyDrop<vm::Memory>,
319 delta_bytes_grown: u64,
320 }
321
322 impl<'a> TakenGcHeap<'a> {
323 fn new(store: &'a mut StoreOpaque) -> TakenGcHeap<'a> {
324 TakenGcHeap {
325 memory: ManuallyDrop::new(store.unwrap_gc_store_mut().gc_heap.take_memory()),
326 store,
327 delta_bytes_grown: 0,
328 }
329 }
330 }
331
332 impl Drop for TakenGcHeap<'_> {
333 fn drop(&mut self) {
334 unsafe {
340 self.store.unwrap_gc_store_mut().gc_heap.replace_memory(
341 ManuallyDrop::take(&mut self.memory),
342 self.delta_bytes_grown,
343 );
344 }
345 }
346 }
347 }
348
349 fn replace_gc_zeal_alloc_counter(
350 &mut self,
351 new_value: Option<NonZeroU32>,
352 ) -> Option<NonZeroU32> {
353 if let Some(gc_store) = &mut self.gc_store {
354 gc_store.replace_gc_zeal_alloc_counter(new_value)
355 } else {
356 None
357 }
358 }
359
360 pub(crate) async fn retry_after_gc_async<T, U>(
369 &mut self,
370 mut limiter: Option<&mut StoreResourceLimiter<'_>>,
371 value: T,
372 asyncness: Asyncness,
373 alloc_func: impl Fn(&mut Self, T) -> Result<U>,
374 ) -> Result<U>
375 where
376 T: Send + Sync + 'static,
377 {
378 self.ensure_gc_store(limiter.as_deref_mut()).await?;
379
380 match alloc_func(self, value) {
381 Ok(x) => Ok(x),
382 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
383 Ok(oom) => {
384 log::trace!("Got GC heap OOM: {oom}");
385
386 let (value, oom) = oom.take_inner();
387 let bytes_needed = oom.bytes_needed();
388
389 let mut store = WithoutGcZealAllocCounter::new(self);
390
391 let gc_heap_capacity = store
392 .gc_store
393 .as_ref()
394 .map_or(0, |gc_store| gc_store.gc_heap_capacity());
395 let last_gc_heap_usage = store.gc_store.as_ref().map_or(0, |gc_store| {
396 gc_store.last_post_gc_allocated_bytes.unwrap_or(0)
397 });
398
399 if should_collect_first(bytes_needed, gc_heap_capacity, last_gc_heap_usage) {
400 log::trace!(
401 "Collecting first, then retrying; growing GC heap if collecting didn't \
402 free up enough space, then retrying again"
403 );
404 store
405 .gc(limiter.as_deref_mut(), None, None, asyncness)
406 .await?;
407
408 match alloc_func(&mut store, value) {
409 Ok(x) => Ok(x),
410 Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
411 Ok(oom2) => {
412 let (value, _) = oom2.take_inner();
415 let _ =
419 store.grow_gc_heap(limiter, bytes_needed, asyncness).await;
420
421 alloc_func(&mut store, value)
422 }
423 Err(e) => Err(e),
424 },
425 }
426 } else {
427 log::trace!(
428 "Grow GC heap first, collecting if growth failed, then retrying"
429 );
430
431 if let Err(e) = store
432 .grow_gc_heap(limiter.as_deref_mut(), bytes_needed.max(1), asyncness)
433 .await
434 {
435 log::trace!("growing GC heap failed: {e}");
436 store.gc(limiter, None, None, asyncness).await?;
437 }
438
439 alloc_func(&mut store, value)
440 }
441 }
442 Err(e) => Err(e),
443 },
444 }
445 }
446
447 pub(crate) fn set_pending_exception(&mut self, exnref: &VMGcRef) -> crate::Error {
455 debug_assert!(exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
456 let gc_store = self.gc_store.as_mut().unwrap();
457 match gc_store.write_gc_ref(&mut self.pending_exception, Some(exnref)) {
458 Ok(()) => ThrownException.into(),
459 Err(e) => e,
460 }
461 }
462
463 pub(crate) fn expose_pending_exception_to_wasm(&mut self) -> Option<NonZeroU32> {
466 let exnref = self.pending_exception.take()?;
467 let gc_store = self.unwrap_gc_store_mut();
468 debug_assert!(exnref.is_exnref(&*gc_store.gc_heap));
469 Some(gc_store.expose_gc_ref_to_wasm(exnref).unwrap())
470 }
471
472 fn take_pending_exception_rooted(&mut self) -> Option<Rooted<ExnRef>> {
475 let vmexnref = self.pending_exception.take()?;
476 debug_assert!(vmexnref.is_exnref(&*self.unwrap_gc_store().gc_heap));
477 let mut nogc = AutoAssertNoGc::new(self);
478 Some(Rooted::new(&mut nogc, vmexnref))
479 }
480
481 pub(crate) fn pending_exception_tag_and_instance(
484 &mut self,
485 ) -> Option<(InstanceId, DefinedTagIndex)> {
486 let pending_exnref = self.pending_exception.as_ref()?.unchecked_copy();
487 debug_assert!(pending_exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
488 let mut store = AutoAssertNoGc::new(self);
489
490 pending_exnref.into_exnref_unchecked().tag(&mut store).ok()
497 }
498
499 #[cfg(feature = "debug")]
502 pub(crate) fn pending_exception_owned_rooted(
503 &mut self,
504 ) -> Result<Option<crate::OwnedRooted<ExnRef>>, crate::OutOfMemory> {
505 let pending = match &self.pending_exception {
506 Some(r) => r,
507 None => return Ok(None),
508 };
509 let cloned = self.gc_store.as_mut().unwrap().clone_gc_ref(pending);
510 let mut nogc = AutoAssertNoGc::new(self);
511 Ok(Some(crate::OwnedRooted::new(&mut nogc, cloned)?))
512 }
513
514 fn throw_impl<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
519 let exception = exception.try_gc_ref(self)?.unchecked_copy();
520 Err(self.set_pending_exception(&exception))
521 }
522
523 #[inline]
537 pub(crate) fn require_gc_store(&self) -> Result<&GcStore> {
538 match &self.gc_store {
539 Some(gc_store) => Ok(gc_store),
540 None => bail!("GC heap not initialized yet"),
541 }
542 }
543
544 #[inline]
546 pub(crate) fn require_gc_store_mut(&mut self) -> Result<&mut GcStore> {
547 match &mut self.gc_store {
548 Some(gc_store) => Ok(gc_store),
549 None => bail!("GC heap not initialized yet"),
550 }
551 }
552
553 pub(crate) fn gc_heap_capacity(&self) -> usize {
556 match self.gc_store.as_ref() {
557 Some(gc_store) => gc_store.gc_heap_capacity(),
558 None => 0,
559 }
560 }
561
562 async fn do_gc(&mut self, asyncness: Asyncness) -> Result<()> {
563 if self.gc_store.is_none() {
565 return Ok(());
566 }
567
568 log::trace!("============ Begin GC ===========");
569
570 let mut roots = core::mem::take(&mut self.gc_roots_list);
573
574 self.trace_roots(&mut roots, asyncness).await;
575 self.unwrap_gc_store_mut()
576 .gc(
577 asyncness,
578 unsafe { roots.iter() },
579 yield_now,
584 )
585 .await?;
586
587 roots.clear();
589 self.gc_roots_list = roots;
590
591 log::trace!("============ End GC ===========");
592 Ok(())
593 }
594
595 async fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList, asyncness: Asyncness) {
596 log::trace!("Begin trace GC roots");
597
598 assert!(gc_roots_list.is_empty());
600
601 self.trace_wasm_stack_roots(gc_roots_list);
602 if asyncness != Asyncness::No {
603 self.yield_now().await;
604 }
605
606 #[cfg(feature = "stack-switching")]
607 {
608 self.trace_wasm_continuation_roots(gc_roots_list);
609 if asyncness != Asyncness::No {
610 self.yield_now().await;
611 }
612 }
613
614 self.trace_vmctx_roots(gc_roots_list);
615 if asyncness != Asyncness::No {
616 self.yield_now().await;
617 }
618
619 self.trace_instance_roots(gc_roots_list);
620 if asyncness != Asyncness::No {
621 self.yield_now().await;
622 }
623
624 self.trace_user_roots(gc_roots_list);
625 if asyncness != Asyncness::No {
626 self.yield_now().await;
627 }
628
629 self.trace_pending_exception_roots(gc_roots_list);
630
631 log::trace!("End trace GC roots")
632 }
633
634 fn trace_wasm_stack_frame(&self, gc_roots_list: &mut GcRootsList, frame: Frame) {
635 let pc = frame.pc();
636 debug_assert!(pc != 0, "we should always get a valid PC for Wasm frames");
637
638 let fp = frame.fp() as *mut usize;
639 debug_assert!(
640 !fp.is_null(),
641 "we should always get a valid frame pointer for Wasm frames"
642 );
643
644 let (store_code, offset) = self
645 .modules()
646 .store_code_by_pc(pc)
647 .expect("should have store code for Wasm frame");
648 let offset = u32::try_from(offset).unwrap();
649
650 let stack_map =
651 wasmtime_environ::StackMap::lookup(offset, store_code.code_memory().stack_map_data());
652
653 if let Some(stack_map) = stack_map {
654 log::trace!(
655 "We have a stack map that maps {} bytes in this Wasm frame",
656 stack_map.frame_size()
657 );
658
659 let sp = unsafe { stack_map.sp(fp) };
660 for stack_slot in unsafe { stack_map.live_gc_refs(sp) } {
661 unsafe {
662 self.trace_wasm_stack_slot(gc_roots_list, stack_slot);
663 }
664 }
665 }
666
667 #[cfg(feature = "debug")]
668 if let Some(frame_table) = store_code.code_memory().frame_table() {
669 for stack_slot in crate::debug::gc_refs_in_frame(frame_table, offset, fp) {
670 unsafe {
671 self.trace_wasm_stack_slot(gc_roots_list, stack_slot);
672 }
673 }
674 }
675 }
676
677 unsafe fn trace_wasm_stack_slot(&self, gc_roots_list: &mut GcRootsList, stack_slot: *mut u32) {
678 let raw: u32 = unsafe { core::ptr::read(stack_slot) };
679 log::trace!("Stack slot @ {stack_slot:p} = {raw:#x}");
680
681 let gc_ref = vm::VMGcRef::from_raw_u32(raw);
682 if gc_ref.is_some() {
683 unsafe {
684 gc_roots_list
685 .add_wasm_stack_root(SendSyncPtr::new(NonNull::new(stack_slot).unwrap()));
686 }
687 }
688 }
689
690 fn trace_wasm_stack_roots(&mut self, gc_roots_list: &mut GcRootsList) {
691 log::trace!("Begin trace GC roots :: Wasm stack");
692
693 Backtrace::trace(self, |frame| {
694 self.trace_wasm_stack_frame(gc_roots_list, frame);
695 core::ops::ControlFlow::Continue(())
696 });
697
698 log::trace!("End trace GC roots :: Wasm stack");
699 }
700
701 #[cfg(feature = "stack-switching")]
702 fn trace_wasm_continuation_roots(&mut self, gc_roots_list: &mut GcRootsList) {
703 use crate::vm::VMStackState;
704
705 log::trace!("Begin trace GC roots :: continuations");
706
707 for continuation in &self.continuations {
708 let state = continuation.common_stack_information.state;
709
710 match state {
718 VMStackState::Suspended => {
719 Backtrace::trace_suspended_continuation(self, continuation.deref(), |frame| {
720 self.trace_wasm_stack_frame(gc_roots_list, frame);
721 core::ops::ControlFlow::Continue(())
722 });
723 }
724 VMStackState::Running => {
725 }
727 VMStackState::Parent => {
728 }
732 VMStackState::Fresh | VMStackState::Returned => {
733 }
735 }
736 }
737
738 log::trace!("End trace GC roots :: continuations");
739 }
740
741 fn trace_vmctx_roots(&mut self, gc_roots_list: &mut GcRootsList) {
742 log::trace!("Begin trace GC roots :: vmctx");
743 self.for_each_global(|store, global| global.trace_root(store, gc_roots_list));
744 self.for_each_table(|store, table| table.trace_roots(store, gc_roots_list));
745 log::trace!("End trace GC roots :: vmctx");
746 }
747
748 fn trace_instance_roots(&mut self, gc_roots_list: &mut GcRootsList) {
749 log::trace!("Begin trace GC roots :: instance");
750 for (_id, instance) in &mut self.instances {
751 unsafe {
754 instance
755 .handle
756 .get_mut()
757 .trace_element_segment_roots(gc_roots_list);
758 }
759 }
760 log::trace!("End trace GC roots :: instance");
761 }
762
763 fn trace_user_roots(&mut self, gc_roots_list: &mut GcRootsList) {
764 log::trace!("Begin trace GC roots :: user");
765 self.gc_roots.trace_roots(gc_roots_list);
766 log::trace!("End trace GC roots :: user");
767 }
768
769 fn trace_pending_exception_roots(&mut self, gc_roots_list: &mut GcRootsList) {
770 log::trace!("Begin trace GC roots :: pending exception");
771 if let Some(pending_exception) = self.pending_exception.as_mut() {
772 unsafe {
773 gc_roots_list.add_vmgcref_root(pending_exception.into(), "Pending exception");
774 }
775 }
776 log::trace!("End trace GC roots :: pending exception");
777 }
778
779 pub(crate) fn insert_gc_host_alloc_type(&mut self, ty: RegisteredType) {
786 if let Some(gc_store) = self.optional_gc_store_mut() {
790 gc_store.ensure_trace_info(ty.index());
791 }
792 self.gc_host_alloc_types.insert(ty);
793 }
794}
795
796struct WithoutGcZealAllocCounter<'a> {
798 store: &'a mut StoreOpaque,
799 counter: Option<NonZeroU32>,
800}
801
802impl Deref for WithoutGcZealAllocCounter<'_> {
803 type Target = StoreOpaque;
804
805 fn deref(&self) -> &Self::Target {
806 &self.store
807 }
808}
809
810impl DerefMut for WithoutGcZealAllocCounter<'_> {
811 fn deref_mut(&mut self) -> &mut Self::Target {
812 &mut self.store
813 }
814}
815
816impl Drop for WithoutGcZealAllocCounter<'_> {
817 fn drop(&mut self) {
818 self.store.replace_gc_zeal_alloc_counter(self.counter);
819 }
820}
821
822impl<'a> WithoutGcZealAllocCounter<'a> {
823 pub fn new(store: &'a mut StoreOpaque) -> Self {
824 let counter = store.replace_gc_zeal_alloc_counter(None);
825 WithoutGcZealAllocCounter { store, counter }
826 }
827}
828
829#[track_caller]
838fn should_collect_first(
839 bytes_needed: u64,
840 gc_heap_capacity: usize,
841 last_gc_heap_usage: usize,
842) -> bool {
843 debug_assert!(last_gc_heap_usage <= gc_heap_capacity);
844
845 if gc_heap_capacity == 0 {
852 return false;
853 }
854
855 if bytes_needed == 0 {
858 return true;
859 }
860
861 let Ok(bytes_needed) = usize::try_from(bytes_needed) else {
862 return false;
865 };
866
867 if bytes_needed > isize::MAX.cast_unsigned() {
868 return false;
872 }
873
874 let Some(predicted_usage) = last_gc_heap_usage.checked_add(bytes_needed) else {
875 return true;
879 };
880
881 predicted_usage < gc_heap_capacity / 2
886}
887
888#[cfg(test)]
889mod tests {
890 use super::should_collect_first;
891
892 #[test]
893 fn test_should_collect_first() {
894 for bytes_needed in 0..256 {
896 assert_eq!(should_collect_first(bytes_needed, 0, 0), false);
897 }
898
899 for cap in 1..256 {
901 for usage in 0..=cap {
902 assert_eq!(should_collect_first(0, cap, usage), true);
903 }
904 }
905
906 let max_alloc_usize = isize::MAX.cast_unsigned();
907 let max_alloc_u64 = u64::try_from(max_alloc_usize).unwrap();
908
909 assert_eq!(
912 should_collect_first(max_alloc_u64 + 1, max_alloc_usize, 0),
913 false,
914 );
915
916 assert_eq!(should_collect_first(1, usize::MAX, usize::MAX), true);
918
919 assert_eq!(should_collect_first(16, 1024, 64), true);
922
923 assert_eq!(should_collect_first(16, 1024, 512), false);
927 }
928}