Skip to content

Commit 973bd02

Browse files
authored
Ensure log bits are correctly maintained (#1169)
This fixes a bug wherein we were erroneously resetting the unlog bits for objects in the modbuf for full-heap GCs. If we reset the unlog bits in a full-heap GC, we may end up setting the unlog bit for an object that actually died which may cause issues for future allocations. In major GCs, we now bulk clear unlog bits for mark-sweep space and immix space during the prepare stage and bulk clear them for copyspace during the release stage. We clear log bits for large objects during the sweep. We also set the unlog bits for mature objects when tracing. This ensures that there are no stale unlog bits and that we don't accidentally log nursery objects. This PR also adds debug assertions checking that any object that has been added to the modbuf is considered mature by MMTk.
1 parent ddc5517 commit 973bd02

File tree

15 files changed

+83
-50
lines changed

15 files changed

+83
-50
lines changed

src/plan/barriers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,13 @@ impl<S: BarrierSemantics> ObjectBarrier<S> {
171171
Self { semantics }
172172
}
173173

174-
/// Attepmt to atomically log an object.
174+
/// Attempt to atomically log an object.
175175
/// Returns true if the object is not logged previously.
176176
fn object_is_unlogged(&self, object: ObjectReference) -> bool {
177177
unsafe { S::UNLOG_BIT_SPEC.load::<S::VM, u8>(object, None) != 0 }
178178
}
179179

180-
/// Attepmt to atomically log an object.
180+
/// Attempt to atomically log an object.
181181
/// Returns true if the object is not logged previously.
182182
fn log_object(&self, object: ObjectReference) -> bool {
183183
#[cfg(all(feature = "vo_bit", feature = "extreme_assertions"))]

src/plan/generational/gc_work.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,22 +108,24 @@ impl<E: ProcessEdgesWork> ProcessModBuf<E> {
108108

109109
impl<E: ProcessEdgesWork> GCWork<E::VM> for ProcessModBuf<E> {
110110
fn do_work(&mut self, worker: &mut GCWorker<E::VM>, mmtk: &'static MMTK<E::VM>) {
111-
// Flip the per-object unlogged bits to "unlogged" state.
112-
for obj in &self.modbuf {
113-
<E::VM as VMBinding>::VMObjectModel::GLOBAL_LOG_BIT_SPEC.store_atomic::<E::VM, u8>(
114-
*obj,
115-
1,
116-
None,
117-
Ordering::SeqCst,
118-
);
119-
}
120-
// scan modbuf only if the current GC is a nursery GC
121-
if mmtk
122-
.get_plan()
123-
.generational()
124-
.unwrap()
125-
.is_current_gc_nursery()
126-
{
111+
// Process and scan modbuf only if the current GC is a nursery GC
112+
let gen = mmtk.get_plan().generational().unwrap();
113+
if gen.is_current_gc_nursery() {
114+
// Flip the per-object unlogged bits to "unlogged" state.
115+
for obj in &self.modbuf {
116+
debug_assert!(
117+
!gen.is_object_in_nursery(*obj),
118+
"{} was logged but is not mature. Dumping process memory maps:\n{}",
119+
*obj,
120+
crate::util::memory::get_process_memory_maps(),
121+
);
122+
<E::VM as VMBinding>::VMObjectModel::GLOBAL_LOG_BIT_SPEC.store_atomic::<E::VM, u8>(
123+
*obj,
124+
1,
125+
None,
126+
Ordering::SeqCst,
127+
);
128+
}
127129
// Scan objects in the modbuf and forward pointers
128130
let modbuf = std::mem::take(&mut self.modbuf);
129131
GCWork::do_work(

src/plan/generational/global.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ impl<VM: VMBinding> CommonGenPlan<VM> {
227227
if self.common.get_los().in_space(object) {
228228
return self.common.get_los().trace_object::<Q>(queue, object);
229229
}
230+
230231
object
231232
}
232233

src/plan/generational/immix/global.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,8 @@ impl<VM: VMBinding> GenImmix<VM> {
249249
let immix_space = ImmixSpace::new(
250250
plan_args.get_space_args("immix_mature", true, false, VMRequest::discontiguous()),
251251
ImmixSpaceArgs {
252-
reset_log_bit_in_major_gc: false,
253-
// We don't need to unlog objects at tracing. Instead, we unlog objects at copying.
254-
// Any object is moved into the mature space, or is copied inside the mature space. We will unlog it.
255-
unlog_object_when_traced: false,
252+
// We need to unlog objects at tracing time since we currently clear all log bits during a major GC
253+
unlog_object_when_traced: true,
256254
// In GenImmix, young objects are not allocated in ImmixSpace directly.
257255
#[cfg(feature = "vo_bit")]
258256
mixed_age: false,

src/plan/immix/global.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ impl<VM: VMBinding> Immix<VM> {
136136
Self::new_with_args(
137137
plan_args,
138138
ImmixSpaceArgs {
139-
reset_log_bit_in_major_gc: false,
140139
unlog_object_when_traced: false,
141140
#[cfg(feature = "vo_bit")]
142141
mixed_age: false,

src/plan/marksweep/global.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl<VM: VMBinding> Plan for MarkSweep<VM> {
5757

5858
fn prepare(&mut self, tls: VMWorkerThread) {
5959
self.common.prepare(tls, true);
60-
self.ms.prepare();
60+
self.ms.prepare(true);
6161
}
6262

6363
fn release(&mut self, tls: VMWorkerThread) {

src/plan/sticky/immix/global.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ impl<VM: VMBinding> crate::plan::generational::global::GenerationalPlanExt<VM> f
254254
trace!("Immix mature object {}, skip", object);
255255
return object;
256256
} else {
257+
// Nursery object
257258
let object = if KIND == TRACE_KIND_TRANSITIVE_PIN || KIND == TRACE_KIND_FAST {
258259
trace!(
259260
"Immix nursery object {} is being traced without moving",
@@ -326,9 +327,6 @@ impl<VM: VMBinding> StickyImmix<VM> {
326327
// Every object we trace in full heap GC is a mature object. Thus in both cases,
327328
// they should be unlogged.
328329
unlog_object_when_traced: true,
329-
// In full heap GC, mature objects may die, and their unlogged bit needs to be reset.
330-
// Along with the option above, we unlog them again during tracing.
331-
reset_log_bit_in_major_gc: true,
332330
// In StickyImmix, both young and old objects are allocated in the ImmixSpace.
333331
#[cfg(feature = "vo_bit")]
334332
mixed_age: true,

src/policy/copyspace.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ impl<VM: VMBinding> CopySpace<VM> {
196196
side_forwarding_status_table.bzero_metadata(start, size);
197197
}
198198

199+
if self.common.needs_log_bit {
200+
if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC {
201+
side.bzero_metadata(start, size);
202+
}
203+
}
204+
199205
// Clear VO bits because all objects in the space are dead.
200206
#[cfg(feature = "vo_bit")]
201207
crate::util::metadata::vo_bit::bzero_vo_bit(start, size);

src/policy/immix/immixspace.rs

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,6 @@ pub struct ImmixSpaceArgs {
6565
/// (no matter we copy an object or not). So we have to use `PromoteToMature`, and instead
6666
/// just set the log bit in the space when an object is traced.
6767
pub unlog_object_when_traced: bool,
68-
/// Reset log bit at the start of a major GC.
69-
/// Normally we do not need to do this. When immix is used as the mature space,
70-
/// any object should be set as unlogged, and that bit does not need to be cleared
71-
/// even if the object is dead. But in sticky Immix, the mature object and
72-
/// the nursery object are in the same space, we will have to use the
73-
/// bit to differentiate them. So we reset all the log bits in major GCs,
74-
/// and unlogged the objects when they are traced (alive).
75-
pub reset_log_bit_in_major_gc: bool,
7668
/// Whether this ImmixSpace instance contains both young and old objects.
7769
/// This affects the updating of valid-object bits. If some lines or blocks of this ImmixSpace
7870
/// instance contain young objects, their VO bits need to be updated during this GC. Currently
@@ -293,7 +285,7 @@ impl<VM: VMBinding> ImmixSpace<VM> {
293285
Block::LOG_BYTES
294286
);
295287

296-
if space_args.unlog_object_when_traced || space_args.reset_log_bit_in_major_gc {
288+
if space_args.unlog_object_when_traced {
297289
assert!(
298290
args.constraints.needs_log_bit,
299291
"Invalid args when the plan does not use log bit"
@@ -389,6 +381,14 @@ impl<VM: VMBinding> ImmixSpace<VM> {
389381
unimplemented!("cyclic mark bits is not supported at the moment");
390382
}
391383

384+
if self.common.needs_log_bit {
385+
if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC {
386+
for chunk in self.chunk_map.all_chunks() {
387+
side.bzero_metadata(chunk.start(), Chunk::BYTES);
388+
}
389+
}
390+
}
391+
392392
// Prepare defrag info
393393
if super::DEFRAG {
394394
self.defrag.prepare(self, plan_stats);
@@ -838,6 +838,7 @@ impl<VM: VMBinding> ImmixSpace<VM> {
838838
/// A work packet to prepare each block for a major GC.
839839
/// Performs the action on a range of chunks.
840840
pub struct PrepareBlockState<VM: VMBinding> {
841+
#[allow(dead_code)]
841842
pub space: &'static ImmixSpace<VM>,
842843
pub chunk: Chunk,
843844
pub defrag_threshold: Option<usize>,
@@ -851,17 +852,6 @@ impl<VM: VMBinding> PrepareBlockState<VM> {
851852
if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC {
852853
side.bzero_metadata(self.chunk.start(), Chunk::BYTES);
853854
}
854-
if self.space.space_args.reset_log_bit_in_major_gc {
855-
if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC {
856-
// We zero all the log bits in major GC, and for every object we trace, we will mark the log bit again.
857-
side.bzero_metadata(self.chunk.start(), Chunk::BYTES);
858-
} else {
859-
// If the log bit is not in side metadata, we cannot bulk zero. We can either
860-
// clear the bit for dead objects in major GC, or clear the log bit for new
861-
// objects. In either cases, we do not need to set log bit at tracing.
862-
unimplemented!("We cannot bulk zero unlogged bit.")
863-
}
864-
}
865855
}
866856
}
867857

src/policy/immortalspace.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ impl<VM: VMBinding> ImmortalSpace<VM> {
213213
object
214214
);
215215
if self.mark_state.test_and_mark::<VM>(object) {
216+
// Set the unlog bit if required
217+
if self.common.needs_log_bit {
218+
VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.store_atomic::<VM, u8>(
219+
object,
220+
1,
221+
None,
222+
Ordering::SeqCst,
223+
);
224+
}
216225
queue.enqueue(object);
217226
}
218227
object

src/policy/largeobjectspace.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,9 @@ impl<VM: VMBinding> LargeObjectSpace<VM> {
269269
trace!("LOS object {} is being marked now", object);
270270
self.treadmill.copy(object, nursery_object);
271271
// We just moved the object out of the logical nursery, mark it as unlogged.
272-
if nursery_object && self.common.needs_log_bit {
272+
// We also unlog mature objects as their unlog bit may have been unset before the
273+
// full-heap GC
274+
if self.common.needs_log_bit {
273275
VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
274276
.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
275277
}
@@ -288,6 +290,10 @@ impl<VM: VMBinding> LargeObjectSpace<VM> {
288290
let sweep = |object: ObjectReference| {
289291
#[cfg(feature = "vo_bit")]
290292
crate::util::metadata::vo_bit::unset_vo_bit(object);
293+
// Clear log bits for dead objects to prevent a new nursery object having the unlog bit set
294+
if self.common.needs_log_bit {
295+
VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
296+
}
291297
self.pr
292298
.release_pages(get_super_page(object.to_object_start::<VM>()));
293299
};

src/policy/marksweepspace/malloc_ms/global.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ impl<VM: VMBinding> MallocSpace<VM> {
488488
}
489489
}
490490

491-
pub fn prepare(&mut self) {}
491+
pub fn prepare(&mut self, _full_heap: bool) {}
492492

493493
pub fn release(&mut self) {
494494
use crate::scheduler::WorkBucketStage;

src/policy/marksweepspace/native_ms/global.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,15 @@ impl<VM: VMBinding> MarkSweepSpace<VM> {
405405
self.chunk_map.set(block.chunk(), ChunkState::Allocated);
406406
}
407407

408-
pub fn prepare(&mut self) {
408+
pub fn prepare(&mut self, full_heap: bool) {
409+
if self.common.needs_log_bit && full_heap {
410+
if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC {
411+
for chunk in self.chunk_map.all_chunks() {
412+
side.bzero_metadata(chunk.start(), Chunk::BYTES);
413+
}
414+
}
415+
}
416+
409417
#[cfg(debug_assertions)]
410418
self.abandoned_in_gc.lock().unwrap().assert_empty();
411419

src/policy/vmspace.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,17 @@ impl<VM: VMBinding> VMSpace<VM> {
283283
);
284284
debug_assert!(self.in_space(object));
285285
if self.mark_state.test_and_mark::<VM>(object) {
286+
// Flip the per-object unlogged bits to "unlogged" state for objects inside the
287+
// bootimage
288+
#[cfg(feature = "set_unlog_bits_vm_space")]
289+
if self.common.needs_log_bit {
290+
VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.store_atomic::<VM, u8>(
291+
object,
292+
1,
293+
None,
294+
Ordering::SeqCst,
295+
);
296+
}
286297
queue.enqueue(object);
287298
}
288299
object

src/util/metadata/log_bit.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ use std::sync::atomic::Ordering;
66
use super::MetadataSpec;
77

88
impl VMGlobalLogBitSpec {
9+
/// Clear the unlog bit to log object (0 means logged)
10+
pub fn clear<VM: VMBinding>(&self, object: ObjectReference, order: Ordering) {
11+
self.store_atomic::<VM, u8>(object, 0, None, order)
12+
}
13+
914
/// Mark the log bit as unlogged (1 means unlogged)
1015
pub fn mark_as_unlogged<VM: VMBinding>(&self, object: ObjectReference, order: Ordering) {
1116
self.store_atomic::<VM, u8>(object, 1, None, order)

0 commit comments

Comments
 (0)