diff --git a/examples/mmtk.h b/examples/mmtk.h index 0b4cfb528c..6f0843d729 100644 --- a/examples/mmtk.h +++ b/examples/mmtk.h @@ -107,13 +107,13 @@ extern void* mmtk_starting_heap_address(); extern void* mmtk_last_heap_address(); // Add a reference to the list of weak references -extern void mmtk_add_weak_candidate(void* ref, void* referent); +extern void mmtk_add_weak_candidate(void* ref); // Add a reference to the list of soft references -extern void mmtk_add_soft_candidate(void* ref, void* referent); +extern void mmtk_add_soft_candidate(void* ref); // Add a reference to the list of phantom references -extern void mmtk_add_phantom_candidate(void* ref, void* referent); +extern void mmtk_add_phantom_candidate(void* ref); // Generic hook to allow benchmarks to be harnessed extern void mmtk_harness_begin(void* tls); diff --git a/src/memory_manager.rs b/src/memory_manager.rs index 907042828e..42831f725e 100644 --- a/src/memory_manager.rs +++ b/src/memory_manager.rs @@ -416,49 +416,34 @@ pub fn modify_check(mmtk: &MMTK, object: ObjectReference) { mmtk.plan.modify_check(object); } -/// Add a reference to the list of weak references. +/// Add a reference to the list of weak references. A binding may +/// call this either when a weak reference is created, or when a weak reference is traced during GC. /// /// Arguments: /// * `mmtk`: A reference to an MMTk instance. /// * `reff`: The weak reference to add. -/// * `referent`: The object that the reference points to. -pub fn add_weak_candidate( - mmtk: &MMTK, - reff: ObjectReference, - referent: ObjectReference, -) { - mmtk.reference_processors - .add_weak_candidate::(reff, referent); +pub fn add_weak_candidate(mmtk: &MMTK, reff: ObjectReference) { + mmtk.reference_processors.add_weak_candidate::(reff); } -/// Add a reference to the list of soft references. +/// Add a reference to the list of soft references. A binding may +/// call this either when a weak reference is created, or when a weak reference is traced during GC. /// /// Arguments: /// * `mmtk`: A reference to an MMTk instance. /// * `reff`: The soft reference to add. -/// * `referent`: The object that the reference points to. -pub fn add_soft_candidate( - mmtk: &MMTK, - reff: ObjectReference, - referent: ObjectReference, -) { - mmtk.reference_processors - .add_soft_candidate::(reff, referent); +pub fn add_soft_candidate(mmtk: &MMTK, reff: ObjectReference) { + mmtk.reference_processors.add_soft_candidate::(reff); } -/// Add a reference to the list of phantom references. +/// Add a reference to the list of phantom references. A binding may +/// call this either when a weak reference is created, or when a weak reference is traced during GC. /// /// Arguments: /// * `mmtk`: A reference to an MMTk instance. /// * `reff`: The phantom reference to add. -/// * `referent`: The object that the reference points to. -pub fn add_phantom_candidate( - mmtk: &MMTK, - reff: ObjectReference, - referent: ObjectReference, -) { - mmtk.reference_processors - .add_phantom_candidate::(reff, referent); +pub fn add_phantom_candidate(mmtk: &MMTK, reff: ObjectReference) { + mmtk.reference_processors.add_phantom_candidate::(reff); } /// Generic hook to allow benchmarks to be harnessed. We do a full heap diff --git a/src/mmtk.rs b/src/mmtk.rs index 362d77e4b6..acda13114a 100644 --- a/src/mmtk.rs +++ b/src/mmtk.rs @@ -101,6 +101,11 @@ impl MMTK { pub fn get_plan(&self) -> &dyn Plan { self.plan.as_ref() } + + #[inline(always)] + pub fn get_options(&self) -> &Options { + &self.options + } } impl Default for MMTK { diff --git a/src/plan/markcompact/global.rs b/src/plan/markcompact/global.rs index 6562b75e72..fe521cdb42 100644 --- a/src/plan/markcompact/global.rs +++ b/src/plan/markcompact/global.rs @@ -97,10 +97,6 @@ impl Plan for MarkCompact { scheduler.work_buckets[WorkBucketStage::Prepare] .add(Prepare::>::new(self)); - // VM-specific weak ref processing - scheduler.work_buckets[WorkBucketStage::RefClosure] - .add(ProcessWeakRefs::>::new()); - scheduler.work_buckets[WorkBucketStage::CalculateForwarding] .add(CalculateForwardingAddress::::new(&self.mc_space)); // do another trace to update references @@ -111,17 +107,41 @@ impl Plan for MarkCompact { scheduler.work_buckets[WorkBucketStage::Release] .add(Release::>::new(self)); + // Reference processing + if !*self.base().options.no_reference_types { + use crate::util::reference_processor::{ + PhantomRefProcessing, SoftRefProcessing, WeakRefProcessing, + }; + scheduler.work_buckets[WorkBucketStage::SoftRefClosure] + .add(SoftRefProcessing::>::new()); + scheduler.work_buckets[WorkBucketStage::WeakRefClosure] + .add(WeakRefProcessing::>::new()); + scheduler.work_buckets[WorkBucketStage::PhantomRefClosure] + .add(PhantomRefProcessing::>::new()); + + // VM-specific weak ref processing + scheduler.work_buckets[WorkBucketStage::WeakRefClosure] + .add(VMProcessWeakRefs::>::new()); + + use crate::util::reference_processor::RefForwarding; + scheduler.work_buckets[WorkBucketStage::RefForwarding] + .add(RefForwarding::>::new()); + + use crate::util::reference_processor::RefEnqueue; + scheduler.work_buckets[WorkBucketStage::Release].add(RefEnqueue::::new()); + } + // Finalization if !*self.base().options.no_finalizer { use crate::util::finalizable_processor::{Finalization, ForwardFinalization}; // finalization // treat finalizable objects as roots and perform a closure (marking) // must be done before calculating forwarding pointers - scheduler.work_buckets[WorkBucketStage::RefClosure] + scheduler.work_buckets[WorkBucketStage::FinalRefClosure] .add(Finalization::>::new()); // update finalizable object references // must be done before compacting - scheduler.work_buckets[WorkBucketStage::RefForwarding] + scheduler.work_buckets[WorkBucketStage::FinalizableForwarding] .add(ForwardFinalization::>::new()); } diff --git a/src/plan/plan_constraints.rs b/src/plan/plan_constraints.rs index a0eea981ee..700f379d56 100644 --- a/src/plan/plan_constraints.rs +++ b/src/plan/plan_constraints.rs @@ -30,6 +30,8 @@ pub struct PlanConstraints { pub needs_linear_scan: bool, pub needs_concurrent_workers: bool, pub generate_gc_trace: bool, + /// Some policies do object forwarding after the first liveness transitive closure, such as mark compact. + /// For plans that use those policies, they should set this as true. pub needs_forward_after_liveness: bool, } diff --git a/src/scheduler/gc_work.rs b/src/scheduler/gc_work.rs index a1f88afc0b..e20a8c6c62 100644 --- a/src/scheduler/gc_work.rs +++ b/src/scheduler/gc_work.rs @@ -121,8 +121,6 @@ impl GCWork for Release { for w in &mmtk.scheduler.workers_shared { w.local_work_bucket.add(ReleaseCollector); } - // TODO: Process weak references properly - mmtk.reference_processors.clear(); } } @@ -250,15 +248,15 @@ impl CoordinatorWork for EndOfGC {} /// processing of those weakrefs may be more complex. For such case, we delegate to the /// VM binding to process weak references. #[derive(Default)] -pub struct ProcessWeakRefs(PhantomData); +pub struct VMProcessWeakRefs(PhantomData); -impl ProcessWeakRefs { +impl VMProcessWeakRefs { pub fn new() -> Self { Self(PhantomData) } } -impl GCWork for ProcessWeakRefs { +impl GCWork for VMProcessWeakRefs { fn do_work(&mut self, worker: &mut GCWorker, _mmtk: &'static MMTK) { trace!("ProcessWeakRefs"); ::VMCollection::process_weak_refs::(worker); @@ -384,6 +382,13 @@ impl ProcessEdgesBase { } /// Scan & update a list of object slots +// +// Note: be very careful when using this trait. process_node() will push objects +// to the buffer, and it is expected that at the end of the operation, flush() +// is called to create new scan work from the buffered objects. If flush() +// is not called, we may miss the objects in the GC and have dangling pointers. +// FIXME: We possibly want to enforce Drop on this trait, and require calling +// flush() in Drop. pub trait ProcessEdgesWork: Send + 'static + Sized + DerefMut + Deref> { diff --git a/src/scheduler/scheduler.rs b/src/scheduler/scheduler.rs index 2fd3a43af2..d39538acd2 100644 --- a/src/scheduler/scheduler.rs +++ b/src/scheduler/scheduler.rs @@ -57,10 +57,14 @@ impl GCWorkScheduler { WorkBucketStage::Unconstrained => WorkBucket::new(true, worker_monitor.clone()), WorkBucketStage::Prepare => WorkBucket::new(false, worker_monitor.clone()), WorkBucketStage::Closure => WorkBucket::new(false, worker_monitor.clone()), - WorkBucketStage::RefClosure => WorkBucket::new(false, worker_monitor.clone()), + WorkBucketStage::SoftRefClosure => WorkBucket::new(false, worker_monitor.clone()), + WorkBucketStage::WeakRefClosure => WorkBucket::new(false, worker_monitor.clone()), + WorkBucketStage::FinalRefClosure => WorkBucket::new(false, worker_monitor.clone()), + WorkBucketStage::PhantomRefClosure => WorkBucket::new(false, worker_monitor.clone()), WorkBucketStage::CalculateForwarding => WorkBucket::new(false, worker_monitor.clone()), WorkBucketStage::SecondRoots => WorkBucket::new(false, worker_monitor.clone()), WorkBucketStage::RefForwarding => WorkBucket::new(false, worker_monitor.clone()), + WorkBucketStage::FinalizableForwarding => WorkBucket::new(false, worker_monitor.clone()), WorkBucketStage::Compact => WorkBucket::new(false, worker_monitor.clone()), WorkBucketStage::Release => WorkBucket::new(false, worker_monitor.clone()), WorkBucketStage::Final => WorkBucket::new(false, worker_monitor.clone()), @@ -78,7 +82,7 @@ impl GCWorkScheduler { let should_open = scheduler.are_buckets_drained(&cur_stages) && scheduler.all_workers_parked(); // Additional check before the `RefClosure` bucket opens. - if should_open && s == WorkBucketStage::RefClosure { + if should_open && s == crate::scheduler::work_bucket::LAST_CLOSURE_BUCKET { if let Some(closure_end) = scheduler.closure_end.lock().unwrap().as_ref() { if closure_end() { // Don't open `RefClosure` if `closure_end` added more works to `Closure`. @@ -92,10 +96,14 @@ impl GCWorkScheduler { }; open_next(Closure); - open_next(RefClosure); + open_next(SoftRefClosure); + open_next(WeakRefClosure); + open_next(FinalRefClosure); + open_next(PhantomRefClosure); open_next(CalculateForwarding); open_next(SecondRoots); open_next(RefForwarding); + open_next(FinalizableForwarding); open_next(Compact); open_next(Release); open_next(Final); @@ -183,10 +191,6 @@ impl GCWorkScheduler { // Prepare global/collectors/mutators self.work_buckets[WorkBucketStage::Prepare].add(Prepare::::new(plan)); - // VM-specific weak ref processing - self.work_buckets[WorkBucketStage::RefClosure] - .add(ProcessWeakRefs::::new()); - // Release global/collectors/mutators self.work_buckets[WorkBucketStage::Release].add(Release::::new(plan)); @@ -205,15 +209,41 @@ impl GCWorkScheduler { .add(ScheduleSanityGC::::new(plan)); } + // Reference processing + if !*plan.base().options.no_reference_types { + use crate::util::reference_processor::{ + PhantomRefProcessing, SoftRefProcessing, WeakRefProcessing, + }; + self.work_buckets[WorkBucketStage::SoftRefClosure] + .add(SoftRefProcessing::::new()); + self.work_buckets[WorkBucketStage::WeakRefClosure] + .add(WeakRefProcessing::::new()); + self.work_buckets[WorkBucketStage::PhantomRefClosure] + .add(PhantomRefProcessing::::new()); + + // VM-specific weak ref processing + self.work_buckets[WorkBucketStage::WeakRefClosure] + .add(VMProcessWeakRefs::::new()); + + use crate::util::reference_processor::RefForwarding; + if plan.constraints().needs_forward_after_liveness { + self.work_buckets[WorkBucketStage::RefForwarding] + .add(RefForwarding::::new()); + } + + use crate::util::reference_processor::RefEnqueue; + self.work_buckets[WorkBucketStage::Release].add(RefEnqueue::::new()); + } + // Finalization if !*plan.base().options.no_finalizer { use crate::util::finalizable_processor::{Finalization, ForwardFinalization}; // finalization - self.work_buckets[WorkBucketStage::RefClosure] + self.work_buckets[WorkBucketStage::FinalRefClosure] .add(Finalization::::new()); // forward refs if plan.constraints().needs_forward_after_liveness { - self.work_buckets[WorkBucketStage::RefForwarding] + self.work_buckets[WorkBucketStage::FinalizableForwarding] .add(ForwardFinalization::::new()); } } diff --git a/src/scheduler/work_bucket.rs b/src/scheduler/work_bucket.rs index db52cd1fed..269d2f4f07 100644 --- a/src/scheduler/work_bucket.rs +++ b/src/scheduler/work_bucket.rs @@ -154,13 +154,17 @@ pub enum WorkBucketStage { Unconstrained, Prepare, Closure, - // TODO: We only support final reference at the moment. If we have references of multiple strengths, - // we may need more than one buckets for each reference strength. - RefClosure, + SoftRefClosure, + WeakRefClosure, + FinalRefClosure, + PhantomRefClosure, CalculateForwarding, SecondRoots, RefForwarding, + FinalizableForwarding, Compact, Release, Final, } + +pub const LAST_CLOSURE_BUCKET: WorkBucketStage = WorkBucketStage::PhantomRefClosure; diff --git a/src/util/address.rs b/src/util/address.rs index d501f38377..a0505a2662 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -453,6 +453,8 @@ mod tests { pub struct ObjectReference(usize); impl ObjectReference { + pub const NULL: ObjectReference = ObjectReference(0); + /// converts the ObjectReference to an Address #[inline(always)] pub fn to_address(self) -> Address { diff --git a/src/util/options.rs b/src/util/options.rs index 2463b6ecaa..78aa396ec9 100644 --- a/src/util/options.rs +++ b/src/util/options.rs @@ -331,7 +331,11 @@ options! { // Should finalization be disabled? no_finalizer: bool [env_var: true, command_line: true] [always_valid] = false, // Should reference type processing be disabled? - no_reference_types: bool [env_var: true, command_line: true] [always_valid] = false, + // If reference type processing is disabled, no weak reference processing work is scheduled, + // and we expect a binding to treat weak references as strong references. + // We disable weak reference processing by default, as we are still working on it. This will be changed to `false` + // once weak reference processing is implemented properly. + no_reference_types: bool [env_var: true, command_line: true] [always_valid] = true, // The zeroing approach to use for new object allocations. Affects each plan differently. (not supported) nursery_zeroing: NurseryZeroingOptions[env_var: true, command_line: true] [always_valid] = NurseryZeroingOptions::Temporal, // How frequent (every X bytes) should we do a stress GC? diff --git a/src/util/reference_processor.rs b/src/util/reference_processor.rs index 17d279e15e..7ab4c9c31b 100644 --- a/src/util/reference_processor.rs +++ b/src/util/reference_processor.rs @@ -1,13 +1,18 @@ -use std::cell::UnsafeCell; +use std::collections::HashSet; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; use std::sync::Mutex; use std::vec::Vec; -use crate::plan::TraceLocal; -use crate::util::opaque_pointer::*; -use crate::util::{Address, ObjectReference}; +use crate::scheduler::ProcessEdgesWork; +use crate::util::ObjectReference; +use crate::util::VMWorkerThread; use crate::vm::ReferenceGlue; use crate::vm::VMBinding; +/// Holds all reference processors for each weak reference Semantics. +/// Currently this is based on Java's weak reference semantics (soft/weak/phantom). +/// We should make changes to make this general rather than Java specific. pub struct ReferenceProcessors { soft: ReferenceProcessor, weak: ReferenceProcessor, @@ -31,57 +36,80 @@ impl ReferenceProcessors { } } - pub fn clear(&self) { - self.soft.clear(); - self.weak.clear(); - self.phantom.clear(); + pub fn add_soft_candidate(&self, reff: ObjectReference) { + trace!("Add soft candidate: {}", reff); + self.soft.add_candidate::(reff); } - pub fn add_soft_candidate( - &self, - reff: ObjectReference, - referent: ObjectReference, - ) { - self.soft.add_candidate::(reff, referent); + pub fn add_weak_candidate(&self, reff: ObjectReference) { + trace!("Add weak candidate: {}", reff); + self.weak.add_candidate::(reff); } - pub fn add_weak_candidate( - &self, - reff: ObjectReference, - referent: ObjectReference, - ) { - self.weak.add_candidate::(reff, referent); + pub fn add_phantom_candidate(&self, reff: ObjectReference) { + trace!("Add phantom candidate: {}", reff); + self.phantom.add_candidate::(reff); } - pub fn add_phantom_candidate( - &self, - reff: ObjectReference, - referent: ObjectReference, - ) { - self.phantom.add_candidate::(reff, referent); + /// This will invoke enqueue for each reference processor, which will + /// call back to the VM to enqueue references whose referents are cleared + /// in this GC. + pub fn enqueue_refs(&self, tls: VMWorkerThread) { + self.soft.enqueue::(tls); + self.weak.enqueue::(tls); + self.phantom.enqueue::(tls); } - pub fn forward_refs(&self, trace: &mut T) { - self.soft.forward::(trace, false); - self.weak.forward::(trace, false); - self.phantom.forward::(trace, false); + /// A separate reference forwarding step. Normally when we scan refs, we deal with forwarding. + /// However, for some plans like mark compact, at the point we do ref scanning, we do not know + /// the forwarding addresses yet, thus we cannot do forwarding during scan refs. And for those + /// plans, this separate step is required. + pub fn forward_refs(&self, trace: &mut E, mmtk: &'static MMTK) { + debug_assert!( + mmtk.plan.constraints().needs_forward_after_liveness, + "A plan with needs_forward_after_liveness=false does not need a separate forward step" + ); + self.soft + .forward::(trace, mmtk.plan.is_current_gc_nursery()); + self.weak + .forward::(trace, mmtk.plan.is_current_gc_nursery()); + self.phantom + .forward::(trace, mmtk.plan.is_current_gc_nursery()); } - pub fn scan_weak_refs(&self, trace: &mut T, tls: VMWorkerThread) { - self.soft.scan::(trace, false, false, tls); - self.weak.scan::(trace, false, false, tls); + // Methods for scanning weak references. It needs to be called in a decreasing order of reference strengths, i.e. soft > weak > phantom + + /// Scan soft references. + pub fn scan_soft_refs(&self, trace: &mut E, mmtk: &'static MMTK) { + // For soft refs, it is up to the VM to decide when to reclaim this. + // If this is not an emergency collection, we have no heap stress. We simply retain soft refs. + if !mmtk.plan.is_emergency_collection() { + // This step only retains the referents (keep the referents alive), it does not update its addresses. + // We will call soft.scan() again with retain=false to update its addresses based on liveness. + self.soft + .retain::(trace, mmtk.plan.is_current_gc_nursery()); + } + // This will update the references (and the referents). + self.soft + .scan::(trace, mmtk.plan.is_current_gc_nursery()); } - pub fn scan_soft_refs(&self, trace: &mut T, tls: VMWorkerThread) { - self.soft.scan::(trace, false, false, tls); + /// Scan weak references. + pub fn scan_weak_refs(&self, trace: &mut E, mmtk: &'static MMTK) { + self.soft + .scan::(trace, mmtk.plan.is_current_gc_nursery()); + self.weak + .scan::(trace, mmtk.plan.is_current_gc_nursery()); } - pub fn scan_phantom_refs( + /// Scan phantom references. + pub fn scan_phantom_refs( &self, - trace: &mut T, - tls: VMWorkerThread, + trace: &mut E, + mmtk: &'static MMTK, ) { - self.phantom.scan::(trace, false, false, tls); + self.phantom + .scan::(trace, mmtk.plan.is_current_gc_nursery()); } } @@ -91,12 +119,6 @@ impl Default for ReferenceProcessors { } } -// Debug flags -pub const TRACE: bool = false; -pub const TRACE_UNREACHABLE: bool = false; -pub const TRACE_DETAIL: bool = false; -pub const TRACE_FORWARD: bool = false; - // XXX: We differ from the original implementation // by ignoring "stress," i.e. where the array // of references is grown by 1 each time. We @@ -106,20 +128,34 @@ pub const TRACE_FORWARD: bool = false; // luckily this is also the value used by Java MMTk.) const INITIAL_SIZE: usize = 256; +/// We create a reference processor for each semantics. Generally we expect these +/// to happen for each processor: +/// 1. The VM adds reference candidates. They could either do it when a weak reference +/// is created, or when a weak reference is traced during GC. +/// 2. We scan references after the GC determins liveness. +/// 3. We forward references if the GC needs forwarding after liveness. +/// 4. We inform the binding of references whose referents are cleared during this GC by enqueue'ing. pub struct ReferenceProcessor { - // XXX: To support the possibility of the collector working - // on the reference in parallel, we wrap the structure - // in an UnsafeCell. - sync: UnsafeCell>, - - /** - * Semantics - */ + /// Most of the reference processor is protected by a mutex. + sync: Mutex, + + /// The semantics for the reference processor semantics: Semantics, -} -// TODO: We should carefully examine the unsync with UnsafeCell. We should be able to provide a safe implementation. -unsafe impl Sync for ReferenceProcessor {} + /// Is it allowed to add candidate to this reference processor? The value is true for most of the time, + /// but it is set to false once we finish forwarding references, at which point we do not expect to encounter + /// any 'new' reference in the same GC. This makes sure that no new entry will be added to our reference table once + /// we finish forwarding, as we will not be able to process the entry in that GC. + // This avoids an issue in the following scenario in mark compact: + // 1. First trace: add a candidate WR + // 2. Weak reference scan: scan the reference table, as MC does not forward object in the first trace. This scan does not update any reference. + // 3. Second trace: call add_candidate again with WR, but WR gets ignored as we already have WR in our reference table. + // 4. Weak reference forward: call trace_object for WR, which pushes WR to the node buffer and update WR -> WR' in our reference table. + // 5. When we trace objects in the node buffer, we will attempt to add WR as a candidate. As we have updated WR to WR' in our reference + // table, we would accept WR as a candidate. But we will not trace WR again, and WR will be invalid after this GC. + // This flag is set to false after Step 4, so in Step 5, we will ignore adding WR. + allow_new_candidate: AtomicBool, +} #[derive(Debug, PartialEq)] pub enum Semantics { @@ -129,207 +165,401 @@ pub enum Semantics { } struct ReferenceProcessorSync { - // XXX: A data race on any of these fields is UB. If - // parallelizing this code, change the types to - // have the correct semantics. - /** - * The table of reference objects for the current semantics - */ - references: Vec
, - - /** - * In a MarkCompact (or similar) collector, we need to update the {@code references} - * field, and then update its contents. We implement this by saving the pointer in - * this untraced field for use during the {@code forward} pass. - */ - unforwarded_references: Option>, - - /** - * Index into the references table for the start of - * the reference nursery. - */ + /// The table of reference objects for the current semantics. We add references to this table by + /// add_candidate(). After scanning this table, a reference in the table should either + /// stay in the table (if the referent is alive) or go to enqueued_reference (if the referent is dead and cleared). + /// Note that this table should not have duplicate entries, otherwise we will scan the duplicates multiple times, and + /// that may lead to incorrect results. + references: HashSet, + + /// References whose referents are cleared during this GC. We add references to this table during + /// scanning, and we pop from this table during the enqueue work at the end of GC. + enqueued_references: Vec, + + /// Index into the references table for the start of nursery objects nursery_index: usize, } impl ReferenceProcessor { pub fn new(semantics: Semantics) -> Self { ReferenceProcessor { - sync: UnsafeCell::new(Mutex::new(ReferenceProcessorSync { - references: Vec::with_capacity(INITIAL_SIZE), - unforwarded_references: None, + sync: Mutex::new(ReferenceProcessorSync { + references: HashSet::with_capacity(INITIAL_SIZE), + enqueued_references: vec![], nursery_index: 0, - })), + }), semantics, + allow_new_candidate: AtomicBool::new(true), + } + } + + /// Add a candidate. + #[inline(always)] + pub fn add_candidate(&self, reff: ObjectReference) { + if !self.allow_new_candidate.load(Ordering::SeqCst) { + return; } + + let mut sync = self.sync.lock().unwrap(); + sync.references.insert(reff); + } + + fn disallow_new_candidate(&self) { + self.allow_new_candidate.store(false, Ordering::SeqCst); } - fn sync(&self) -> &Mutex { - unsafe { &*self.sync.get() } + fn allow_new_candidate(&self) { + self.allow_new_candidate.store(true, Ordering::SeqCst); } - // UNSAFE: Bypasses mutex - // It is designed to allow getting mut ref from UnsafeCell. - // TODO: We may need to rework on this to remove the unsafety. - #[allow(clippy::mut_from_ref)] - unsafe fn sync_mut(&self) -> &mut ReferenceProcessorSync { - (*self.sync.get()).get_mut().unwrap() + // These funcions simply call `trace_object()`, which does two things: 1. to make sure the object is kept alive + // and 2. to get the new object reference if the object is copied. The functions are intended to make the code + // easier to understand. + + #[inline(always)] + fn get_forwarded_referent( + e: &mut E, + referent: ObjectReference, + ) -> ObjectReference { + e.trace_object(referent) } - pub fn clear(&self) { - let mut sync = self.sync().lock().unwrap(); - sync.references.clear(); - sync.unforwarded_references = None; - sync.nursery_index = 0; + #[inline(always)] + fn get_forwarded_reference( + e: &mut E, + object: ObjectReference, + ) -> ObjectReference { + e.trace_object(object) } - pub fn add_candidate(&self, reff: ObjectReference, referent: ObjectReference) { - let mut sync = self.sync().lock().unwrap(); - VM::VMReferenceGlue::set_referent(reff, referent); - sync.references.push(reff.to_address()); + #[inline(always)] + fn keep_referent_alive( + e: &mut E, + referent: ObjectReference, + ) -> ObjectReference { + e.trace_object(referent) } - pub fn forward(&self, trace: &mut T, _nursery: bool) { - let mut sync = unsafe { self.sync_mut() }; - let references: &mut Vec
= &mut sync.references; - // XXX: Copies `unforwarded_references` out. Should be fine since it's not accessed - // concurrently & it's set to `None` at the end anyway.. - let mut unforwarded_references: Vec
= sync.unforwarded_references.clone().unwrap(); - if TRACE { - trace!("Starting ReferenceProcessor.forward({:?})", self.semantics); + /// Inform the binding to enqueue the weak references whose referents were cleared in this GC. + pub fn enqueue(&self, tls: VMWorkerThread) { + let mut sync = self.sync.lock().unwrap(); + + // This is the end of a GC. We do some assertions here to make sure our reference tables are correct. + #[cfg(debug_assertions)] + { + // For references in the table, the reference needs to be valid, and if the referent is not null, it should be valid as well + sync.references.iter().for_each(|reff| { + debug_assert!(!reff.is_null()); + debug_assert!(reff.is_in_any_space()); + let referent = VM::VMReferenceGlue::get_referent(*reff); + if !referent.is_null() { + debug_assert!( + referent.is_in_any_space(), + "Referent {:?} (of reference {:?}) is not in any space", + referent, + reff + ); + } + }); + // For references that will be enqueue'd, the referent needs to be valid, and the referent needs to be null. + sync.enqueued_references.iter().for_each(|reff| { + debug_assert!(!reff.is_null()); + debug_assert!(reff.is_in_any_space()); + let referent = VM::VMReferenceGlue::get_referent(*reff); + debug_assert!(referent.is_null()); + }); } - if TRACE_DETAIL { - trace!("{:?} Reference table is {:?}", self.semantics, references); - trace!( - "{:?} unforwardedReferences is {:?}", - self.semantics, - unforwarded_references - ); + + if !sync.enqueued_references.is_empty() { + trace!("enqueue: {:?}", sync.enqueued_references); + VM::VMReferenceGlue::enqueue_references(&sync.enqueued_references, tls); + sync.enqueued_references.clear(); } - for (i, unforwarded_ref) in unforwarded_references - .iter_mut() - .enumerate() - .take(references.len()) - { - let reference = unsafe { unforwarded_ref.to_object_reference() }; - if TRACE_DETAIL { - trace!("slot {:?}: forwarding {:?}", i, reference); + self.allow_new_candidate(); + } + + /// Forward the reference tables in the reference processor. This is only needed if a plan does not forward + /// objects in their first transitive closure. + /// nursery is not used for this. + pub fn forward(&self, trace: &mut E, _nursery: bool) { + let mut sync = self.sync.lock().unwrap(); + debug!("Starting ReferenceProcessor.forward({:?})", self.semantics); + + // Forward a single reference + #[inline(always)] + fn forward_reference( + trace: &mut E, + reference: ObjectReference, + ) -> ObjectReference { + let old_referent = ::VMReferenceGlue::get_referent(reference); + let new_referent = ReferenceProcessor::get_forwarded_referent(trace, old_referent); + ::VMReferenceGlue::set_referent(reference, new_referent); + let new_reference = ReferenceProcessor::get_forwarded_reference(trace, reference); + { + use crate::vm::ObjectModel; + trace!( + "Forwarding reference: {} (size: {})", + reference, + ::VMObjectModel::get_current_size(reference) + ); + trace!( + " referent: {} (forwarded to {})", + old_referent, + new_referent + ); + trace!(" reference: forwarded to {}", new_reference); } - VM::VMReferenceGlue::set_referent( - reference, - trace.get_forwarded_referent(VM::VMReferenceGlue::get_referent(reference)), + debug_assert!( + !new_reference.is_null(), + "reference {:?}'s forwarding pointer is NULL", + reference ); - let new_reference = trace.get_forwarded_reference(reference); - *unforwarded_ref = new_reference.to_address(); + new_reference } - if TRACE { - trace!("Ending ReferenceProcessor.forward({:?})", self.semantics) - } - sync.unforwarded_references = None; + sync.references = sync + .references + .iter() + .map(|reff| forward_reference::(trace, *reff)) + .collect(); + + sync.enqueued_references = sync + .enqueued_references + .iter() + .map(|reff| forward_reference::(trace, *reff)) + .collect(); + + debug!("Ending ReferenceProcessor.forward({:?})", self.semantics); + + // We finish forwarding. No longer accept new candidates. + self.disallow_new_candidate(); } - fn scan( - &self, - trace: &mut T, - nursery: bool, - retain: bool, - tls: VMWorkerThread, - ) { - let sync = unsafe { self.sync_mut() }; - sync.unforwarded_references = Some(sync.references.clone()); - let references: &mut Vec
= &mut sync.references; + /// Scan the reference table, and update each reference/referent. + // TODO: nursery is currently ignored. We used to use Vec for the reference table, and use an int + // to point to the reference that we last scanned. However, when we use HashSet for reference table, + // we can no longer do that. + fn scan(&self, trace: &mut E, _nursery: bool) { + let mut sync = self.sync.lock().unwrap(); + + debug!("Starting ReferenceProcessor.scan({:?})", self.semantics); + + trace!( + "{:?} Reference table is {:?}", + self.semantics, + sync.references + ); + + debug_assert!(sync.enqueued_references.is_empty()); + // Put enqueued reference in this vec + let mut enqueued_references = vec![]; + + // Determinine liveness for each reference and only keep the refs if `process_reference()` returns Some. + let new_set: HashSet = sync + .references + .iter() + .filter_map(|reff| self.process_reference(trace, *reff, &mut enqueued_references)) + .collect(); + + debug!( + "{:?} reference table from {} to {} ({} enqueued)", + self.semantics, + sync.references.len(), + new_set.len(), + enqueued_references.len() + ); + sync.references = new_set; + sync.enqueued_references = enqueued_references; + + debug!("Ending ReferenceProcessor.scan({:?})", self.semantics); + } - if TRACE { - trace!("Starting ReferenceProcessor.scan({:?})", self.semantics); - } - let mut to_index = if nursery { sync.nursery_index } else { 0 }; - let from_index = to_index; + /// Retain referent in the reference table. This method deals only with soft references. + /// It retains the referent if the reference is definitely reachable. This method does + /// not update reference or referent. So after this method, scan() should be used to update + /// the references/referents. + fn retain(&self, trace: &mut E, _nursery: bool) { + debug_assert!(self.semantics == Semantics::SOFT); - if TRACE_DETAIL { - trace!("{:?} Reference table is {:?}", self.semantics, references); - } - if retain { - for addr in references.iter().skip(from_index) { - let reference = unsafe { addr.to_object_reference() }; - self.retain_referent::(trace, reference); + let sync = self.sync.lock().unwrap(); + + debug!("Starting ReferenceProcessor.retain({:?})", self.semantics); + trace!( + "{:?} Reference table is {:?}", + self.semantics, + sync.references + ); + + for reference in sync.references.iter() { + debug_assert!(!reference.is_null()); + + trace!("Processing reference: {:?}", reference); + + if !reference.is_live() { + // Reference is currently unreachable but may get reachable by the + // following trace. We postpone the decision. + continue; } - } else { - for i in from_index..references.len() { - let reference = unsafe { references[i].to_object_reference() }; - - /* Determine liveness (and forward if necessary) the reference */ - let new_reference = VM::VMReferenceGlue::process_reference(trace, reference, tls); - if !new_reference.is_null() { - references[to_index] = new_reference.to_address(); - to_index += 1; - if TRACE_DETAIL { - let index = to_index - 1; - trace!( - "SCANNED {} {:?} -> {:?}", - index, - references[index], - unsafe { references[index].to_object_reference() } - ); - } - } + + // Reference is definitely reachable. Retain the referent. + let referent = ::VMReferenceGlue::get_referent(*reference); + if !referent.is_null() { + Self::keep_referent_alive(trace, referent); } - trace!( - "{:?} references: {} -> {}", - self.semantics, - references.len(), - to_index - ); - sync.nursery_index = to_index; - references.truncate(to_index); + trace!(" ~> {:?} (retained)", referent.to_address()); } - /* flush out any remset entries generated during the above activities */ - - // FIXME: We are calling mutator() for a worker thread - panic!("We are calling mutator() for a worker tls. We need to fix this."); - // unsafe { VM::VMActivePlan::mutator(tls)) }.flush_remembered_sets(); - // if TRACE { - // trace!("Ending ReferenceProcessor.scan({:?})", self.semantics); - // } + debug!("Ending ReferenceProcessor.retain({:?})", self.semantics); } - /** - * This method deals only with soft references. It retains the referent - * if the reference is definitely reachable. - * @param reference the address of the reference. This may or may not - * be the address of a heap object, depending on the VM. - * @param trace the thread local trace element. - */ - fn retain_referent( + /// Process a reference. + /// * If both the reference and the referent is alive, return the updated reference and update its referent properly. + /// * If the reference is alive, and the referent is not null but not alive, return None and the reference (with cleared referent) is enqueued. + /// * For other cases, return None. + /// + /// If a None value is returned, the reference can be removed from the reference table. Otherwise, the updated reference should be kept + /// in the reference table. + fn process_reference( &self, - trace: &mut T, + trace: &mut E, reference: ObjectReference, - ) { + enqueued_references: &mut Vec, + ) -> Option { debug_assert!(!reference.is_null()); - debug_assert!(self.semantics == Semantics::SOFT); - if TRACE_DETAIL { - trace!("Processing reference: {:?}", reference); - } + trace!("Process reference: {}", reference); + // If the reference is dead, we're done with it. Let it (and + // possibly its referent) be garbage-collected. if !reference.is_live() { - /* - * Reference is currently unreachable but may get reachable by the - * following trace. We postpone the decision. - */ - return; + ::VMReferenceGlue::clear_referent(reference); + trace!(" UNREACHABLE reference: {}", reference); + trace!(" (unreachable)"); + return None; } - /* - * Reference is definitely reachable. Retain the referent. - */ - let referent = VM::VMReferenceGlue::get_referent(reference); - if !referent.is_null() { - trace.retain_referent(referent); + // The reference object is live + let new_reference = Self::get_forwarded_reference(trace, reference); + let old_referent = ::VMReferenceGlue::get_referent(reference); + trace!(" ~> {}", old_referent); + + // If the application has cleared the referent the Java spec says + // this does not cause the Reference object to be enqueued. We + // simply allow the Reference object to fall out of our + // waiting list. + if old_referent.is_null() { + trace!(" (null referent) "); + return None; } - if TRACE_DETAIL { - trace!(" ~> {:?} (retained)", referent.to_address()); + + trace!(" => {}", new_reference); + + if old_referent.is_live() { + // Referent is still reachable in a way that is as strong as + // or stronger than the current reference level. + let new_referent = Self::get_forwarded_referent(trace, old_referent); + debug_assert!(new_referent.is_live()); + trace!(" ~> {}", new_referent); + + // The reference object stays on the waiting list, and the + // referent is untouched. The only thing we must do is + // ensure that the former addresses are updated with the + // new forwarding addresses in case the collector is a + // copying collector. + + // Update the referent + ::VMReferenceGlue::set_referent(new_reference, new_referent); + Some(new_reference) + } else { + // Referent is unreachable. Clear the referent and enqueue the reference object. + trace!(" UNREACHABLE referent: {}", old_referent); + + ::VMReferenceGlue::clear_referent(new_reference); + enqueued_references.push(new_reference); + None } } } + +use crate::scheduler::GCWork; +use crate::scheduler::GCWorker; +use crate::MMTK; +use std::marker::PhantomData; + +#[derive(Default)] +pub struct SoftRefProcessing(PhantomData); +impl GCWork for SoftRefProcessing { + fn do_work(&mut self, worker: &mut GCWorker, mmtk: &'static MMTK) { + let mut w = E::new(vec![], false, mmtk); + w.set_worker(worker); + mmtk.reference_processors.scan_soft_refs(&mut w, mmtk); + w.flush(); + } +} +impl SoftRefProcessing { + pub fn new() -> Self { + Self(PhantomData) + } +} + +#[derive(Default)] +pub struct WeakRefProcessing(PhantomData); +impl GCWork for WeakRefProcessing { + fn do_work(&mut self, worker: &mut GCWorker, mmtk: &'static MMTK) { + let mut w = E::new(vec![], false, mmtk); + w.set_worker(worker); + mmtk.reference_processors.scan_weak_refs(&mut w, mmtk); + w.flush(); + } +} +impl WeakRefProcessing { + pub fn new() -> Self { + Self(PhantomData) + } +} + +#[derive(Default)] +pub struct PhantomRefProcessing(PhantomData); +impl GCWork for PhantomRefProcessing { + fn do_work(&mut self, worker: &mut GCWorker, mmtk: &'static MMTK) { + let mut w = E::new(vec![], false, mmtk); + w.set_worker(worker); + mmtk.reference_processors.scan_phantom_refs(&mut w, mmtk); + w.flush(); + } +} +impl PhantomRefProcessing { + pub fn new() -> Self { + Self(PhantomData) + } +} + +#[derive(Default)] +pub struct RefForwarding(PhantomData); +impl GCWork for RefForwarding { + fn do_work(&mut self, worker: &mut GCWorker, mmtk: &'static MMTK) { + let mut w = E::new(vec![], false, mmtk); + w.set_worker(worker); + mmtk.reference_processors.forward_refs(&mut w, mmtk); + w.flush(); + } +} +impl RefForwarding { + pub fn new() -> Self { + Self(PhantomData) + } +} + +#[derive(Default)] +pub struct RefEnqueue(PhantomData); +impl GCWork for RefEnqueue { + fn do_work(&mut self, worker: &mut GCWorker, mmtk: &'static MMTK) { + mmtk.reference_processors.enqueue_refs::(worker.tls); + } +} +impl RefEnqueue { + pub fn new() -> Self { + Self(PhantomData) + } +} diff --git a/src/vm/reference_glue.rs b/src/vm/reference_glue.rs index e3520f77eb..a88263edd4 100644 --- a/src/vm/reference_glue.rs +++ b/src/vm/reference_glue.rs @@ -1,7 +1,6 @@ -use crate::plan::TraceLocal; -use crate::util::opaque_pointer::*; use crate::util::Address; use crate::util::ObjectReference; +use crate::util::VMWorkerThread; use crate::vm::VMBinding; /// VM-specific methods for reference processing. @@ -30,16 +29,12 @@ pub trait ReferenceGlue { /// * `referent`: The referent object reference. fn set_referent(reff: ObjectReference, referent: ObjectReference); - /// Process a reference with the current semantics and return an updated reference (e.g. with a new address) - /// if the reference is still alive, otherwise return a null object reference. - /// - /// Arguments: - /// * `trace`: A reference to a `TraceLocal` object for this reference. - /// * `reference`: The address of the reference. This may or may not be the address of a heap object, depending on the VM. - /// * `tls`: The GC thread that is processing this reference. - fn process_reference( - trace: &mut T, - reference: ObjectReference, - tls: VMWorkerThread, - ) -> ObjectReference; + /// For reference types, if the referent is cleared during GC, the reference + /// will be added to a queue, and MMTk will call this method to inform + /// the VM about the changes for those references. This method is used + /// to implement Java's ReferenceQueue. + /// Note that this method is called for each type of weak references during GC, and + /// the references slice will be cleared after this call is returned. That means + /// MMTk will no longer keep these references alive once this method is returned. + fn enqueue_references(references: &[ObjectReference], tls: VMWorkerThread); } diff --git a/vmbindings/dummyvm/api/mmtk.h b/vmbindings/dummyvm/api/mmtk.h index 0b4cfb528c..6f0843d729 100644 --- a/vmbindings/dummyvm/api/mmtk.h +++ b/vmbindings/dummyvm/api/mmtk.h @@ -107,13 +107,13 @@ extern void* mmtk_starting_heap_address(); extern void* mmtk_last_heap_address(); // Add a reference to the list of weak references -extern void mmtk_add_weak_candidate(void* ref, void* referent); +extern void mmtk_add_weak_candidate(void* ref); // Add a reference to the list of soft references -extern void mmtk_add_soft_candidate(void* ref, void* referent); +extern void mmtk_add_soft_candidate(void* ref); // Add a reference to the list of phantom references -extern void mmtk_add_phantom_candidate(void* ref, void* referent); +extern void mmtk_add_phantom_candidate(void* ref); // Generic hook to allow benchmarks to be harnessed extern void mmtk_harness_begin(void* tls); diff --git a/vmbindings/dummyvm/src/api.rs b/vmbindings/dummyvm/src/api.rs index 3c5aa55a8e..61585ed67d 100644 --- a/vmbindings/dummyvm/src/api.rs +++ b/vmbindings/dummyvm/src/api.rs @@ -127,18 +127,18 @@ pub extern "C" fn mmtk_handle_user_collection_request(tls: VMMutatorThread) { } #[no_mangle] -pub extern "C" fn mmtk_add_weak_candidate(reff: ObjectReference, referent: ObjectReference) { - memory_manager::add_weak_candidate(&SINGLETON, reff, referent) +pub extern "C" fn mmtk_add_weak_candidate(reff: ObjectReference) { + memory_manager::add_weak_candidate(&SINGLETON, reff) } #[no_mangle] -pub extern "C" fn mmtk_add_soft_candidate(reff: ObjectReference, referent: ObjectReference) { - memory_manager::add_soft_candidate(&SINGLETON, reff, referent) +pub extern "C" fn mmtk_add_soft_candidate(reff: ObjectReference) { + memory_manager::add_soft_candidate(&SINGLETON, reff) } #[no_mangle] -pub extern "C" fn mmtk_add_phantom_candidate(reff: ObjectReference, referent: ObjectReference) { - memory_manager::add_phantom_candidate(&SINGLETON, reff, referent) +pub extern "C" fn mmtk_add_phantom_candidate(reff: ObjectReference) { + memory_manager::add_phantom_candidate(&SINGLETON, reff) } #[no_mangle] diff --git a/vmbindings/dummyvm/src/reference_glue.rs b/vmbindings/dummyvm/src/reference_glue.rs index 086f1cfaeb..bf1ce233f9 100644 --- a/vmbindings/dummyvm/src/reference_glue.rs +++ b/vmbindings/dummyvm/src/reference_glue.rs @@ -1,7 +1,6 @@ use mmtk::vm::ReferenceGlue; use mmtk::util::ObjectReference; -use mmtk::TraceLocal; -use mmtk::util::opaque_pointer::*; +use mmtk::util::opaque_pointer::VMWorkerThread; use crate::DummyVM; pub struct VMReferenceGlue {} @@ -13,7 +12,7 @@ impl ReferenceGlue for VMReferenceGlue { fn get_referent(_object: ObjectReference) -> ObjectReference { unimplemented!() } - fn process_reference(_trace: &mut T, _reference: ObjectReference, _tls: VMWorkerThread) -> ObjectReference { + fn enqueue_references(_references: &[ObjectReference], _tls: VMWorkerThread) { unimplemented!() } }