Skip to content

Commit e9afc44

Browse files
committed
Unregister PPPs when they are no longer PPPs
This commit, together with a commit in the `ruby` repo, allows the GC to remove objects from the list of registered PPPs when they are no longer PPPs. Specifically, iseq objects will stop being PPPs since ISEQ_COMPILE_DATA_CLEAR. This commit also adds eBPF tracing extensions to track the number of PPPs and their children involved when pinning or unpinning PPP children, and the number of PPPs removed when cleaning up the PPP list, and visualize them on the generated timeline plot.
1 parent 2fb091c commit e9afc44

File tree

8 files changed

+196
-44
lines changed

8 files changed

+196
-44
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ target/
1010

1111
# These are backup files generated by rustfmt
1212
**/*.rs.bk
13+
14+
# Python scripts in eBPF tools may generate such temporary caches.
15+
__pycache__

mmtk/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ edition = "2021"
1212
# Metadata for the Ruby repository
1313
[package.metadata.ci-repos.ruby]
1414
repo = "mmtk/ruby" # This is used by actions/checkout, so the format is "owner/repo", not URL.
15-
rev = "8c96eaf5cf4bb39610fc03797a42870f7eb8dca2"
15+
rev = "0511ec851cd290fac520dbe5a862b409488e159e"
1616

1717
[lib]
1818
name = "mmtk_ruby"

mmtk/src/abi.rs

+1
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ pub struct RubyUpcalls {
385385
pub scan_final_jobs_roots: extern "C" fn(),
386386
pub scan_roots_in_mutator_thread:
387387
extern "C" fn(mutator_tls: VMMutatorThread, worker_tls: VMWorkerThread),
388+
pub is_no_longer_ppp: extern "C" fn(ObjectReference) -> bool,
388389
pub scan_object_ruby_style: extern "C" fn(object: ObjectReference),
389390
pub call_gc_mark_children: extern "C" fn(object: ObjectReference),
390391
pub call_obj_free: extern "C" fn(object: ObjectReference),

mmtk/src/ppp.rs

+108-39
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use std::sync::Mutex;
22

33
use mmtk::{
44
memory_manager,
5-
scheduler::{GCWork, WorkBucketStage},
5+
scheduler::{GCWork, GCWorker, WorkBucketStage},
66
util::{ObjectReference, VMWorkerThread},
7+
MMTK,
78
};
89

910
use crate::{abi::GCThreadTLS, upcalls, Ruby};
@@ -65,42 +66,32 @@ impl PPPRegistry {
6566
}
6667
}
6768

68-
pub fn cleanup_ppps(&self) {
69-
log::debug!("Removing dead PPPs...");
70-
{
71-
let mut ppps = self
72-
.ppps
73-
.try_lock()
74-
.expect("PPPRegistry::ppps should not have races during GC.");
75-
76-
probe!(mmtk_ruby, remove_dead_ppps_start, ppps.len());
77-
ppps.retain_mut(|obj| {
78-
if obj.is_live() {
79-
*obj = obj.get_forwarded_object().unwrap_or(*obj);
80-
true
81-
} else {
82-
log::trace!(" PPP removed: {}", *obj);
83-
false
69+
pub fn cleanup_ppps(&self, worker: &mut GCWorker<Ruby>) {
70+
worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].add(RemoveDeadPPPs);
71+
if crate::mmtk().get_plan().current_gc_may_move_object() {
72+
let packet = {
73+
let mut pinned_ppp_children = self
74+
.pinned_ppp_children
75+
.try_lock()
76+
.expect("Unexpected contention on pinned_ppp_children");
77+
UnpinPPPChildren {
78+
children: std::mem::take(&mut pinned_ppp_children),
8479
}
85-
});
86-
probe!(mmtk_ruby, remove_dead_ppps_end);
87-
}
88-
89-
log::debug!("Unpinning pinned PPP children...");
80+
};
9081

91-
if !crate::mmtk().get_plan().current_gc_may_move_object() {
92-
log::debug!("The current GC is non-moving. Skipped unpinning PPP children.");
82+
worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].add(packet);
9383
} else {
94-
let mut pinned_ppps = self
95-
.pinned_ppp_children
96-
.try_lock()
97-
.expect("PPPRegistry::pinned_ppp_children should not have races during GC.");
98-
probe!(mmtk_ruby, unpin_ppp_children_start, pinned_ppps.len());
99-
for obj in pinned_ppps.drain(..) {
100-
let unpinned = memory_manager::unpin_object(obj);
101-
debug_assert!(unpinned);
102-
}
103-
probe!(mmtk_ruby, unpin_ppp_children_end);
84+
debug!("Skipping unpinning PPP children because the current GC is non-copying.");
85+
debug_assert_eq!(
86+
{
87+
let pinned_ppp_children = self
88+
.pinned_ppp_children
89+
.try_lock()
90+
.expect("Unexpected contention on pinned_ppp_children");
91+
pinned_ppp_children.len()
92+
},
93+
0
94+
);
10495
}
10596
}
10697
}
@@ -116,14 +107,12 @@ struct PinPPPChildren {
116107
}
117108

118109
impl GCWork<Ruby> for PinPPPChildren {
119-
fn do_work(
120-
&mut self,
121-
worker: &mut mmtk::scheduler::GCWorker<Ruby>,
122-
_mmtk: &'static mmtk::MMTK<Ruby>,
123-
) {
110+
fn do_work(&mut self, worker: &mut GCWorker<Ruby>, _mmtk: &'static MMTK<Ruby>) {
124111
let gc_tls = unsafe { GCThreadTLS::from_vwt_check(worker.tls) };
112+
let num_ppps = self.ppps.len();
125113
let mut ppp_children = vec![];
126114
let mut newly_pinned_ppp_children = vec![];
115+
let mut num_no_longer_ppps = 0usize;
127116

128117
let visit_object = |_worker, target_object: ObjectReference, pin| {
129118
log::trace!(
@@ -142,6 +131,11 @@ impl GCWork<Ruby> for PinPPPChildren {
142131
.set_temporarily_and_run_code(visit_object, || {
143132
for obj in self.ppps.iter().cloned() {
144133
log::trace!(" PPP: {}", obj);
134+
if (upcalls().is_no_longer_ppp)(obj) {
135+
num_no_longer_ppps += 1;
136+
log::trace!(" No longer PPP. Skip: {}", obj);
137+
continue;
138+
}
145139
(upcalls().call_gc_mark_children)(obj);
146140
}
147141
});
@@ -152,6 +146,16 @@ impl GCWork<Ruby> for PinPPPChildren {
152146
}
153147
}
154148

149+
let num_pinned_children = newly_pinned_ppp_children.len();
150+
151+
probe!(
152+
mmtk_ruby,
153+
pin_ppp_children,
154+
num_ppps,
155+
num_no_longer_ppps,
156+
num_pinned_children
157+
);
158+
155159
{
156160
let mut pinned_ppp_children = crate::binding()
157161
.ppp_registry
@@ -162,3 +166,68 @@ impl GCWork<Ruby> for PinPPPChildren {
162166
}
163167
}
164168
}
169+
170+
struct RemoveDeadPPPs;
171+
172+
impl GCWork<Ruby> for RemoveDeadPPPs {
173+
fn do_work(&mut self, _worker: &mut GCWorker<Ruby>, _mmtk: &'static MMTK<Ruby>) {
174+
log::debug!("Removing dead PPPs...");
175+
176+
let registry = &crate::binding().ppp_registry;
177+
{
178+
let mut ppps = registry
179+
.ppps
180+
.try_lock()
181+
.expect("PPPRegistry::ppps should not have races during GC.");
182+
183+
let num_ppps = ppps.len();
184+
let mut num_no_longer_ppps = 0usize;
185+
let mut num_dead_ppps = 0usize;
186+
187+
ppps.retain_mut(|obj| {
188+
if obj.is_live() {
189+
let new_obj = obj.get_forwarded_object().unwrap_or(*obj);
190+
if (upcalls().is_no_longer_ppp)(new_obj) {
191+
num_no_longer_ppps += 1;
192+
log::trace!(" No longer PPP. Remove: {}", new_obj);
193+
false
194+
} else {
195+
*obj = new_obj;
196+
true
197+
}
198+
} else {
199+
num_dead_ppps += 1;
200+
log::trace!(" Dead PPP removed: {}", *obj);
201+
false
202+
}
203+
});
204+
205+
probe!(
206+
mmtk_ruby,
207+
remove_dead_ppps,
208+
num_ppps,
209+
num_no_longer_ppps,
210+
num_dead_ppps
211+
);
212+
}
213+
}
214+
}
215+
216+
struct UnpinPPPChildren {
217+
children: Vec<ObjectReference>,
218+
}
219+
220+
impl GCWork<Ruby> for UnpinPPPChildren {
221+
fn do_work(&mut self, _worker: &mut GCWorker<Ruby>, _mmtk: &'static MMTK<Ruby>) {
222+
log::debug!("Unpinning pinned PPP children...");
223+
224+
let num_children = self.children.len();
225+
226+
probe!(mmtk_ruby, unpin_ppp_children, num_children);
227+
228+
for obj in self.children.iter() {
229+
let unpinned = memory_manager::unpin_object(*obj);
230+
debug_assert!(unpinned);
231+
}
232+
}
233+
}

mmtk/src/scanning.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::utils::ChunkedVecCollector;
44
use crate::{extra_assert, is_mmtk_object_safe, upcalls, Ruby, RubySlot};
55
use mmtk::scheduler::{GCWork, GCWorker, WorkBucketStage};
66
use mmtk::util::{ObjectReference, VMWorkerThread};
7-
use mmtk::vm::{ObjectTracer, RootsWorkFactory, Scanning, SlotVisitor};
7+
use mmtk::vm::{ObjectTracer, ObjectTracerContext, RootsWorkFactory, Scanning, SlotVisitor};
88
use mmtk::{Mutator, MutatorContext};
99

1010
pub struct VMScanning {}
@@ -135,18 +135,18 @@ impl Scanning<Ruby> for VMScanning {
135135

136136
fn process_weak_refs(
137137
worker: &mut GCWorker<Ruby>,
138-
tracer_context: impl mmtk::vm::ObjectTracerContext<Ruby>,
138+
tracer_context: impl ObjectTracerContext<Ruby>,
139139
) -> bool {
140140
crate::binding()
141141
.weak_proc
142142
.process_weak_stuff(worker, tracer_context);
143-
crate::binding().ppp_registry.cleanup_ppps();
143+
crate::binding().ppp_registry.cleanup_ppps(worker);
144144
false
145145
}
146146

147147
fn forward_weak_refs(
148148
_worker: &mut GCWorker<Ruby>,
149-
_tracer_context: impl mmtk::vm::ObjectTracerContext<Ruby>,
149+
_tracer_context: impl ObjectTracerContext<Ruby>,
150150
) {
151151
panic!("We can't use MarkCompact in Ruby.");
152152
}

tools/tracing/timeline/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Ruby binding-specific timeline visualizer extensions
2+
3+
Scripts in this directory extends the eBPF timeline visualizer tools in the
4+
[mmtk-core](https://github.com/mmtk/mmtk-core/) repository to add more information about work
5+
packets defined in the mmtk-ruby binding.
6+
7+
Read `mmtk-core/tools/tracing/timeline/README.md` for the basic usage of the tools, and read
8+
`mmtk-core/tools/tracing/timeline/EXTENSION.md` for details about extensions.
9+
10+
## Examples:
11+
12+
To capture a trace with Ruby-specific information:
13+
14+
```
15+
/path/to/mmtk-core/tools/tracing/timeline/capture.py \
16+
-x /path/to/mmtk-ruby/tools/tracing/timeline/capture_ruby.bt \
17+
-m /path/to/mmtk-ruby/mmtk/target/release/libmmtk_ruby.so \
18+
> my-execution.log
19+
```
20+
21+
To convert the log into the JSON format, with Ruby-specific information added to the timeline
22+
blocks:
23+
24+
```
25+
/path/to/mmtk-core/tools/tracing/timeline/visualize.py \
26+
-x /path/to/mmtk-ruby/tools/tracing/timeline/visualize_ruby.bt \
27+
my-execution.log
28+
```
29+
30+
It will generate `my-execution.log.json.gz` which can be loaded into [Perfetto UI].
31+
32+
[Perfetto UI]: https://www.ui.perfetto.dev/
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
usdt:$MMTK:mmtk_ruby:pin_ppp_children {
2+
if (@enable_print) {
3+
printf("pin_ppp_children,meta,%d,%lu,%lu,%lu,%lu\n", tid, nsecs, arg0, arg1, arg2);
4+
}
5+
}
6+
7+
usdt:$MMTK:mmtk_ruby:remove_dead_ppps {
8+
if (@enable_print) {
9+
printf("remove_dead_ppps,meta,%d,%lu,%lu,%lu,%lu\n", tid, nsecs, arg0, arg1, arg2);
10+
}
11+
}
12+
13+
usdt:$MMTK:mmtk_ruby:unpin_ppp_children {
14+
if (@enable_print) {
15+
printf("unpin_ppp_children,meta,%d,%lu,%lu\n", tid, nsecs, arg0);
16+
}
17+
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python3
2+
3+
def enrich_meta_extra(log_processor, name, tid, ts, gc, wp, args):
4+
if wp is not None:
5+
match name:
6+
case "pin_ppp_children":
7+
num_ppps, num_no_longer_ppps, num_pinned_children = [int(x) for x in args]
8+
num_still_ppps = num_ppps - num_no_longer_ppps
9+
wp["args"] |= {
10+
"num_ppps": num_ppps,
11+
"num_no_longer_ppps": num_no_longer_ppps,
12+
"num_still_ppps": num_still_ppps,
13+
"num_pinned_children": num_pinned_children,
14+
}
15+
16+
case "remove_dead_ppps":
17+
num_ppps, num_no_longer_ppps, num_dead_ppps = [int(x) for x in args]
18+
num_retained_ppps = num_ppps - num_no_longer_ppps - num_dead_ppps
19+
wp["args"] |= {
20+
"num_ppps": num_ppps,
21+
"num_no_longer_ppps": num_no_longer_ppps,
22+
"num_dead_ppps": num_dead_ppps,
23+
"num_retained_ppps": num_retained_ppps,
24+
}
25+
26+
case "unpin_ppp_children":
27+
num_children = int(args[0])
28+
wp["args"] |= {
29+
"num_ppp_children": num_children,
30+
}

0 commit comments

Comments
 (0)