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