From 6e91fc3877d73e68716efc01dcf270b03b766b92 Mon Sep 17 00:00:00 2001 From: Kunshan Wang Date: Tue, 7 Jan 2025 13:43:26 +0800 Subject: [PATCH 1/3] Unique object enqueuing option Added a constant `VMBinding::UNIQUE_OBJECT_ENQUEUING`. When set to true, MMTk will guarantee that each object is enqueued at most once in each GC. This can be useful for VMs that piggyback on object scanning to visit objects during GC. Implementation-wise, the mark bit is set atomically when `VMBinding::UNIQUE_OBJECT_ENQUEUING` is true. This PR only affects the native MarkSweep space. Other spaces already do this atomically. Fixes: https://github.com/mmtk/mmtk-core/issues/1254 --- src/policy/marksweepspace/native_ms/global.rs | 50 +++++++++++++++++++ src/vm/mod.rs | 15 ++++++ 2 files changed, 65 insertions(+) diff --git a/src/policy/marksweepspace/native_ms/global.rs b/src/policy/marksweepspace/native_ms/global.rs index 783765529e..3a9bb37383 100644 --- a/src/policy/marksweepspace/native_ms/global.rs +++ b/src/policy/marksweepspace/native_ms/global.rs @@ -329,6 +329,56 @@ impl MarkSweepSpace { } } + /// Mark an object non-atomically. If multiple GC worker threads attempt to mark the same + /// object, more than one of them may return `true`. + fn attempt_mark_non_atomic(&self, object: ObjectReference) -> bool { + if !VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_marked::(object, Ordering::SeqCst) { + VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.mark::(object, Ordering::SeqCst); + true + } else { + false + } + } + + /// Mark an object atomically. + fn attempt_mark_atomic(&self, object: ObjectReference) -> bool { + loop { + let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::( + object, + None, + Ordering::SeqCst, + ); + if old_value == 1u8 { + return false; + } + + if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC + .compare_exchange_metadata::( + object, + old_value, + 1u8, + None, + Ordering::SeqCst, + Ordering::SeqCst, + ) + .is_ok() + { + break; + } + } + true + } + + /// Mark an object. Return `true` if the object is newly marked. Return `false` if the object + /// was already marked. + fn attempt_mark(&self, object: ObjectReference) -> bool { + if VM::UNIQUE_OBJECT_ENQUEUING { + self.attempt_mark_atomic(object) + } else { + self.attempt_mark_non_atomic(object) + } + } + fn trace_object( &self, queue: &mut Q, diff --git a/src/vm/mod.rs b/src/vm/mod.rs index a0c7c717c7..6c3666b35c 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -79,4 +79,19 @@ where /// Note that MMTk does not attempt to do anything to align the cursor to this value, but /// it merely asserts with this constant. const ALLOC_END_ALIGNMENT: usize = 1; + + /// When set to `true`, all plans will guarantee that during each GC, each live object is + /// enqueued at most once, and therefore scanned (by either [`Scanning::scan_object`] or + /// [`Scanning::scan_object_and_trace_edges`]) at most once. + /// + /// When set to `false`, MMTk may enqueue an object multiple times due to optimizations, such as + /// using non-atomic operatios to mark objects. Consequently, an object may be scanned multiple + /// times during a GC. + /// + /// The default value is `false` because duplicated object-enqueuing is benign for most VMs, and + /// related optimizations, such as non-atomic marking, can improve GC speed. VM bindings can + /// override this if they need. For example, some VMs piggyback on object-scanning to visit + /// objects during a GC, but may have data race if multiple GC workers visit the same object at + /// the same time. Such VMs can set this constant to `true` to workaround this problem. + const UNIQUE_OBJECT_ENQUEUING: bool = false; } From 357abe7db5f06adc14fd6c30ece3f96e8c7fd5e3 Mon Sep 17 00:00:00 2001 From: Kunshan Wang Date: Tue, 7 Jan 2025 17:00:56 +0800 Subject: [PATCH 2/3] Minor fixes --- src/policy/marksweepspace/native_ms/global.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/policy/marksweepspace/native_ms/global.rs b/src/policy/marksweepspace/native_ms/global.rs index 3a9bb37383..a83510f723 100644 --- a/src/policy/marksweepspace/native_ms/global.rs +++ b/src/policy/marksweepspace/native_ms/global.rs @@ -342,13 +342,15 @@ impl MarkSweepSpace { /// Mark an object atomically. fn attempt_mark_atomic(&self, object: ObjectReference) -> bool { + let mark_state = 1u8; + loop { let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::( object, None, Ordering::SeqCst, ); - if old_value == 1u8 { + if old_value == mark_state { return false; } @@ -356,7 +358,7 @@ impl MarkSweepSpace { .compare_exchange_metadata::( object, old_value, - 1u8, + mark_state, None, Ordering::SeqCst, Ordering::SeqCst, @@ -389,8 +391,7 @@ impl MarkSweepSpace { "Cannot mark an object {} that was not alloced by free list allocator.", object, ); - if !VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_marked::(object, Ordering::SeqCst) { - VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.mark::(object, Ordering::SeqCst); + if self.attempt_mark(object) { let block = Block::containing(object); block.set_state(BlockState::Marked); queue.enqueue(object); From 50425f9d1794f86112df206ddcb0f9d9dbeb6aa5 Mon Sep 17 00:00:00 2001 From: Kunshan Wang Date: Thu, 9 Jan 2025 13:39:48 +0800 Subject: [PATCH 3/3] Move constant to Scanning --- src/policy/marksweepspace/native_ms/global.rs | 3 ++- src/vm/mod.rs | 15 --------------- src/vm/scanning.rs | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/policy/marksweepspace/native_ms/global.rs b/src/policy/marksweepspace/native_ms/global.rs index a83510f723..f39b051156 100644 --- a/src/policy/marksweepspace/native_ms/global.rs +++ b/src/policy/marksweepspace/native_ms/global.rs @@ -29,6 +29,7 @@ use crate::util::heap::chunk_map::*; use crate::util::linear_scan::Region; use crate::util::VMThread; use crate::vm::ObjectModel; +use crate::vm::Scanning; use std::sync::Mutex; /// The result for `MarkSweepSpace.acquire_block()`. `MarkSweepSpace` will attempt @@ -374,7 +375,7 @@ impl MarkSweepSpace { /// Mark an object. Return `true` if the object is newly marked. Return `false` if the object /// was already marked. fn attempt_mark(&self, object: ObjectReference) -> bool { - if VM::UNIQUE_OBJECT_ENQUEUING { + if VM::VMScanning::UNIQUE_OBJECT_ENQUEUING { self.attempt_mark_atomic(object) } else { self.attempt_mark_non_atomic(object) diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 6c3666b35c..a0c7c717c7 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -79,19 +79,4 @@ where /// Note that MMTk does not attempt to do anything to align the cursor to this value, but /// it merely asserts with this constant. const ALLOC_END_ALIGNMENT: usize = 1; - - /// When set to `true`, all plans will guarantee that during each GC, each live object is - /// enqueued at most once, and therefore scanned (by either [`Scanning::scan_object`] or - /// [`Scanning::scan_object_and_trace_edges`]) at most once. - /// - /// When set to `false`, MMTk may enqueue an object multiple times due to optimizations, such as - /// using non-atomic operatios to mark objects. Consequently, an object may be scanned multiple - /// times during a GC. - /// - /// The default value is `false` because duplicated object-enqueuing is benign for most VMs, and - /// related optimizations, such as non-atomic marking, can improve GC speed. VM bindings can - /// override this if they need. For example, some VMs piggyback on object-scanning to visit - /// objects during a GC, but may have data race if multiple GC workers visit the same object at - /// the same time. Such VMs can set this constant to `true` to workaround this problem. - const UNIQUE_OBJECT_ENQUEUING: bool = false; } diff --git a/src/vm/scanning.rs b/src/vm/scanning.rs index 05b6260f32..b02feb7fe1 100644 --- a/src/vm/scanning.rs +++ b/src/vm/scanning.rs @@ -140,6 +140,21 @@ pub trait RootsWorkFactory: Clone + Send + 'static { /// VM-specific methods for scanning roots/objects. pub trait Scanning { + /// When set to `true`, all plans will guarantee that during each GC, each live object is + /// enqueued at most once, and therefore scanned (by either [`Scanning::scan_object`] or + /// [`Scanning::scan_object_and_trace_edges`]) at most once. + /// + /// When set to `false`, MMTk may enqueue an object multiple times due to optimizations, such as + /// using non-atomic operatios to mark objects. Consequently, an object may be scanned multiple + /// times during a GC. + /// + /// The default value is `false` because duplicated object-enqueuing is benign for most VMs, and + /// related optimizations, such as non-atomic marking, can improve GC speed. VM bindings can + /// override this if they need. For example, some VMs piggyback on object-scanning to visit + /// objects during a GC, but may have data race if multiple GC workers visit the same object at + /// the same time. Such VMs can set this constant to `true` to workaround this problem. + const UNIQUE_OBJECT_ENQUEUING: bool = false; + /// Return true if the given object supports slot enqueuing. /// /// - If this returns true, MMTk core will call `scan_object` on the object.