1use crate::global_state::GlobalState;
2use crate::plan::PlanConstraints;
3use crate::scheduler::GCWorkScheduler;
4use crate::util::conversions::*;
5use crate::util::metadata::side_metadata::{
6 SideMetadataContext, SideMetadataSanity, SideMetadataSpec,
7};
8use crate::util::object_enum::ObjectEnumerator;
9use crate::util::Address;
10use crate::util::ObjectReference;
11
12use crate::util::heap::layout::vm_layout::{vm_layout, LOG_BYTES_IN_CHUNK};
13use crate::util::heap::{PageResource, VMRequest};
14use crate::util::options::Options;
15use crate::vm::{ActivePlan, Collection};
16
17use crate::util::constants::LOG_BYTES_IN_MBYTE;
18use crate::util::conversions;
19use crate::util::opaque_pointer::*;
20
21use crate::mmtk::SFT_MAP;
22#[cfg(debug_assertions)]
23use crate::policy::sft::EMPTY_SFT_NAME;
24use crate::policy::sft::SFT;
25use crate::util::alloc::allocator::AllocationOptions;
26use crate::util::copy::*;
27use crate::util::heap::gc_trigger::GCTrigger;
28use crate::util::heap::layout::vm_layout::BYTES_IN_CHUNK;
29use crate::util::heap::layout::Mmapper;
30use crate::util::heap::layout::VMMap;
31use crate::util::heap::space_descriptor::SpaceDescriptor;
32use crate::util::heap::HeapMeta;
33use crate::util::os::*;
34use crate::vm::VMBinding;
35
36use std::marker::PhantomData;
37use std::sync::atomic::AtomicBool;
38use std::sync::Arc;
39use std::sync::Mutex;
40
41use downcast_rs::Downcast;
42
43pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
44 fn as_space(&self) -> &dyn Space<VM>;
45 fn as_sft(&self) -> &(dyn SFT + Sync + 'static);
46 fn get_page_resource(&self) -> &dyn PageResource<VM>;
47
48 fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>>;
51
52 fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap);
56
57 fn initialize_side_metadata(&self) {}
61
62 fn acquire(&self, tls: VMThread, pages: usize, alloc_options: AllocationOptions) -> Address {
63 trace!(
64 "Space.acquire, tls={:?}, alloc_options={:?}",
65 tls,
66 alloc_options
67 );
68
69 debug_assert!(
70 !self.get_gc_trigger().will_oom_on_alloc(pages << crate::util::constants::LOG_BYTES_IN_PAGE),
71 "The requested pages is larger than the max heap size. Is will_go_oom_on_acquire used before acquring memory?"
72 );
73
74 trace!("Reserving pages");
75 let pr = self.get_page_resource();
76 let pages_reserved = pr.reserve_pages(pages);
77 trace!("Pages reserved");
78
79 let should_poll = VM::VMActivePlan::is_mutator(tls);
82
83 let gc_triggered = should_poll && {
86 trace!("Polling ..");
87 self.get_gc_trigger().poll(false, Some(self.as_space()))
88 };
89
90 let should_get_pages = !gc_triggered || alloc_options.allow_overcommit;
94
95 if should_get_pages {
99 if let Some(addr) = self.get_new_pages_and_initialize(tls, pages, pr, pages_reserved) {
100 addr
101 } else {
102 self.not_acquiring(tls, alloc_options, pr, pages_reserved, true);
103 Address::ZERO
104 }
105 } else {
106 self.not_acquiring(tls, alloc_options, pr, pages_reserved, false);
107 Address::ZERO
108 }
109 }
110
111 fn get_new_pages_and_initialize(
120 &self,
121 tls: VMThread,
122 pages: usize,
123 pr: &dyn PageResource<VM>,
124 pages_reserved: usize,
125 ) -> Option<Address> {
126 let lock = self.common().acquire_lock.lock().unwrap();
133
134 let Ok(res) = pr.get_new_pages(self.common().descriptor, pages_reserved, pages, tls) else {
135 return None;
136 };
137
138 debug!(
139 "Got new pages {} ({} pages) for {} in chunk {}, new_chunk? {}",
140 res.start,
141 res.pages,
142 self.get_name(),
143 conversions::chunk_align_down(res.start),
144 res.new_chunk
145 );
146 let bytes = conversions::pages_to_bytes(res.pages);
147 #[cfg(debug_assertions)]
148 self.common()
149 .metadata
150 .assert_metadata_ranges_in_reserved_range(res.start, bytes, self.get_name());
151
152 let mmap = || {
153 if let Err(mmap_error) = self
156 .common()
157 .mmapper
158 .ensure_mapped(
159 res.start,
160 res.pages,
161 self.common()
162 .options
163 .transparent_hugepages_as_huge_page_support(),
164 self.common().mmap_protection(),
165 &MmapAnnotation::Space {
166 name: self.get_name(),
167 },
168 )
169 .and(self.common().metadata.try_map_metadata_space(
170 res.start,
171 bytes,
172 self.get_name(),
173 ))
174 {
175 OS::handle_mmap_error::<VM>(mmap_error, tls);
176 }
177 };
178 let grow_space = || {
179 self.grow_space(res.start, bytes, res.new_chunk);
180 };
181
182 if SFT_MAP.get_side_metadata().is_some() {
184 mmap();
186 grow_space();
188 drop(lock);
190 } else {
191 grow_space();
193 drop(lock);
194 mmap();
196 }
197
198 if self.common().zeroed {
200 crate::util::memory::zero(res.start, bytes);
201 }
202
203 {
205 debug_assert_eq!(SFT_MAP.get_checked(res.start).name(), self.get_name());
208 debug_assert!(self.address_in_space(res.start));
210 debug_assert_eq!(
212 self.common().vm_map().get_descriptor_for_address(res.start),
213 self.common().descriptor
214 );
215
216 let last_byte = res.start + bytes - 1;
218 debug_assert_eq!(SFT_MAP.get_checked(last_byte).name(), self.get_name());
220 debug_assert!(self.address_in_space(last_byte));
222 debug_assert_eq!(
224 self.common().vm_map().get_descriptor_for_address(last_byte),
225 self.common().descriptor
226 );
227 }
228
229 debug!("Space.acquire(), returned = {}", res.start);
230 Some(res.start)
231 }
232
233 fn not_acquiring(
239 &self,
240 tls: VMThread,
241 alloc_options: AllocationOptions,
242 pr: &dyn PageResource<VM>,
243 pages_reserved: usize,
244 attempted_allocation_and_failed: bool,
245 ) {
246 assert!(
247 VM::VMActivePlan::is_mutator(tls),
248 "A non-mutator thread failed to get pages from page resource. \
249 Copying GC plans should compute the copying headroom carefully to prevent this."
250 );
251
252 pr.clear_request(pages_reserved);
254
255 if !alloc_options.at_safepoint {
257 return;
258 }
259
260 debug!("Collection required");
261
262 if !self.common().global_state.is_initialized() {
263 panic!(
265 "GC is not allowed here: collection is not initialized \
266 (did you call initialize_collection()?). \
267 Out of physical memory: {phy}",
268 phy = attempted_allocation_and_failed
269 );
270 }
271
272 if attempted_allocation_and_failed {
273 let gc_performed = self.get_gc_trigger().poll(true, Some(self.as_space()));
275 debug_assert!(gc_performed, "GC not performed when forced.");
276 }
277
278 let meta_pages_reserved = self.estimate_side_meta_pages(pages_reserved);
280 let total_pages_reserved = pages_reserved + meta_pages_reserved;
281 self.get_gc_trigger()
282 .policy
283 .on_pending_allocation(total_pages_reserved);
284
285 VM::VMCollection::block_for_gc(VMMutatorThread(tls)); }
287
288 fn address_in_space(&self, start: Address) -> bool {
289 if !self.common().descriptor.is_contiguous() {
290 self.common().vm_map().get_descriptor_for_address(start) == self.common().descriptor
291 } else {
292 start >= self.common().start && start < self.common().start + self.common().extent
293 }
294 }
295
296 fn in_space(&self, object: ObjectReference) -> bool {
297 self.address_in_space(object.to_raw_address())
298 }
299
300 fn grow_space(&self, start: Address, bytes: usize, new_chunk: bool) {
310 trace!(
311 "Grow space from {} for {} bytes (new chunk = {})",
312 start,
313 bytes,
314 new_chunk
315 );
316
317 #[cfg(debug_assertions)]
319 if !new_chunk {
320 debug_assert!(
321 SFT_MAP.get_checked(start).name() != EMPTY_SFT_NAME,
322 "In grow_space(start = {}, bytes = {}, new_chunk = {}), we have empty SFT entries (chunk for {} = {})",
323 start,
324 bytes,
325 new_chunk,
326 start,
327 SFT_MAP.get_checked(start).name()
328 );
329 debug_assert!(
330 SFT_MAP.get_checked(start + bytes - 1).name() != EMPTY_SFT_NAME,
331 "In grow_space(start = {}, bytes = {}, new_chunk = {}), we have empty SFT entries (chunk for {} = {})",
332 start,
333 bytes,
334 new_chunk,
335 start + bytes - 1,
336 SFT_MAP.get_checked(start + bytes - 1).name()
337 );
338 }
339
340 if new_chunk {
341 unsafe { SFT_MAP.update(self.as_sft(), start, bytes) };
342 }
343 }
344
345 fn estimate_side_meta_pages(&self, data_pages: usize) -> usize {
353 self.common().metadata.calculate_reserved_pages(data_pages)
354 }
355
356 fn reserved_pages(&self) -> usize {
357 let data_pages = self.get_page_resource().reserved_pages();
358 let meta_pages = self.estimate_side_meta_pages(data_pages);
359 data_pages + meta_pages
360 }
361
362 fn available_physical_pages(&self) -> usize {
364 self.get_page_resource().get_available_physical_pages()
365 }
366
367 fn get_name(&self) -> &'static str {
368 self.common().name
369 }
370
371 fn get_descriptor(&self) -> SpaceDescriptor {
372 self.common().descriptor
373 }
374
375 fn common(&self) -> &CommonSpace<VM>;
376 fn get_gc_trigger(&self) -> &GCTrigger<VM> {
377 self.common().gc_trigger.as_ref()
378 }
379
380 fn release_multiple_pages(&mut self, start: Address);
381
382 fn set_copy_for_sft_trace(&mut self, _semantics: Option<CopySemantics>) {
385 panic!("A copying space should override this method")
386 }
387
388 fn verify_side_metadata_sanity(&self, side_metadata_sanity_checker: &mut SideMetadataSanity) {
398 side_metadata_sanity_checker
399 .verify_metadata_context(std::any::type_name::<Self>(), &self.common().metadata)
400 }
401
402 fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator);
423
424 fn set_allocate_as_live(&self, live: bool) {
425 self.common()
426 .allocate_as_live
427 .store(live, std::sync::atomic::Ordering::SeqCst);
428 }
429
430 fn should_allocate_as_live(&self) -> bool {
431 self.common()
432 .allocate_as_live
433 .load(std::sync::atomic::Ordering::Acquire)
434 }
435
436 fn clear_side_log_bits(&self);
439
440 fn set_side_log_bits(&self);
443}
444
445#[allow(unused)]
449pub(crate) fn print_vm_map<VM: VMBinding>(
450 space: &dyn Space<VM>,
451 out: &mut impl std::fmt::Write,
452) -> Result<(), std::fmt::Error> {
453 let common = space.common();
454 write!(out, "{} ", common.name)?;
455 if common.immortal {
456 write!(out, "I")?;
457 } else {
458 write!(out, " ")?;
459 }
460 if common.movable {
461 write!(out, " ")?;
462 } else {
463 write!(out, "N")?;
464 }
465 write!(out, " ")?;
466 if common.contiguous {
467 write!(
468 out,
469 "{}->{}",
470 common.start,
471 common.start + common.extent - 1
472 )?;
473 match common.vmrequest {
474 VMRequest::Extent { extent, .. } => {
475 write!(out, " E {}", extent)?;
476 }
477 VMRequest::Fraction { frac, .. } => {
478 write!(out, " F {}", frac)?;
479 }
480 _ => {}
481 }
482 } else {
483 let mut a = space
484 .get_page_resource()
485 .common()
486 .get_head_discontiguous_region();
487 while !a.is_zero() {
488 write!(
489 out,
490 "{}->{}",
491 a,
492 a + space.common().vm_map().get_contiguous_region_size(a) - 1
493 )?;
494 a = space.common().vm_map().get_next_contiguous_region(a);
495 if !a.is_zero() {
496 write!(out, " ")?;
497 }
498 }
499 }
500 writeln!(out)?;
501
502 Ok(())
503}
504
505impl_downcast!(Space<VM> where VM: VMBinding);
506
507pub struct CommonSpace<VM: VMBinding> {
508 pub name: &'static str,
509 pub descriptor: SpaceDescriptor,
510 pub vmrequest: VMRequest,
511
512 pub copy: Option<CopySemantics>,
515
516 pub immortal: bool,
517 pub movable: bool,
518 pub contiguous: bool,
519 pub zeroed: bool,
520
521 pub permission_exec: bool,
522
523 pub start: Address,
524 pub extent: usize,
525
526 pub vm_map: &'static dyn VMMap,
527 pub mmapper: &'static dyn Mmapper,
528
529 pub(crate) metadata: SideMetadataContext,
530
531 pub needs_log_bit: bool,
534 pub unlog_allocated_object: bool,
535 pub unlog_traced_object: bool,
536
537 pub acquire_lock: Mutex<()>,
539
540 pub gc_trigger: Arc<GCTrigger<VM>>,
541 pub global_state: Arc<GlobalState>,
542 pub options: Arc<Options>,
543
544 pub allocate_as_live: AtomicBool,
545
546 p: PhantomData<VM>,
547}
548
549pub struct PolicyCreateSpaceArgs<'a, VM: VMBinding> {
551 pub plan_args: PlanCreateSpaceArgs<'a, VM>,
552 pub movable: bool,
553 pub immortal: bool,
554 pub local_side_metadata_specs: Vec<SideMetadataSpec>,
555}
556
557pub struct PlanCreateSpaceArgs<'a, VM: VMBinding> {
559 pub name: &'static str,
560 pub zeroed: bool,
561 pub permission_exec: bool,
562 pub unlog_allocated_object: bool,
563 pub unlog_traced_object: bool,
564 pub vmrequest: VMRequest,
565 pub global_side_metadata_specs: Vec<SideMetadataSpec>,
566 pub vm_map: &'static dyn VMMap,
567 pub mmapper: &'static dyn Mmapper,
568 pub heap: &'a mut HeapMeta,
569 pub constraints: &'a PlanConstraints,
570 pub gc_trigger: Arc<GCTrigger<VM>>,
571 pub scheduler: Arc<GCWorkScheduler<VM>>,
572 pub options: Arc<Options>,
573 pub global_state: Arc<GlobalState>,
574}
575
576impl<'a, VM: VMBinding> PlanCreateSpaceArgs<'a, VM> {
577 pub fn into_policy_args(
579 self,
580 movable: bool,
581 immortal: bool,
582 policy_metadata_specs: Vec<SideMetadataSpec>,
583 ) -> PolicyCreateSpaceArgs<'a, VM> {
584 PolicyCreateSpaceArgs {
585 movable,
586 immortal,
587 local_side_metadata_specs: policy_metadata_specs,
588 plan_args: self,
589 }
590 }
591}
592
593impl<VM: VMBinding> CommonSpace<VM> {
594 pub fn new(args: PolicyCreateSpaceArgs<VM>) -> Self {
595 let mut rtn = CommonSpace {
596 name: args.plan_args.name,
597 descriptor: SpaceDescriptor::UNINITIALIZED,
598 vmrequest: args.plan_args.vmrequest,
599 copy: None,
600 immortal: args.immortal,
601 movable: args.movable,
602 contiguous: true,
603 permission_exec: args.plan_args.permission_exec,
604 zeroed: args.plan_args.zeroed,
605 start: unsafe { Address::zero() },
606 extent: 0,
607 vm_map: args.plan_args.vm_map,
608 mmapper: args.plan_args.mmapper,
609 needs_log_bit: args.plan_args.constraints.needs_log_bit,
610 unlog_allocated_object: args.plan_args.unlog_allocated_object,
611 unlog_traced_object: args.plan_args.unlog_traced_object,
612 gc_trigger: args.plan_args.gc_trigger.clone(),
613 metadata: SideMetadataContext {
614 global: args.plan_args.global_side_metadata_specs,
615 local: args.local_side_metadata_specs,
616 },
617 acquire_lock: Mutex::new(()),
618 global_state: args.plan_args.global_state,
619 options: args.plan_args.options.clone(),
620 allocate_as_live: AtomicBool::new(false),
621 p: PhantomData,
622 };
623
624 let vmrequest = args.plan_args.vmrequest;
625 if vmrequest.is_discontiguous() {
626 rtn.contiguous = false;
627 rtn.descriptor = SpaceDescriptor::create_descriptor();
629 return rtn;
631 }
632
633 let (extent, align, top) = match vmrequest {
634 VMRequest::Fraction { frac, top: _top } => (get_frac_available(frac), None, _top),
635 VMRequest::Extent {
636 extent: _extent,
637 top: _top,
638 } => (_extent, None, _top),
639 VMRequest::AlignedExtent { align, extent, top } => (extent, Some(align), top),
640 VMRequest::Fixed {
641 extent: _extent, ..
642 } => (_extent, None, false),
643 _ => unreachable!(),
644 };
645
646 assert!(
647 extent == raw_align_up(extent, BYTES_IN_CHUNK),
648 "{} requested non-aligned extent: {} bytes",
649 rtn.name,
650 extent
651 );
652
653 let reasonable_extent = conversions::raw_align_up(
656 Self::estimate_reasonable_contiguous_extent(
657 &args.plan_args.options,
658 &args.plan_args.gc_trigger,
659 args.plan_args.vm_map,
660 extent,
661 ),
662 BYTES_IN_CHUNK,
663 );
664 debug!(
665 "resonable_extent for space {} is {} bytes",
666 rtn.name, reasonable_extent
667 );
668
669 let anno = MmapAnnotation::Space { name: rtn.name };
670 let huge_page_option = args
671 .plan_args
672 .options
673 .transparent_hugepages_as_huge_page_support();
674
675 let start = if let VMRequest::Fixed { start: _start, .. } = vmrequest {
676 if let Err(mmap_error) = args.plan_args.mmapper.quarantine_address_range(
677 _start,
678 bytes_to_pages_up(reasonable_extent),
679 huge_page_option,
680 &anno,
681 ) {
682 panic!(
683 "Failed to quarantine fixed contiguous space {} [{}, {}) for {} bytes: {}",
684 rtn.name,
685 _start,
686 _start + extent,
687 extent,
688 mmap_error
689 );
690 }
691 _start
692 } else {
693 args.plan_args
694 .heap
695 .reserve_quarantined(
696 reasonable_extent,
697 align,
698 top,
699 args.plan_args.mmapper,
700 huge_page_option,
701 &anno,
702 )
703 .unwrap_or_else(|mmap_error| {
704 panic!(
705 "Failed to quarantine contiguous space {} for {} bytes: {}",
706 rtn.name, extent, mmap_error
707 )
708 })
709 };
710 assert!(
711 start == chunk_align_up(start),
712 "{} starting on non-aligned boundary: {}",
713 rtn.name,
714 start
715 );
716
717 rtn.contiguous = true;
718 rtn.start = start;
719 rtn.extent = reasonable_extent;
720 rtn.descriptor =
721 SpaceDescriptor::create_descriptor_from_heap_range(start, start + reasonable_extent);
722
723 {
727 use crate::util::heap::layout;
728 let overlap =
729 Address::range_intersection(&(start..start + extent), &layout::available_range());
730 if !overlap.is_empty() {
731 args.plan_args.vm_map.insert(
732 overlap.start,
733 overlap.end - overlap.start,
734 rtn.descriptor,
735 );
736 }
737 }
738
739 debug!(
740 "Created space {} [{}, {}) for {} bytes",
741 rtn.name,
742 start,
743 start + reasonable_extent,
744 reasonable_extent
745 );
746
747 rtn
748 }
749
750 pub fn estimate_reasonable_contiguous_extent(
755 options: &Options,
756 gc_trigger: &GCTrigger<VM>,
757 vm_map: &dyn VMMap,
758 extent: usize,
759 ) -> usize {
760 if *options.plan == crate::util::options::PlanSelector::PageProtect {
762 return extent;
763 }
764
765 const VIRTUAL_MEMORY_RATIO_TO_MAX_HEAP_SIZE: usize = 2;
769 let estimate = conversions::pages_to_bytes(gc_trigger.policy.get_max_heap_size_in_pages())
770 * VIRTUAL_MEMORY_RATIO_TO_MAX_HEAP_SIZE;
771
772 estimate.max(vm_map.min_contiguous_extent())
773 }
774
775 pub fn initialize_sft(
776 &self,
777 sft: &(dyn SFT + Sync + 'static),
778 sft_map: &mut dyn crate::policy::sft_map::SFTMap,
779 ) {
780 if self.contiguous {
787 unsafe { sft_map.eager_initialize(sft, self.start, self.extent) };
788 }
789 }
790
791 pub fn vm_map(&self) -> &'static dyn VMMap {
792 self.vm_map
793 }
794
795 pub fn mmap_protection(&self) -> MmapProtection {
796 if self.permission_exec || cfg!(feature = "exec_permission_on_all_spaces") {
797 MmapProtection::ReadWriteExec
798 } else {
799 MmapProtection::ReadWrite
800 }
801 }
802
803 pub(crate) fn debug_print_object_global_info(&self, object: ObjectReference) {
804 #[cfg(feature = "vo_bit")]
805 println!(
806 "vo bit = {}",
807 crate::util::metadata::vo_bit::is_vo_bit_set(object)
808 );
809 if self.needs_log_bit {
810 use crate::vm::object_model::ObjectModel;
811 use std::sync::atomic::Ordering;
812 println!(
813 "log bit = {}",
814 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.is_unlogged::<VM>(object, Ordering::Relaxed),
815 );
816 }
817 println!("is live = {}", object.is_live());
818 }
819}
820
821fn get_frac_available(frac: f32) -> usize {
822 trace!("AVAILABLE_START={}", vm_layout().available_start());
823 trace!("AVAILABLE_END={}", vm_layout().available_end());
824 let bytes = (frac * vm_layout().available_bytes() as f32) as usize;
825 trace!("bytes={}*{}={}", frac, vm_layout().available_bytes(), bytes);
826 let mb = bytes >> LOG_BYTES_IN_MBYTE;
827 let rtn = mb << LOG_BYTES_IN_MBYTE;
828 trace!("rtn={}", rtn);
829 let aligned_rtn = raw_align_up(rtn, BYTES_IN_CHUNK);
830 trace!("aligned_rtn={}", aligned_rtn);
831 aligned_rtn
832}
833
834pub fn required_chunks(pages: usize) -> usize {
835 let extent = raw_align_up(pages_to_bytes(pages), BYTES_IN_CHUNK);
836 extent >> LOG_BYTES_IN_CHUNK
837}