From 10883f49cf2c48f3e2c45a0ecdba31595dc68ddc Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Thu, 15 May 2025 04:38:07 +0000 Subject: [PATCH 1/2] Introduce SpaceInspector and RegionInspector --- macros/src/has_spaces_impl.rs | 4 +- src/mmtk.rs | 11 +++ src/plan/global.rs | 4 +- src/policy/immix/immixspace.rs | 29 +++++- src/policy/space.rs | 4 + src/util/heap/inspection/mod.rs | 52 ++++++++++ src/util/heap/mod.rs | 2 + .../mock_tests/mock_test_heap_inspector.rs | 99 +++++++++++++++++++ src/vm/tests/mock_tests/mod.rs | 1 + 9 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 src/util/heap/inspection/mod.rs create mode 100644 src/vm/tests/mock_tests/mock_test_heap_inspector.rs diff --git a/macros/src/has_spaces_impl.rs b/macros/src/has_spaces_impl.rs index 71286e415d..4016e95c89 100644 --- a/macros/src/has_spaces_impl.rs +++ b/macros/src/has_spaces_impl.rs @@ -70,12 +70,12 @@ pub(crate) fn generate_impl_items<'a>( }; quote! { - fn for_each_space(&self, __func: &mut dyn FnMut(&dyn Space)) { + fn for_each_space<'a>(&'a self, __func: &mut dyn FnMut(&'a dyn Space)) { #(#space_visitors)* #parent_visitor } - fn for_each_space_mut(&mut self, __func: &mut dyn FnMut(&mut dyn Space)) { + fn for_each_space_mut<'a>(&'a mut self, __func: &mut dyn FnMut(&'a mut dyn Space)) { #(#space_visitors_mut)* #parent_visitor_mut } diff --git a/src/mmtk.rs b/src/mmtk.rs index e0a355108f..05f2df18fd 100644 --- a/src/mmtk.rs +++ b/src/mmtk.rs @@ -12,6 +12,7 @@ use crate::util::address::ObjectReference; use crate::util::analysis::AnalysisManager; use crate::util::finalizable_processor::FinalizableProcessor; use crate::util::heap::gc_trigger::GCTrigger; +use crate::util::heap::inspection::SpaceInspector; use crate::util::heap::layout::heap_parameters::MAX_SPACES; use crate::util::heap::layout::vm_layout::VMLayout; use crate::util::heap::layout::{self, Mmapper, VMMap}; @@ -596,4 +597,14 @@ impl MMTK { .vm_space .initialize_object_metadata(object, false) } + + pub fn inspect_spaces<'a>(&'a self) -> Vec<&'a dyn SpaceInspector> { + let mut ret = vec![]; + self.get_plan().for_each_space(&mut |space| { + if let Some(inspector) = space.as_inspector() { + ret.push(inspector); + } + }); + ret + } } diff --git a/src/plan/global.rs b/src/plan/global.rs index 5a5bb38ab5..5f38c39908 100644 --- a/src/plan/global.rs +++ b/src/plan/global.rs @@ -714,13 +714,13 @@ pub trait HasSpaces { /// /// If `Self` contains nested fields that contain more spaces, this method shall visit spaces /// in the outer struct first. - fn for_each_space(&self, func: &mut dyn FnMut(&dyn Space)); + fn for_each_space<'a>(&'a self, func: &mut dyn FnMut(&'a dyn Space)); /// Visit each space field mutably. /// /// If `Self` contains nested fields that contain more spaces, this method shall visit spaces /// in the outer struct first. - fn for_each_space_mut(&mut self, func: &mut dyn FnMut(&mut dyn Space)); + fn for_each_space_mut<'a>(&'a mut self, func: &mut dyn FnMut(&'a mut dyn Space)); } /// A plan that uses `PlanProcessEdges` needs to provide an implementation for this trait. diff --git a/src/policy/immix/immixspace.rs b/src/policy/immix/immixspace.rs index 7b0429406b..d70ad94332 100644 --- a/src/policy/immix/immixspace.rs +++ b/src/policy/immix/immixspace.rs @@ -167,6 +167,9 @@ impl Space for ImmixSpace { fn as_sft(&self) -> &(dyn SFT + Sync + 'static) { self } + fn as_inspector(&self) -> Option<&dyn crate::util::heap::inspection::SpaceInspector> { + Some(self) + } fn get_page_resource(&self) -> &dyn PageResource { &self.pr } @@ -409,7 +412,7 @@ impl ImmixSpace { if self.common.needs_log_bit { if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC { for chunk in self.chunk_map.all_chunks() { - side.bzero_metadata(chunk.start(), Chunk::BYTES); + side.bzero_metadata(chunk .start(), Chunk::BYTES); } } } @@ -1178,3 +1181,27 @@ impl ClearVOBitsAfterPrepare { } } } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{RegionInspector, SpaceInspector, list_child_regions}; + impl SpaceInspector for ImmixSpace { + fn name(&self) -> &str { + SFT::name(self) + } + + fn list_regions(&self, parent_region: Option<&dyn RegionInspector>) -> Vec> { + if let Some(parent_region) = parent_region { + list_child_regions::(parent_region).or_else(|| { + if !crate::policy::immix::BLOCK_ONLY { + list_child_regions::(parent_region) + } else { + None + } + }).unwrap_or_else(|| vec![]) + } else { + self.chunk_map.all_chunks().map(|r: Chunk| Box::new(r) as Box).collect() + } + } + } +} diff --git a/src/policy/space.rs b/src/policy/space.rs index d300efbbf9..28350c928c 100644 --- a/src/policy/space.rs +++ b/src/policy/space.rs @@ -2,6 +2,7 @@ use crate::global_state::GlobalState; use crate::plan::PlanConstraints; use crate::scheduler::GCWorkScheduler; use crate::util::conversions::*; +use crate::util::heap::inspection::SpaceInspector; use crate::util::metadata::side_metadata::{ SideMetadataContext, SideMetadataSanity, SideMetadataSpec, }; @@ -42,6 +43,9 @@ use downcast_rs::Downcast; pub trait Space: 'static + SFT + Sync + Downcast { fn as_space(&self) -> &dyn Space; fn as_sft(&self) -> &(dyn SFT + Sync + 'static); + fn as_inspector(&self) -> Option<&dyn SpaceInspector> { + None + } fn get_page_resource(&self) -> &dyn PageResource; /// Get a mutable reference to the underlying page resource, or `None` if the space does not diff --git a/src/util/heap/inspection/mod.rs b/src/util/heap/inspection/mod.rs new file mode 100644 index 0000000000..0b0ddde8d5 --- /dev/null +++ b/src/util/heap/inspection/mod.rs @@ -0,0 +1,52 @@ +use crate::policy::sft::SFT; +use crate::util::linear_scan::RegionIterator; +use crate::util::{linear_scan::Region, Address, ObjectReference}; +use crate::util::metadata::side_metadata::spec_defs::VO_BIT; +use crate::util::heap::chunk_map::Chunk; + +pub trait SpaceInspector { + fn name(&self) -> &str; + fn list_regions(&self, parent_region: Option<&dyn RegionInspector>) -> Vec>; +} + +pub(crate) fn list_child_regions(region: &dyn RegionInspector) -> Option>> { + if region.region_type() == std::any::type_name::() { + let start_child_region = CHILD::from_aligned_address(region.start()); + let end_child_region = CHILD::from_aligned_address(region.start() + region.size()); + Some(RegionIterator::::new(start_child_region, end_child_region) + .map(|r| Box::new(r) as Box) + .collect()) + } else { + None + } +} + +pub trait RegionInspector { + fn region_type(&self) -> &str; + fn start(&self) -> Address; + fn size(&self) -> usize; + #[cfg(feature = "vo_bit")] + fn list_objects(&self) -> Vec { + let mut objects = vec![]; + VO_BIT.scan_non_zero_values::(self.start(), self.start() + self.size(), &mut |address| { + use crate::util::metadata::vo_bit; + let object = vo_bit::get_object_ref_for_vo_addr(address); + objects.push(object); + }); + objects + } +} + +impl RegionInspector for R { + fn region_type(&self) -> &str { + std::any::type_name::() + } + + fn start(&self) -> Address { + Region::start(self) + } + + fn size(&self) -> usize { + Self::BYTES + } +} diff --git a/src/util/heap/mod.rs b/src/util/heap/mod.rs index ae974740e8..11512bc94c 100644 --- a/src/util/heap/mod.rs +++ b/src/util/heap/mod.rs @@ -22,3 +22,5 @@ pub use self::layout::vm_layout; pub(crate) use self::monotonepageresource::MonotonePageResource; pub(crate) use self::pageresource::PageResource; pub(crate) use self::vmrequest::VMRequest; + +pub mod inspection; diff --git a/src/vm/tests/mock_tests/mock_test_heap_inspector.rs b/src/vm/tests/mock_tests/mock_test_heap_inspector.rs new file mode 100644 index 0000000000..190708176f --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_heap_inspector.rs @@ -0,0 +1,99 @@ +// GITHUB-CI: MMTK_PLAN=Immix +// GITHUB-CI: FEATURES=vo_bit + +use std::collections::HashSet; + +use constants::BYTES_IN_WORD; + +use super::mock_test_prelude::*; + +use crate::{util::*, AllocationSemantics, MMTK}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +pub fn get_all_objects(mmtk: &'static MMTK) -> HashSet { + let mut result = HashSet::new(); + mmtk.enumerate_objects(|object| { + result.insert(object); + }); + result +} + +#[test] +pub fn test_heap_inspector() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture_mut(|fixture| { + let mmtk = fixture.mmtk(); + let mutator = &mut fixture.mutator; + let space_inspector = mmtk.inspect_spaces(); + assert!(space_inspector.len() > 0); + + let get_immix_inspector = || { + space_inspector.iter().find(|s| s.name() == "immix").unwrap() + }; + + { + let immix_space_inspector = get_immix_inspector(); + let chunk_inspector = immix_space_inspector.list_regions(None); + assert_eq!(chunk_inspector.len(), 0); + } + + let mut new_obj = |size: usize, semantics: AllocationSemantics| { + let align = BYTES_IN_WORD; + let start = memory_manager::alloc(mutator, size, align, 0, semantics); + let object = MockVM::object_start_to_ref(start); + memory_manager::post_alloc(mutator, object, size, semantics); + object + }; + + // Allocate one object + let object = new_obj(40, AllocationSemantics::Default); + + { + let immix_space_inspector = get_immix_inspector(); + // Check chunks + let chunk_inspector = immix_space_inspector.list_regions(None); + assert_eq!(chunk_inspector.len(), 1); + assert_eq!(chunk_inspector[0].region_type(), "mmtk::util::heap::chunk_map::Chunk"); + let objects = chunk_inspector[0].list_objects(); + assert_eq!(objects.len(), 1); + assert_eq!(objects[0], object); + // Check blocks + let block_inspector = immix_space_inspector.list_regions(Some(&*chunk_inspector[0])); + assert_eq!(block_inspector.len(), 128); // 128 blocks in a chunk + assert_eq!(block_inspector[0].region_type(), "mmtk::policy::immix::block::Block"); + let objects = block_inspector[0].list_objects(); + assert_eq!(objects.len(), 1); + assert_eq!(objects[0], object); + // Check lines + let line_inspector = immix_space_inspector.list_regions(Some(&*block_inspector[0])); + assert_eq!(line_inspector.len(), 128); // 128 lines in a block + assert_eq!(line_inspector[0].region_type(), "mmtk::policy::immix::line::Line"); + let objects = line_inspector[0].list_objects(); + assert_eq!(objects.len(), 1); + assert_eq!(objects[0], object); + } + + // Allocate another object + let object2 = new_obj(40, AllocationSemantics::Default); + + { + let immix_space_inspector = get_immix_inspector(); + // Check checks + let chunk_inspector = immix_space_inspector.list_regions(None); + assert_eq!(chunk_inspector.len(), 1); + assert_eq!(chunk_inspector[0].region_type(), "mmtk::util::heap::chunk_map::Chunk"); + let objects = chunk_inspector[0].list_objects(); + assert_eq!(objects.len(), 2); + assert_eq!(objects[0], object); + assert_eq!(objects[1], object2); + } + }); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs index 946d861da1..a932ef05fa 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -40,6 +40,7 @@ mod mock_test_handle_mmap_conflict; mod mock_test_handle_mmap_oom; #[cfg(feature = "vo_bit")] mod mock_test_heap_traversal; +mod mock_test_heap_inspector; mod mock_test_init_fork; #[cfg(feature = "is_mmtk_object")] mod mock_test_internal_ptr_before_object_ref; From 8a6cac2f878bd4182b0deb90009e45d98380267c Mon Sep 17 00:00:00 2001 From: Yi Lin Date: Wed, 21 May 2025 05:36:29 +0000 Subject: [PATCH 2/2] Implement SpaceInspector for all policies --- src/mmtk.rs | 8 +- src/policy/copyspace.rs | 20 +++ src/policy/immix/immixspace.rs | 38 +++-- src/policy/immortalspace.rs | 23 +++ src/policy/largeobjectspace.rs | 24 +++ src/policy/lockfreeimmortalspace.rs | 24 +++ src/policy/markcompactspace.rs | 24 +++ src/policy/marksweepspace/malloc_ms/global.rs | 24 +++ src/policy/marksweepspace/native_ms/global.rs | 27 ++++ src/policy/space.rs | 4 +- src/policy/vmspace.rs | 24 +++ src/util/heap/freelistpageresource.rs | 1 - src/util/heap/inspection.rs | 146 ++++++++++++++++++ src/util/heap/inspection/mod.rs | 52 ------- src/util/heap/mod.rs | 1 + src/util/linear_scan.rs | 21 +++ .../mock_test_heap_inspector_all.rs | 83 ++++++++++ ...r.rs => mock_test_heap_inspector_immix.rs} | 27 ++-- src/vm/tests/mock_tests/mod.rs | 5 +- 19 files changed, 480 insertions(+), 96 deletions(-) create mode 100644 src/util/heap/inspection.rs delete mode 100644 src/util/heap/inspection/mod.rs create mode 100644 src/vm/tests/mock_tests/mock_test_heap_inspector_all.rs rename src/vm/tests/mock_tests/{mock_test_heap_inspector.rs => mock_test_heap_inspector_immix.rs} (86%) diff --git a/src/mmtk.rs b/src/mmtk.rs index 05f2df18fd..cc1c511cff 100644 --- a/src/mmtk.rs +++ b/src/mmtk.rs @@ -598,12 +598,12 @@ impl MMTK { .initialize_object_metadata(object, false) } - pub fn inspect_spaces<'a>(&'a self) -> Vec<&'a dyn SpaceInspector> { + /// Inspect MMTk spaces. The space inspector allows users to inspect the heap hierarchically, + /// with all levels of regions. Users can further inspect objects in the regions if vo_bit is enabled. + pub fn inspect_spaces(&self) -> Vec<&dyn SpaceInspector> { let mut ret = vec![]; self.get_plan().for_each_space(&mut |space| { - if let Some(inspector) = space.as_inspector() { - ret.push(inspector); - } + ret.push(space.as_inspector()); }); ret } diff --git a/src/policy/copyspace.rs b/src/policy/copyspace.rs index bbc66ccd4d..318333100e 100644 --- a/src/policy/copyspace.rs +++ b/src/policy/copyspace.rs @@ -111,6 +111,10 @@ impl Space for CopySpace { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } + fn get_page_resource(&self) -> &dyn PageResource { &self.pr } @@ -160,6 +164,22 @@ impl crate::policy::gc_work::PolicyTraceObject for CopySpace< } } +use crate::util::heap::inspection::{RegionInspector, SpaceInspector}; +impl SpaceInspector for CopySpace { + fn list_top_regions(&self) -> Vec> { + crate::util::heap::inspection::into_regions::( + &mut self.pr.iterate_allocated_regions(), + ) + } + + fn list_sub_regions( + &self, + _parent_region: &dyn RegionInspector, + ) -> Vec> { + vec![] + } +} + impl CopySpace { pub fn new(args: crate::policy::space::PlanCreateSpaceArgs, from_space: bool) -> Self { let vm_map = args.vm_map; diff --git a/src/policy/immix/immixspace.rs b/src/policy/immix/immixspace.rs index d70ad94332..067868ba90 100644 --- a/src/policy/immix/immixspace.rs +++ b/src/policy/immix/immixspace.rs @@ -167,8 +167,8 @@ impl Space for ImmixSpace { fn as_sft(&self) -> &(dyn SFT + Sync + 'static) { self } - fn as_inspector(&self) -> Option<&dyn crate::util::heap::inspection::SpaceInspector> { - Some(self) + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self } fn get_page_resource(&self) -> &dyn PageResource { &self.pr @@ -412,7 +412,7 @@ impl ImmixSpace { if self.common.needs_log_bit { if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC { for chunk in self.chunk_map.all_chunks() { - side.bzero_metadata(chunk .start(), Chunk::BYTES); + side.bzero_metadata(chunk.start(), Chunk::BYTES); } } } @@ -1184,24 +1184,28 @@ impl ClearVOBitsAfterPrepare { mod inspector { use super::*; - use crate::util::heap::inspection::{RegionInspector, SpaceInspector, list_child_regions}; + use crate::util::heap::inspection::{list_sub_regions, RegionInspector, SpaceInspector}; impl SpaceInspector for ImmixSpace { - fn name(&self) -> &str { - SFT::name(self) + fn list_top_regions(&self) -> Vec> { + self.chunk_map + .all_chunks() + .map(|r: Chunk| Box::new(r) as Box) + .collect() } - fn list_regions(&self, parent_region: Option<&dyn RegionInspector>) -> Vec> { - if let Some(parent_region) = parent_region { - list_child_regions::(parent_region).or_else(|| { - if !crate::policy::immix::BLOCK_ONLY { - list_child_regions::(parent_region) - } else { - None - } - }).unwrap_or_else(|| vec![]) - } else { - self.chunk_map.all_chunks().map(|r: Chunk| Box::new(r) as Box).collect() + fn list_sub_regions( + &self, + parent_region: &dyn RegionInspector, + ) -> Vec> { + if let Some(regions) = list_sub_regions::(parent_region) { + return regions; + } + if !crate::policy::immix::BLOCK_ONLY { + if let Some(regions) = list_sub_regions::(parent_region) { + return regions; + } } + vec![] } } } diff --git a/src/policy/immortalspace.rs b/src/policy/immortalspace.rs index c08ffeeaaa..208fe65930 100644 --- a/src/policy/immortalspace.rs +++ b/src/policy/immortalspace.rs @@ -96,6 +96,9 @@ impl Space for ImmortalSpace { fn as_sft(&self) -> &(dyn SFT + Sync + 'static) { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } fn get_page_resource(&self) -> &dyn PageResource { &self.pr } @@ -227,3 +230,23 @@ impl ImmortalSpace { object } } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{RegionInspector, SpaceInspector}; + + impl SpaceInspector for ImmortalSpace { + fn list_top_regions(&self) -> Vec> { + crate::util::heap::inspection::into_regions::( + &mut self.pr.iterate_allocated_regions(), + ) + } + + fn list_sub_regions( + &self, + _parent_region: &dyn RegionInspector, + ) -> Vec> { + vec![] + } + } +} diff --git a/src/policy/largeobjectspace.rs b/src/policy/largeobjectspace.rs index d9f274d479..8df4716f4c 100644 --- a/src/policy/largeobjectspace.rs +++ b/src/policy/largeobjectspace.rs @@ -154,6 +154,9 @@ impl Space for LargeObjectSpace { fn as_sft(&self) -> &(dyn SFT + Sync + 'static) { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } fn get_page_resource(&self) -> &dyn PageResource { &self.pr } @@ -380,3 +383,24 @@ impl LargeObjectSpace { fn get_super_page(cell: Address) -> Address { cell.align_down(BYTES_IN_PAGE) } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{RegionInspector, SpaceInspector}; + + impl SpaceInspector for LargeObjectSpace { + fn list_top_regions(&self) -> Vec> { + let space = unsafe { &*(self as *const Self) }; + vec![Box::new(crate::util::heap::inspection::SpaceAsRegion::new( + space, + ))] + } + + fn list_sub_regions( + &self, + _parent_region: &dyn RegionInspector, + ) -> Vec> { + vec![] + } + } +} diff --git a/src/policy/lockfreeimmortalspace.rs b/src/policy/lockfreeimmortalspace.rs index 76e44c55ee..a36b75ef09 100644 --- a/src/policy/lockfreeimmortalspace.rs +++ b/src/policy/lockfreeimmortalspace.rs @@ -108,6 +108,9 @@ impl Space for LockFreeImmortalSpace { fn as_sft(&self) -> &(dyn SFT + Sync + 'static) { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } fn get_page_resource(&self) -> &dyn PageResource { unimplemented!() } @@ -271,3 +274,24 @@ impl LockFreeImmortalSpace { space } } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{RegionInspector, SpaceInspector}; + + impl SpaceInspector for LockFreeImmortalSpace { + fn list_top_regions(&self) -> Vec> { + let space = unsafe { &*(self as *const Self) }; + vec![Box::new(crate::util::heap::inspection::SpaceAsRegion::new( + space, + ))] + } + + fn list_sub_regions( + &self, + _parent_region: &dyn RegionInspector, + ) -> Vec> { + vec![] + } + } +} diff --git a/src/policy/markcompactspace.rs b/src/policy/markcompactspace.rs index d7ca0c0745..6092f7cd6a 100644 --- a/src/policy/markcompactspace.rs +++ b/src/policy/markcompactspace.rs @@ -113,6 +113,10 @@ impl Space for MarkCompactSpace { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } + fn get_page_resource(&self) -> &dyn PageResource { &self.pr } @@ -438,3 +442,23 @@ impl crate::util::linear_scan::LinearScanObjectSize for MarkCompa VM::VMObjectModel::get_current_size(object) } } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{RegionInspector, SpaceInspector}; + + impl SpaceInspector for MarkCompactSpace { + fn list_top_regions(&self) -> Vec> { + crate::util::heap::inspection::into_regions::( + &mut self.pr.iterate_allocated_regions(), + ) + } + + fn list_sub_regions( + &self, + _parent_region: &dyn RegionInspector, + ) -> Vec> { + vec![] + } + } +} diff --git a/src/policy/marksweepspace/malloc_ms/global.rs b/src/policy/marksweepspace/malloc_ms/global.rs index fd473dd0ee..d617a85158 100644 --- a/src/policy/marksweepspace/malloc_ms/global.rs +++ b/src/policy/marksweepspace/malloc_ms/global.rs @@ -149,6 +149,10 @@ impl Space for MallocSpace { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } + fn get_page_resource(&self) -> &dyn PageResource { unreachable!() } @@ -895,3 +899,23 @@ impl GCWork for MSSweepChunk { self.ms.sweep_chunk(self.chunk); } } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{RegionInspector, SpaceInspector}; + impl SpaceInspector for MallocSpace { + fn list_top_regions(&self) -> Vec> { + self.chunk_map + .all_chunks() + .map(|r: Chunk| Box::new(r) as Box) + .collect() + } + + fn list_sub_regions( + &self, + _parent_region: &dyn RegionInspector, + ) -> Vec> { + vec![] + } + } +} diff --git a/src/policy/marksweepspace/native_ms/global.rs b/src/policy/marksweepspace/native_ms/global.rs index 1dc87cd04c..1cf5600bd3 100644 --- a/src/policy/marksweepspace/native_ms/global.rs +++ b/src/policy/marksweepspace/native_ms/global.rs @@ -231,6 +231,10 @@ impl Space for MarkSweepSpace { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } + fn get_page_resource(&self) -> &dyn crate::util::heap::PageResource { &self.pr } @@ -667,3 +671,26 @@ impl Drop for RecycleBlocks { epilogue::debug_assert_counter_zero(&self.counter, "RecycleBlocks::counter"); } } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{list_sub_regions, RegionInspector, SpaceInspector}; + impl SpaceInspector for MarkSweepSpace { + fn list_top_regions(&self) -> Vec> { + self.chunk_map + .all_chunks() + .map(|r: Chunk| Box::new(r) as Box) + .collect() + } + + fn list_sub_regions( + &self, + parent_region: &dyn RegionInspector, + ) -> Vec> { + if let Some(regions) = list_sub_regions::(parent_region) { + return regions; + } + vec![] + } + } +} diff --git a/src/policy/space.rs b/src/policy/space.rs index 28350c928c..99e88ef5d7 100644 --- a/src/policy/space.rs +++ b/src/policy/space.rs @@ -43,9 +43,7 @@ use downcast_rs::Downcast; pub trait Space: 'static + SFT + Sync + Downcast { fn as_space(&self) -> &dyn Space; fn as_sft(&self) -> &(dyn SFT + Sync + 'static); - fn as_inspector(&self) -> Option<&dyn SpaceInspector> { - None - } + fn as_inspector(&self) -> &dyn SpaceInspector; fn get_page_resource(&self) -> &dyn PageResource; /// Get a mutable reference to the underlying page resource, or `None` if the space does not diff --git a/src/policy/vmspace.rs b/src/policy/vmspace.rs index ab1101aaf5..6de4849db5 100644 --- a/src/policy/vmspace.rs +++ b/src/policy/vmspace.rs @@ -99,6 +99,9 @@ impl Space for VMSpace { fn as_sft(&self) -> &(dyn SFT + Sync + 'static) { self } + fn as_inspector(&self) -> &dyn crate::util::heap::inspection::SpaceInspector { + self + } fn get_page_resource(&self) -> &dyn PageResource { &self.pr } @@ -300,3 +303,24 @@ impl VMSpace { object } } + +mod inspector { + use super::*; + use crate::util::heap::inspection::{RegionInspector, SpaceInspector}; + + impl SpaceInspector for VMSpace { + fn list_top_regions(&self) -> Vec> { + let space = unsafe { &*(self as *const Self) }; + vec![Box::new(crate::util::heap::inspection::SpaceAsRegion::new( + space, + ))] + } + + fn list_sub_regions( + &self, + _parent_region: &dyn RegionInspector, + ) -> Vec> { + vec![] + } + } +} diff --git a/src/util/heap/freelistpageresource.rs b/src/util/heap/freelistpageresource.rs index aa66fe232d..852a1361c2 100644 --- a/src/util/heap/freelistpageresource.rs +++ b/src/util/heap/freelistpageresource.rs @@ -93,7 +93,6 @@ impl PageResource for FreeListPageResource { }; new_chunk = true; } - if page_offset == freelist::FAILURE { return Result::Err(PRAllocFail); } else { diff --git a/src/util/heap/inspection.rs b/src/util/heap/inspection.rs new file mode 100644 index 0000000000..5e4476adeb --- /dev/null +++ b/src/util/heap/inspection.rs @@ -0,0 +1,146 @@ +use crate::policy::sft::SFT; +use crate::policy::space::Space; +use crate::util::linear_scan::RegionIterator; +#[cfg(feature = "vo_bit")] +use crate::util::ObjectReference; +use crate::util::{linear_scan::Region, Address}; + +/// SpaceInspector allows users to inspect the heap in a hierarchical structure. +pub trait SpaceInspector: SFT +where + Self: 'static, +{ + /// The name of this space, given by the plan. + fn space_name(&self) -> &str { + SFT::name(self) + } + /// The name of this policy. + fn policy_name(&self) -> &str { + std::any::type_name::() + } + /// List the top-level regions used by this space. This is usually [`crate::util::heap::chunk_map::Chunk`] for most spaces. + /// If there is no region used by the space at the moment, it returns an empty Vector. + fn list_top_regions(&self) -> Vec>; + /// List sub regions of the given parent region if the space organises its heap in a heirarchical way. + /// The parent region could be the results from [`SpaceInspector::list_top_regions`] or the results from [`SpaceInspector::list_sub_regions`]. + /// If there is no sub regions for the given region, it returns an empty Vector. + fn list_sub_regions( + &self, + parent_region: &dyn RegionInspector, + ) -> Vec>; +} + +/// For the given region inspector, if it matches the PARENT region, return the sub regions of the CHILD region type. +/// Otherwise, return None. +pub(crate) fn list_sub_regions( + region: &dyn RegionInspector, +) -> Option>> { + if region.region_type() == std::any::type_name::() { + let start_child_region = CHILD::from_aligned_address(region.start()); + let end_child_region = CHILD::from_aligned_address(region.start() + region.size()); + Some( + RegionIterator::::new(start_child_region, end_child_region) + .map(|r| Box::new(r) as Box) + .collect(), + ) + } else { + None + } +} + +/// Convert an iterator of pairs of (region start, region size) into a vector of region inspector. +pub(crate) fn into_regions( + regions: &mut dyn Iterator, +) -> Vec> { + regions + .flat_map(|(start, size)| { + let mut current = start; + let end = start + size; + std::iter::from_fn(move || { + if current >= end { + return None; + } + let region = R::from_aligned_address(current); + current += R::BYTES; + Some(Box::new(region) as Box) + }) + }) + .collect() +} + +/// RegionInspector allows users to inspect a region of the heap. +pub trait RegionInspector { + /// The type of this region. + fn region_type(&self) -> &str; + /// The start address of this region. + fn start(&self) -> Address; + /// The byte size of this region. + fn size(&self) -> usize; + #[cfg(feature = "vo_bit")] + /// List all objects in this region. This is only available when `vo_bit` feature is enabled. + fn list_objects(&self) -> Vec { + let mut objects = vec![]; + crate::util::metadata::side_metadata::spec_defs::VO_BIT.scan_non_zero_values::( + self.start(), + self.start() + self.size(), + &mut |address| { + use crate::util::metadata::vo_bit; + let object = vo_bit::get_object_ref_for_vo_addr(address); + objects.push(object); + }, + ); + objects + } +} + +impl RegionInspector for R { + fn region_type(&self) -> &str { + std::any::type_name::() + } + + fn start(&self) -> Address { + Region::start(self) + } + + fn size(&self) -> usize { + Self::BYTES + } +} + +use crate::vm::VMBinding; +/// SpaceAsRegion is a special RegionInspector. Some spaces do not organize its space memory with regions. +/// For those spaces, they simply return this type as the top-level region inspector so users can inspect +/// such spaces in the same way as other spaces that use regions. +pub(crate) struct SpaceAsRegion { + space: &'static dyn Space, +} + +impl SpaceAsRegion { + pub fn new(space: &'static dyn Space) -> Self { + Self { space } + } +} + +impl RegionInspector for SpaceAsRegion { + fn region_type(&self) -> &str { + std::any::type_name::>() + } + + fn start(&self) -> Address { + Address::ZERO + } + + fn size(&self) -> usize { + 0 + } + #[cfg(feature = "vo_bit")] + fn list_objects(&self) -> Vec { + let mut res = vec![]; + let mut enumerator = + crate::util::object_enum::ClosureObjectEnumerator::<_, VM>::new(|object| { + res.push(object); + }); + self.space.enumerate_objects(&mut enumerator); + res + } +} diff --git a/src/util/heap/inspection/mod.rs b/src/util/heap/inspection/mod.rs deleted file mode 100644 index 0b0ddde8d5..0000000000 --- a/src/util/heap/inspection/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::policy::sft::SFT; -use crate::util::linear_scan::RegionIterator; -use crate::util::{linear_scan::Region, Address, ObjectReference}; -use crate::util::metadata::side_metadata::spec_defs::VO_BIT; -use crate::util::heap::chunk_map::Chunk; - -pub trait SpaceInspector { - fn name(&self) -> &str; - fn list_regions(&self, parent_region: Option<&dyn RegionInspector>) -> Vec>; -} - -pub(crate) fn list_child_regions(region: &dyn RegionInspector) -> Option>> { - if region.region_type() == std::any::type_name::() { - let start_child_region = CHILD::from_aligned_address(region.start()); - let end_child_region = CHILD::from_aligned_address(region.start() + region.size()); - Some(RegionIterator::::new(start_child_region, end_child_region) - .map(|r| Box::new(r) as Box) - .collect()) - } else { - None - } -} - -pub trait RegionInspector { - fn region_type(&self) -> &str; - fn start(&self) -> Address; - fn size(&self) -> usize; - #[cfg(feature = "vo_bit")] - fn list_objects(&self) -> Vec { - let mut objects = vec![]; - VO_BIT.scan_non_zero_values::(self.start(), self.start() + self.size(), &mut |address| { - use crate::util::metadata::vo_bit; - let object = vo_bit::get_object_ref_for_vo_addr(address); - objects.push(object); - }); - objects - } -} - -impl RegionInspector for R { - fn region_type(&self) -> &str { - std::any::type_name::() - } - - fn start(&self) -> Address { - Region::start(self) - } - - fn size(&self) -> usize { - Self::BYTES - } -} diff --git a/src/util/heap/mod.rs b/src/util/heap/mod.rs index 11512bc94c..5b507cc594 100644 --- a/src/util/heap/mod.rs +++ b/src/util/heap/mod.rs @@ -23,4 +23,5 @@ pub(crate) use self::monotonepageresource::MonotonePageResource; pub(crate) use self::pageresource::PageResource; pub(crate) use self::vmrequest::VMRequest; +/// Heap inspection API. pub mod inspection; diff --git a/src/util/linear_scan.rs b/src/util/linear_scan.rs index ec39379afd..5e63dfa7a4 100644 --- a/src/util/linear_scan.rs +++ b/src/util/linear_scan.rs @@ -135,6 +135,27 @@ pub trait Region: Copy + PartialEq + PartialOrd { } } +/// Data structure to reference a MMTk 4KB page. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Eq)] +pub struct Page(Address); + +impl Region for Page { + const LOG_BYTES: usize = crate::util::constants::LOG_BYTES_IN_PAGE as usize; + + fn from_aligned_address(address: Address) -> Self { + debug_assert!(address.is_aligned_to(Self::BYTES)); + Self(address) + } + + fn start(&self) -> Address { + self.0 + } +} + +// Re-export Chunk. +pub use crate::util::heap::chunk_map::Chunk; + /// An iterator for contiguous regions. pub struct RegionIterator { current: R, diff --git a/src/vm/tests/mock_tests/mock_test_heap_inspector_all.rs b/src/vm/tests/mock_tests/mock_test_heap_inspector_all.rs new file mode 100644 index 0000000000..608cd08e2a --- /dev/null +++ b/src/vm/tests/mock_tests/mock_test_heap_inspector_all.rs @@ -0,0 +1,83 @@ +// GITHUB-CI: MMTK_PLAN=all +// GITHUB-CI: FEATURES=vo_bit + +use std::collections::HashSet; + +use constants::BYTES_IN_WORD; + +use super::mock_test_prelude::*; + +use crate::{util::*, AllocationSemantics, MMTK}; + +lazy_static! { + static ref FIXTURE: Fixture = Fixture::new(); +} + +pub fn get_all_objects(mmtk: &'static MMTK) -> HashSet { + let mut result = HashSet::new(); + let space_inspectors = mmtk.inspect_spaces(); + assert!(space_inspectors.len() > 0); + space_inspectors.iter().for_each(|s| { + let mut regions = s.list_top_regions(); + while regions.len() > 0 { + let region = regions.pop().unwrap(); + let mut sub_regions = s.list_sub_regions(&*region); + if sub_regions.len() > 0 { + // If we have sub regions keep looking at them + regions.append(&mut sub_regions); + } else { + // Otherwise, we are at the leaf level, listing objects. + for object in region.list_objects() { + assert!(!result.contains(&object)); + result.insert(object); + } + } + } + }); + return result; +} + +#[test] +pub fn test_heap_inspector_all() { + with_mockvm( + default_setup, + || { + FIXTURE.with_fixture_mut(|fixture| { + let mmtk = fixture.mmtk(); + let mutator = &mut fixture.mutator; + + let mut new_obj = |size: usize, semantics: AllocationSemantics| { + let align = BYTES_IN_WORD; + let start = memory_manager::alloc(mutator, size, align, 0, semantics); + let object = MockVM::object_start_to_ref(start); + memory_manager::post_alloc(mutator, object, size, semantics); + object + }; + + let mut known_objects = HashSet::new(); + + let mut new_and_assert = |size: usize, semantics: AllocationSemantics| { + let object = new_obj(size, semantics); // a random size + known_objects.insert(object); + let traversal = get_all_objects(mmtk); + assert_eq!(traversal, known_objects); + }; + + { + use AllocationSemantics::*; + + // Add some single objects. Size doesn't matter. + println!("Allocating object 40"); + new_and_assert(40, Default); + println!("Allocating object 64"); + new_and_assert(64, Default); + println!("Allocating object 96"); + new_and_assert(96, Immortal); + println!("Allocating object 131000"); + new_and_assert(131000, Los); + } + }); + }, + no_cleanup, + ) +} diff --git a/src/vm/tests/mock_tests/mock_test_heap_inspector.rs b/src/vm/tests/mock_tests/mock_test_heap_inspector_immix.rs similarity index 86% rename from src/vm/tests/mock_tests/mock_test_heap_inspector.rs rename to src/vm/tests/mock_tests/mock_test_heap_inspector_immix.rs index 190708176f..66557b6cce 100644 --- a/src/vm/tests/mock_tests/mock_test_heap_inspector.rs +++ b/src/vm/tests/mock_tests/mock_test_heap_inspector_immix.rs @@ -1,28 +1,18 @@ // GITHUB-CI: MMTK_PLAN=Immix // GITHUB-CI: FEATURES=vo_bit -use std::collections::HashSet; - use constants::BYTES_IN_WORD; use super::mock_test_prelude::*; -use crate::{util::*, AllocationSemantics, MMTK}; +use crate::{util::*, AllocationSemantics}; lazy_static! { static ref FIXTURE: Fixture = Fixture::new(); } -pub fn get_all_objects(mmtk: &'static MMTK) -> HashSet { - let mut result = HashSet::new(); - mmtk.enumerate_objects(|object| { - result.insert(object); - }); - result -} - #[test] -pub fn test_heap_inspector() { +pub fn test_heap_inspector_immix() { with_mockvm( default_setup, || { @@ -33,12 +23,13 @@ pub fn test_heap_inspector() { assert!(space_inspector.len() > 0); let get_immix_inspector = || { - space_inspector.iter().find(|s| s.name() == "immix").unwrap() + space_inspector.iter().find(|s| s.space_name() == "immix").unwrap() }; { let immix_space_inspector = get_immix_inspector(); - let chunk_inspector = immix_space_inspector.list_regions(None); + assert_eq!(immix_space_inspector.policy_name(), "mmtk::policy::immix::immixspace::ImmixSpace"); + let chunk_inspector = immix_space_inspector.list_top_regions(); assert_eq!(chunk_inspector.len(), 0); } @@ -56,21 +47,21 @@ pub fn test_heap_inspector() { { let immix_space_inspector = get_immix_inspector(); // Check chunks - let chunk_inspector = immix_space_inspector.list_regions(None); + let chunk_inspector = immix_space_inspector.list_top_regions(); assert_eq!(chunk_inspector.len(), 1); assert_eq!(chunk_inspector[0].region_type(), "mmtk::util::heap::chunk_map::Chunk"); let objects = chunk_inspector[0].list_objects(); assert_eq!(objects.len(), 1); assert_eq!(objects[0], object); // Check blocks - let block_inspector = immix_space_inspector.list_regions(Some(&*chunk_inspector[0])); + let block_inspector = immix_space_inspector.list_sub_regions(&*chunk_inspector[0]); assert_eq!(block_inspector.len(), 128); // 128 blocks in a chunk assert_eq!(block_inspector[0].region_type(), "mmtk::policy::immix::block::Block"); let objects = block_inspector[0].list_objects(); assert_eq!(objects.len(), 1); assert_eq!(objects[0], object); // Check lines - let line_inspector = immix_space_inspector.list_regions(Some(&*block_inspector[0])); + let line_inspector = immix_space_inspector.list_sub_regions(&*block_inspector[0]); assert_eq!(line_inspector.len(), 128); // 128 lines in a block assert_eq!(line_inspector[0].region_type(), "mmtk::policy::immix::line::Line"); let objects = line_inspector[0].list_objects(); @@ -84,7 +75,7 @@ pub fn test_heap_inspector() { { let immix_space_inspector = get_immix_inspector(); // Check checks - let chunk_inspector = immix_space_inspector.list_regions(None); + let chunk_inspector = immix_space_inspector.list_top_regions(); assert_eq!(chunk_inspector.len(), 1); assert_eq!(chunk_inspector[0].region_type(), "mmtk::util::heap::chunk_map::Chunk"); let objects = chunk_inspector[0].list_objects(); diff --git a/src/vm/tests/mock_tests/mod.rs b/src/vm/tests/mock_tests/mod.rs index a932ef05fa..15d7eec32a 100644 --- a/src/vm/tests/mock_tests/mod.rs +++ b/src/vm/tests/mock_tests/mod.rs @@ -39,8 +39,11 @@ mod mock_test_conservatism; mod mock_test_handle_mmap_conflict; mod mock_test_handle_mmap_oom; #[cfg(feature = "vo_bit")] +mod mock_test_heap_inspector_all; +#[cfg(feature = "vo_bit")] +mod mock_test_heap_inspector_immix; +#[cfg(feature = "vo_bit")] mod mock_test_heap_traversal; -mod mock_test_heap_inspector; mod mock_test_init_fork; #[cfg(feature = "is_mmtk_object")] mod mock_test_internal_ptr_before_object_ref;