Skip to content

Commit a6f8cac

Browse files
udesouqinsoon
andauthored
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 0a97e82 commit a6f8cac

File tree

9 files changed

+248
-67
lines changed

9 files changed

+248
-67
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ MMTK_BUILD=release MMTK_JULIA_DIR=`pwd`/mmtk-julia make -C julia # or "MMTK_BUI
1313
```
1414

1515
If you would like debugging information in your release build of MMTk, add `debug = true` under `[profile.release]` in `mmtk/Cargo.toml`.
16+
If you would like disable object movement, add the `non_moving` feature when building the Rust binding.
1617

1718
### Checking out and Building Julia with MMTk
1819

@@ -38,6 +39,7 @@ Before building Julia, build the binding in `mmtk-julia/mmtk`. You must have alr
3839

3940
In `mmtk-core` we currently support either Immix or StickyImmix implementations.
4041
Build it with `cargo build --features immix` or `cargo build --features stickyimmix`.
42+
Optionally, add the `non_moving` feature when building the Rust binding for disabling object movement, which is enabled by default (although we are currently pinning the vast majority of objects).
4143
Add `--release` at the end if you would like to have a release build, otherwise it is a debug build.
4244
For a release build with debugging information, first add `debug = true` under `[profile.release]` in `mmtk/Cargo.toml`.
4345

mmtk/Cargo.toml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ edition = "2018"
1111
[package.metadata.julia]
1212
# Our CI matches the following line and extract mmtk/julia. If this line is updated, please check ci yaml files and make sure it works.
1313
julia_repo = "https://github.com/mmtk/julia.git"
14-
julia_version = "32d38fb974b955bc2c610a72617cfc59265fa260"
14+
julia_version = "dfd0a0edcb9f2d101bf498d5bfce2bd74153772d"
1515

1616
[lib]
1717
crate-type = ["cdylib"]
@@ -46,16 +46,17 @@ memoffset = "*"
4646
# ykstackmaps = { git = "https://github.com/udesou/ykstackmaps.git", branch = "udesou-master", version = "*" }
4747

4848
[features]
49-
default = ["mmtk/vm_space", "julia_copy_stack", "object_pinning", "is_mmtk_object", "mmtk/vo_bit_access"]
49+
# We must build with default features
50+
default = ["mmtk/vm_space", "julia_copy_stack", "mmtk/object_pinning", "mmtk/is_mmtk_object", "mmtk/vo_bit_access"]
5051

51-
# Plans
52+
# Default features
53+
julia_copy_stack = []
54+
55+
# Plans: choose one
5256
nogc = []
5357
immix = []
5458
stickyimmix = ["mmtk/sticky_immix_non_moving_nursery", "mmtk/immix_smaller_block"]
5559
marksweep = []
56-
object_pinning = ["mmtk/object_pinning"]
57-
is_mmtk_object = ["mmtk/is_mmtk_object"]
5860

5961
# This feature disables moving
6062
non_moving = ["mmtk/immix_non_moving", "mmtk/immix_smaller_block"]
61-
julia_copy_stack = []

mmtk/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fn main() {
2828
let bindings = bindgen::Builder::default()
2929
.header(format!("{}/src/julia.h", julia_dir))
3030
.header(format!("{}/src/julia_internal.h", julia_dir))
31+
.header(format!("{}/src/gc-common.h", julia_dir))
3132
// Including the paths to depending .h files
3233
.clang_arg("-I")
3334
.clang_arg(format!("{}/mmtk/api", mmtk_dir))
@@ -51,6 +52,7 @@ fn main() {
5152
.allowlist_item("jl_bt_element_t")
5253
.allowlist_item("jl_taggedvalue_t")
5354
.allowlist_item("_jl_module_using")
55+
.allowlist_item("_bigval_t")
5456
.allowlist_item("MMTkMutatorContext")
5557
// --opaque-type MMTkMutatorContext
5658
.opaque_type("MMTkMutatorContext")

mmtk/src/api.rs

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

339339
#[cfg(feature = "stickyimmix")]
340340
set_side_log_bit_for_region(start, size);
341-
#[cfg(feature = "is_mmtk_object")]
342341
set_side_vo_bit_for_region(start, size);
343342
}
344343

@@ -371,7 +370,7 @@ pub extern "C" fn mmtk_memory_region_copy(
371370
pub extern "C" fn mmtk_immortal_region_post_alloc(start: Address, size: usize) {
372371
#[cfg(feature = "stickyimmix")]
373372
set_side_log_bit_for_region(start, size);
374-
#[cfg(feature = "is_mmtk_object")]
373+
375374
set_side_vo_bit_for_region(start, size);
376375
}
377376

@@ -385,7 +384,8 @@ fn set_side_log_bit_for_region(start: Address, size: usize) {
385384
}
386385
}
387386

388-
#[cfg(feature = "is_mmtk_object")]
387+
// We have to set VO bit even if this is a non_moving build. Otherwise, assertions in mmtk-core
388+
// will complain about seeing objects without VO bit.
389389
fn set_side_vo_bit_for_region(start: Address, size: usize) {
390390
debug!(
391391
"Bulk set VO bit {} to {} ({} bytes)",
@@ -473,9 +473,10 @@ pub extern "C" fn mmtk_get_obj_size(obj: ObjectReference) -> usize {
473473
}
474474
}
475475

476-
#[cfg(all(feature = "object_pinning", not(feature = "non_moving")))]
477476
#[no_mangle]
478477
pub extern "C" fn mmtk_pin_object(object: ObjectReference) -> bool {
478+
crate::early_return_for_non_moving_build!(false);
479+
479480
// We may in the future replace this with a check for the immix space (bound check), which should be much cheaper.
480481
if mmtk_object_is_managed_by_mmtk(object.to_raw_address().as_usize()) {
481482
memory_manager::pin_object(object)
@@ -485,9 +486,10 @@ pub extern "C" fn mmtk_pin_object(object: ObjectReference) -> bool {
485486
}
486487
}
487488

488-
#[cfg(all(feature = "object_pinning", not(feature = "non_moving")))]
489489
#[no_mangle]
490490
pub extern "C" fn mmtk_unpin_object(object: ObjectReference) -> bool {
491+
crate::early_return_for_non_moving_build!(false);
492+
491493
if mmtk_object_is_managed_by_mmtk(object.to_raw_address().as_usize()) {
492494
memory_manager::unpin_object(object)
493495
} else {
@@ -496,9 +498,10 @@ pub extern "C" fn mmtk_unpin_object(object: ObjectReference) -> bool {
496498
}
497499
}
498500

499-
#[cfg(all(feature = "object_pinning", not(feature = "non_moving")))]
500501
#[no_mangle]
501502
pub extern "C" fn mmtk_is_pinned(object: ObjectReference) -> bool {
503+
crate::early_return_for_non_moving_build!(false);
504+
502505
if mmtk_object_is_managed_by_mmtk(object.to_raw_address().as_usize()) {
503506
memory_manager::is_pinned(object)
504507
} else {
@@ -507,25 +510,6 @@ pub extern "C" fn mmtk_is_pinned(object: ObjectReference) -> bool {
507510
}
508511
}
509512

510-
// If the `non-moving` feature is selected, pinning/unpinning is a noop and simply returns false
511-
#[cfg(all(feature = "object_pinning", feature = "non_moving"))]
512-
#[no_mangle]
513-
pub extern "C" fn mmtk_pin_object(_object: ObjectReference) -> bool {
514-
false
515-
}
516-
517-
#[cfg(all(feature = "object_pinning", feature = "non_moving"))]
518-
#[no_mangle]
519-
pub extern "C" fn mmtk_unpin_object(_object: ObjectReference) -> bool {
520-
false
521-
}
522-
523-
#[cfg(all(feature = "object_pinning", feature = "non_moving"))]
524-
#[no_mangle]
525-
pub extern "C" fn mmtk_is_pinned(_object: ObjectReference) -> bool {
526-
false
527-
}
528-
529513
#[no_mangle]
530514
pub extern "C" fn get_mmtk_version() -> *const c_char {
531515
crate::build_info::MMTK_JULIA_FULL_VERSION_STRING

mmtk/src/collection.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
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::opaque_pointer::*;
1010
use mmtk::vm::{Collection, GCThreadContext};
@@ -14,6 +14,7 @@ use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicU64, Ordering};
1414
use crate::{BLOCK_FOR_GC, STW_COND, WORLD_HAS_STOPPED};
1515

1616
static GC_START: AtomicU64 = AtomicU64::new(0);
17+
static CURRENT_GC_MAY_MOVE: AtomicBool = AtomicBool::new(true);
1718

1819
pub struct VMCollection {}
1920

@@ -30,11 +31,18 @@ impl Collection<JuliaVM> for VMCollection {
3031

3132
trace!("Stopped the world!");
3233

34+
// Store if the current GC may move objects -- we will use it when the current GC finishes.
35+
// We cache the value here just in case MMTk may clear it before we use the value.
36+
CURRENT_GC_MAY_MOVE.store(
37+
crate::SINGLETON.get_plan().current_gc_may_move_object(),
38+
Ordering::SeqCst,
39+
);
40+
3341
// Tell MMTk the stacks are ready.
3442
{
3543
use mmtk::vm::ActivePlan;
3644
for mutator in crate::active_plan::VMActivePlan::mutators() {
37-
info!("stop_all_mutators: visiting {:?}", mutator.mutator_tls);
45+
debug!("stop_all_mutators: visiting {:?}", mutator.mutator_tls);
3846
mutator_visitor(mutator);
3947
}
4048
}
@@ -46,6 +54,9 @@ impl Collection<JuliaVM> for VMCollection {
4654
}
4755

4856
fn resume_mutators(_tls: VMWorkerThread) {
57+
// unpin conservative roots
58+
crate::conservative::unpin_conservative_roots();
59+
4960
// Get the end time of the GC
5061
let end = unsafe { jl_hrtime() };
5162
trace!("gc_end = {}", end);
@@ -64,7 +75,7 @@ impl Collection<JuliaVM> for VMCollection {
6475
let &(_, ref cvar) = &*STW_COND.clone();
6576
cvar.notify_all();
6677

67-
info!(
78+
debug!(
6879
"Live bytes = {}, total bytes = {}",
6980
crate::api::mmtk_used_bytes(),
7081
crate::api::mmtk_total_bytes()
@@ -74,27 +85,29 @@ impl Collection<JuliaVM> for VMCollection {
7485
}
7586

7687
fn block_for_gc(_tls: VMMutatorThread) {
77-
info!("Triggered GC!");
88+
debug!("Triggered GC!");
7889

7990
unsafe { jl_mmtk_prepare_to_collect() };
8091

81-
info!("Finished blocking mutator for GC!");
92+
debug!("Finished blocking mutator for GC!");
8293
}
8394

8495
fn spawn_gc_thread(_tls: VMThread, ctx: GCThreadContext<JuliaVM>) {
8596
// Just drop the join handle. The thread will run until the process quits.
86-
let _ = std::thread::spawn(move || {
87-
use mmtk::util::opaque_pointer::*;
88-
use mmtk::util::Address;
89-
let worker_tls = VMWorkerThread(VMThread(OpaquePointer::from_address(unsafe {
90-
Address::from_usize(thread_id::get())
91-
})));
92-
match ctx {
93-
GCThreadContext::Worker(w) => {
94-
mmtk::memory_manager::start_worker(&SINGLETON, worker_tls, w)
97+
let _ = std::thread::Builder::new()
98+
.name("MMTk Worker".to_string())
99+
.spawn(move || {
100+
use mmtk::util::opaque_pointer::*;
101+
use mmtk::util::Address;
102+
let worker_tls = VMWorkerThread(VMThread(OpaquePointer::from_address(unsafe {
103+
Address::from_usize(thread_id::get())
104+
})));
105+
match ctx {
106+
GCThreadContext::Worker(w) => {
107+
mmtk::memory_manager::start_worker(&SINGLETON, worker_tls, w)
108+
}
95109
}
96-
}
97-
});
110+
});
98111
}
99112

100113
fn schedule_finalization(_tls: VMWorkerThread) {}
@@ -121,14 +134,18 @@ pub fn is_current_gc_nursery() -> bool {
121134
}
122135
}
123136

137+
pub fn is_current_gc_moving() -> bool {
138+
CURRENT_GC_MAY_MOVE.load(Ordering::SeqCst)
139+
}
140+
124141
#[no_mangle]
125142
pub extern "C" fn mmtk_block_thread_for_gc() {
126143
AtomicBool::store(&BLOCK_FOR_GC, true, Ordering::SeqCst);
127144

128145
let &(ref lock, ref cvar) = &*STW_COND.clone();
129146
let mut count = lock.lock().unwrap();
130147

131-
info!("Blocking for GC!");
148+
debug!("Blocking for GC!");
132149

133150
AtomicBool::store(&WORLD_HAS_STOPPED, true, Ordering::SeqCst);
134151

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)