Skip to content

Commit e40e4ca

Browse files
udesouqinsoon
andcommitted
Conservative stack scanning (#203)
This PR ports #157 and #184 to dev. It also adds an optimization that if a task is not started, the conservative stack scanning can be skipped for the task. Merge with mmtk/julia#80. --------- Co-authored-by: Yi Lin <qinsoon@gmail.com>
1 parent d3d7997 commit e40e4ca

File tree

9 files changed

+327
-54
lines changed

9 files changed

+327
-54
lines changed

mmtk/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,17 @@ memoffset = "*"
4040
# ykstackmaps = { git = "https://github.com/udesou/ykstackmaps.git", branch = "udesou-master", version = "*" }
4141

4242
[features]
43-
default = ["mmtk/vm_space", "julia_copy_stack", "object_pinning", "is_mmtk_object", "mmtk/vo_bit_access"]
43+
# We must build with default features
44+
default = ["mmtk/vm_space", "julia_copy_stack", "mmtk/object_pinning", "mmtk/is_mmtk_object", "mmtk/vo_bit_access"]
4445

45-
# Plans
46+
# Default features
47+
julia_copy_stack = []
48+
49+
# Plans: choose one
4650
nogc = []
4751
immix = []
4852
stickyimmix = ["mmtk/sticky_immix_non_moving_nursery", "mmtk/immix_smaller_block"]
4953
marksweep = []
50-
object_pinning = ["mmtk/object_pinning"]
51-
is_mmtk_object = ["mmtk/is_mmtk_object"]
5254

5355
# This feature disables moving
5456
non_moving = ["mmtk/immix_non_moving", "mmtk/immix_smaller_block"]
55-
julia_copy_stack = []

mmtk/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fn main() {
5454
let bindings = bindgen::Builder::default()
5555
.header(format!("{}/src/julia.h", julia_dir))
5656
.header(format!("{}/src/julia_internal.h", julia_dir))
57+
.header(format!("{}/src/gc-common.h", julia_dir))
5758
// Including the paths to depending .h files
5859
.clang_arg("-I")
5960
.clang_arg(format!("{}/mmtk/api", mmtk_dir))
@@ -77,6 +78,7 @@ fn main() {
7778
.allowlist_item("jl_bt_element_t")
7879
.allowlist_item("jl_taggedvalue_t")
7980
.allowlist_item("MMTkMutatorContext")
81+
.allowlist_item("_bigval_t")
8082
// --opaque-type MMTkMutatorContext
8183
.opaque_type("MMTkMutatorContext")
8284
// compile using c++

mmtk/src/api.rs

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ pub extern "C" fn mmtk_set_vm_space(start: Address, size: usize) {
350350

351351
#[cfg(feature = "stickyimmix")]
352352
set_side_log_bit_for_region(start, size);
353-
#[cfg(feature = "is_mmtk_object")]
354353
set_side_vo_bit_for_region(start, size);
355354
}
356355

@@ -383,7 +382,7 @@ pub extern "C" fn mmtk_memory_region_copy(
383382
pub extern "C" fn mmtk_immortal_region_post_alloc(start: Address, size: usize) {
384383
#[cfg(feature = "stickyimmix")]
385384
set_side_log_bit_for_region(start, size);
386-
#[cfg(feature = "is_mmtk_object")]
385+
387386
set_side_vo_bit_for_region(start, size);
388387
}
389388

@@ -397,7 +396,8 @@ fn set_side_log_bit_for_region(start: Address, size: usize) {
397396
}
398397
}
399398

400-
#[cfg(feature = "is_mmtk_object")]
399+
// We have to set VO bit even if this is a non_moving build. Otherwise, assertions in mmtk-core
400+
// will complain about seeing objects without VO bit.
401401
fn set_side_vo_bit_for_region(start: Address, size: usize) {
402402
debug!(
403403
"Bulk set VO bit {} to {} ({} bytes)",
@@ -485,9 +485,10 @@ pub extern "C" fn mmtk_get_obj_size(obj: ObjectReference) -> usize {
485485
}
486486
}
487487

488-
#[cfg(all(feature = "object_pinning", not(feature = "non_moving")))]
489488
#[no_mangle]
490489
pub extern "C" fn mmtk_pin_object(object: ObjectReference) -> bool {
490+
crate::early_return_for_non_moving_build!(false);
491+
491492
// We may in the future replace this with a check for the immix space (bound check), which should be much cheaper.
492493
if mmtk_object_is_managed_by_mmtk(object.to_raw_address().as_usize()) {
493494
memory_manager::pin_object(object)
@@ -497,9 +498,10 @@ pub extern "C" fn mmtk_pin_object(object: ObjectReference) -> bool {
497498
}
498499
}
499500

500-
#[cfg(all(feature = "object_pinning", not(feature = "non_moving")))]
501501
#[no_mangle]
502502
pub extern "C" fn mmtk_unpin_object(object: ObjectReference) -> bool {
503+
crate::early_return_for_non_moving_build!(false);
504+
503505
if mmtk_object_is_managed_by_mmtk(object.to_raw_address().as_usize()) {
504506
memory_manager::unpin_object(object)
505507
} else {
@@ -508,9 +510,10 @@ pub extern "C" fn mmtk_unpin_object(object: ObjectReference) -> bool {
508510
}
509511
}
510512

511-
#[cfg(all(feature = "object_pinning", not(feature = "non_moving")))]
512513
#[no_mangle]
513514
pub extern "C" fn mmtk_is_pinned(object: ObjectReference) -> bool {
515+
crate::early_return_for_non_moving_build!(false);
516+
514517
if mmtk_object_is_managed_by_mmtk(object.to_raw_address().as_usize()) {
515518
memory_manager::is_pinned(object)
516519
} else {
@@ -519,25 +522,6 @@ pub extern "C" fn mmtk_is_pinned(object: ObjectReference) -> bool {
519522
}
520523
}
521524

522-
// If the `non-moving` feature is selected, pinning/unpinning is a noop and simply returns false
523-
#[cfg(all(feature = "object_pinning", feature = "non_moving"))]
524-
#[no_mangle]
525-
pub extern "C" fn mmtk_pin_object(_object: ObjectReference) -> bool {
526-
false
527-
}
528-
529-
#[cfg(all(feature = "object_pinning", feature = "non_moving"))]
530-
#[no_mangle]
531-
pub extern "C" fn mmtk_unpin_object(_object: ObjectReference) -> bool {
532-
false
533-
}
534-
535-
#[cfg(all(feature = "object_pinning", feature = "non_moving"))]
536-
#[no_mangle]
537-
pub extern "C" fn mmtk_is_pinned(_object: ObjectReference) -> bool {
538-
false
539-
}
540-
541525
#[no_mangle]
542526
pub extern "C" fn get_mmtk_version() -> *const c_char {
543527
crate::build_info::MMTK_JULIA_FULL_VERSION_STRING

mmtk/src/collection.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
jl_hrtime, jl_throw_out_of_memory_error,
55
};
66
use crate::{JuliaVM, USER_TRIGGERED_GC};
7-
use log::{info, trace};
7+
use log::{debug, trace};
88
use mmtk::util::alloc::AllocationError;
99
use mmtk::util::heap::GCTriggerPolicy;
1010
use mmtk::util::opaque_pointer::*;
@@ -15,6 +15,7 @@ use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicU64, Ordering};
1515
use crate::{BLOCK_FOR_GC, STW_COND, WORLD_HAS_STOPPED};
1616

1717
pub static GC_START: AtomicU64 = AtomicU64::new(0);
18+
static CURRENT_GC_MAY_MOVE: AtomicBool = AtomicBool::new(true);
1819

1920
use std::collections::HashSet;
2021
use std::sync::RwLock;
@@ -52,11 +53,18 @@ impl Collection<JuliaVM> for VMCollection {
5253

5354
trace!("Stopped the world!");
5455

56+
// Store if the current GC may move objects -- we will use it when the current GC finishes.
57+
// We cache the value here just in case MMTk may clear it before we use the value.
58+
CURRENT_GC_MAY_MOVE.store(
59+
crate::SINGLETON.get_plan().current_gc_may_move_object(),
60+
Ordering::SeqCst,
61+
);
62+
5563
// Tell MMTk the stacks are ready.
5664
{
5765
use mmtk::vm::ActivePlan;
5866
for mutator in crate::active_plan::VMActivePlan::mutators() {
59-
info!("stop_all_mutators: visiting {:?}", mutator.mutator_tls);
67+
debug!("stop_all_mutators: visiting {:?}", mutator.mutator_tls);
6068
mutator_visitor(mutator);
6169
}
6270
}
@@ -68,6 +76,9 @@ impl Collection<JuliaVM> for VMCollection {
6876
}
6977

7078
fn resume_mutators(_tls: VMWorkerThread) {
79+
// unpin conservative roots
80+
crate::conservative::unpin_conservative_roots();
81+
7182
// Get the end time of the GC
7283
let end = unsafe { jl_hrtime() };
7384
trace!("gc_end = {}", end);
@@ -86,7 +97,7 @@ impl Collection<JuliaVM> for VMCollection {
8697
let (_, cvar) = &*STW_COND.clone();
8798
cvar.notify_all();
8899

89-
info!(
100+
debug!(
90101
"Live bytes = {}, total bytes = {}",
91102
crate::api::mmtk_used_bytes(),
92103
crate::api::mmtk_total_bytes()
@@ -96,11 +107,11 @@ impl Collection<JuliaVM> for VMCollection {
96107
}
97108

98109
fn block_for_gc(_tls: VMMutatorThread) {
99-
info!("Triggered GC!");
110+
debug!("Triggered GC!");
100111

101112
unsafe { jl_gc_prepare_to_collect() };
102113

103-
info!("Finished blocking mutator for GC!");
114+
debug!("Finished blocking mutator for GC!");
104115
}
105116

106117
fn spawn_gc_thread(_tls: VMThread, ctx: GCThreadContext<JuliaVM>) {
@@ -158,14 +169,18 @@ pub fn is_current_gc_nursery() -> bool {
158169
}
159170
}
160171

172+
pub fn is_current_gc_moving() -> bool {
173+
CURRENT_GC_MAY_MOVE.load(Ordering::SeqCst)
174+
}
175+
161176
#[no_mangle]
162177
pub extern "C" fn mmtk_block_thread_for_gc() {
163178
AtomicBool::store(&BLOCK_FOR_GC, true, Ordering::SeqCst);
164179

165180
let (lock, cvar) = &*STW_COND.clone();
166181
let mut count = lock.lock().unwrap();
167182

168-
info!("Blocking for GC!");
183+
debug!("Blocking for GC!");
169184

170185
AtomicBool::store(&WORLD_HAS_STOPPED, true, Ordering::SeqCst);
171186

mmtk/src/conservative.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use crate::jl_task_stack_buffer;
2+
use crate::julia_types::*;
3+
use mmtk::memory_manager;
4+
use mmtk::util::constants::BYTES_IN_ADDRESS;
5+
use mmtk::util::{Address, ObjectReference};
6+
use std::collections::HashSet;
7+
use std::sync::Mutex;
8+
lazy_static! {
9+
pub static ref CONSERVATIVE_ROOTS: Mutex<HashSet<ObjectReference>> = Mutex::new(HashSet::new());
10+
}
11+
pub fn pin_conservative_roots() {
12+
crate::early_return_for_non_moving_build!(());
13+
crate::early_return_for_current_gc!();
14+
15+
let mut roots = CONSERVATIVE_ROOTS.lock().unwrap();
16+
let n_roots = roots.len();
17+
roots.retain(|obj| mmtk::memory_manager::pin_object(*obj));
18+
let n_pinned = roots.len();
19+
log::debug!("Conservative roots: {}, pinned: {}", n_roots, n_pinned);
20+
}
21+
pub fn unpin_conservative_roots() {
22+
crate::early_return_for_non_moving_build!(());
23+
crate::early_return_for_current_gc!();
24+
25+
let mut roots = CONSERVATIVE_ROOTS.lock().unwrap();
26+
let n_pinned = roots.len();
27+
let mut n_live = 0;
28+
roots.drain().for_each(|obj| {
29+
if mmtk::memory_manager::is_live_object(obj) {
30+
n_live += 1;
31+
mmtk::memory_manager::unpin_object(obj);
32+
}
33+
});
34+
log::debug!(
35+
"Conservative roots: pinned: {}, unpinned/live {}",
36+
n_pinned,
37+
n_live
38+
);
39+
}
40+
pub fn mmtk_conservative_scan_task_stack(ta: *const jl_task_t) {
41+
crate::early_return_for_non_moving_build!(());
42+
crate::early_return_for_current_gc!();
43+
44+
let mut size: u64 = 0;
45+
let mut ptid: i32 = 0;
46+
log::debug!("mmtk_conservative_scan_native_stack begin ta = {:?}", ta);
47+
let stk = unsafe { jl_task_stack_buffer(ta, &mut size as *mut _, &mut ptid as *mut _) };
48+
log::debug!(
49+
"mmtk_conservative_scan_native_stack continue stk = {}, size = {}, ptid = {:x}",
50+
stk,
51+
size,
52+
ptid
53+
);
54+
if !stk.is_zero() {
55+
log::debug!("Conservatively scan the stack");
56+
// See jl_guard_size
57+
// TODO: Are we sure there are always guard pages we need to skip?
58+
const JL_GUARD_PAGE: usize = 4096 * 8;
59+
let guard_page_start = stk + JL_GUARD_PAGE;
60+
log::debug!("Skip guard page: {}, {}", stk, guard_page_start);
61+
conservative_scan_range(guard_page_start, stk + size as usize);
62+
} else {
63+
log::warn!("Skip stack for {:?}", ta);
64+
}
65+
}
66+
pub fn mmtk_conservative_scan_task_registers(ta: *const jl_task_t) {
67+
crate::early_return_for_non_moving_build!(());
68+
crate::early_return_for_current_gc!();
69+
70+
let (lo, hi) = get_range(&unsafe { &*ta }.ctx);
71+
conservative_scan_range(lo, hi);
72+
}
73+
pub fn mmtk_conservative_scan_ptls_registers(ptls: &mut _jl_tls_states_t) {
74+
crate::early_return_for_non_moving_build!(());
75+
crate::early_return_for_current_gc!();
76+
77+
let (lo, hi) = get_range(&((*ptls).gc_tls.ctx_at_the_time_gc_started));
78+
conservative_scan_range(lo, hi);
79+
}
80+
// TODO: This scans the entire context type, which is slower.
81+
// We actually only need to scan registers.
82+
fn get_range<T>(ctx: &T) -> (Address, Address) {
83+
let start = Address::from_ptr(ctx);
84+
let ty_size = std::mem::size_of::<T>();
85+
(start, start + ty_size)
86+
}
87+
fn conservative_scan_range(lo: Address, hi: Address) {
88+
// The high address is exclusive
89+
let hi = if hi.is_aligned_to(BYTES_IN_ADDRESS) {
90+
hi - BYTES_IN_ADDRESS
91+
} else {
92+
hi.align_down(BYTES_IN_ADDRESS)
93+
};
94+
let lo = lo.align_up(BYTES_IN_ADDRESS);
95+
log::trace!("Scan {} (lo) {} (hi)", lo, hi);
96+
let mut cursor = hi;
97+
while cursor >= lo {
98+
let addr = unsafe { cursor.load::<Address>() };
99+
if let Some(obj) = is_potential_mmtk_object(addr) {
100+
CONSERVATIVE_ROOTS.lock().unwrap().insert(obj);
101+
}
102+
cursor -= BYTES_IN_ADDRESS;
103+
}
104+
}
105+
fn is_potential_mmtk_object(addr: Address) -> Option<ObjectReference> {
106+
if crate::object_model::is_addr_in_immixspace(addr) {
107+
// We only care about immix space. If the object is in other spaces, we won't move them, and we don't need to pin them.
108+
memory_manager::find_object_from_internal_pointer(addr, usize::MAX)
109+
} else {
110+
None
111+
}
112+
}

0 commit comments

Comments
 (0)