summaryrefslogtreecommitdiff
path: root/gc/mmtk/src/pinning_registry.rs
blob: b498b508f1f97b72ab59a1e62ff75e192a8cfbca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use std::sync::Mutex;

use mmtk::memory_manager;
use mmtk::scheduler::GCWork;
use mmtk::scheduler::GCWorker;
use mmtk::scheduler::WorkBucketStage;
use mmtk::util::ObjectReference;
use mmtk::util::VMWorkerThread;
use mmtk::MMTK;

use crate::abi::GCThreadTLS;
use crate::upcalls;
use crate::Ruby;

pub struct PinningRegistry {
    pinning_objs: Mutex<Vec<ObjectReference>>,
    pinned_objs: Mutex<Vec<ObjectReference>>,
}

impl PinningRegistry {
    pub fn new() -> Self {
        Self {
            pinning_objs: Default::default(),
            pinned_objs: Default::default(),
        }
    }

    pub fn register(&self, object: ObjectReference) {
        let mut pinning_objs = self.pinning_objs.lock().unwrap();
        pinning_objs.push(object);
    }

    pub fn pin_children(&self, tls: VMWorkerThread) {
        if !crate::mmtk().get_plan().current_gc_may_move_object() {
            log::debug!("The current GC is non-moving, skipping pinning children.");
            return;
        }

        let gc_tls = unsafe { GCThreadTLS::from_vwt_check(tls) };
        let worker = gc_tls.worker();

        let pinning_objs = self
            .pinning_objs
            .try_lock()
            .expect("PinningRegistry should not have races during GC.");

        let packet_size = 512;
        let work_packets = pinning_objs
            .chunks(packet_size)
            .map(|chunk| {
                Box::new(PinPinningChildren {
                    pinning_objs: chunk.to_vec(),
                }) as _
            })
            .collect();

        worker.scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(work_packets);
    }

    pub fn cleanup(&self, worker: &mut GCWorker<Ruby>) {
        worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].add(RemoveDeadPinnings);
        if crate::mmtk().get_plan().current_gc_may_move_object() {
            let packet = {
                let mut pinned_objs = self
                    .pinned_objs
                    .try_lock()
                    .expect("Unexpected contention on pinned_objs");
                UnpinPinnedObjects {
                    objs: std::mem::take(&mut pinned_objs),
                }
            };

            worker.scheduler().work_buckets[WorkBucketStage::VMRefClosure].add(packet);
        } else {
            debug!("The current GC is non-moving, skipping unpinning objects.");
            debug_assert_eq!(
                {
                    let pinned_objs = self
                        .pinned_objs
                        .try_lock()
                        .expect("Unexpected contention on pinned_objs");
                    pinned_objs.len()
                },
                0
            );
        }
    }
}

impl Default for PinningRegistry {
    fn default() -> Self {
        Self::new()
    }
}

struct PinPinningChildren {
    pinning_objs: Vec<ObjectReference>,
}

impl GCWork<Ruby> for PinPinningChildren {
    fn do_work(&mut self, worker: &mut GCWorker<Ruby>, _mmtk: &'static MMTK<Ruby>) {
        let gc_tls = unsafe { GCThreadTLS::from_vwt_check(worker.tls) };
        let mut pinned_objs = vec![];
        let mut newly_pinned_objs = vec![];

        let visit_object = |_worker, target_object: ObjectReference, pin| {
            log::trace!(
                "    -> {} {}",
                if pin { "(pin)" } else { "     " },
                target_object
            );
            if pin {
                debug_assert!(
                    target_object.get_forwarded_object().is_none(),
                    "Trying to pin {target_object} but has been moved"
                );

                pinned_objs.push(target_object);
            }
            target_object
        };

        gc_tls
            .object_closure
            .set_temporarily_and_run_code(visit_object, || {
                for obj in self.pinning_objs.iter().cloned() {
                    log::trace!("  Pinning: {}", obj);
                    (upcalls().call_gc_mark_children)(obj);
                }
            });

        for target_object in pinned_objs {
            if memory_manager::pin_object(target_object) {
                newly_pinned_objs.push(target_object);
            }
        }

        let mut pinned_objs = crate::binding()
            .pinning_registry
            .pinned_objs
            .lock()
            .unwrap();
        pinned_objs.append(&mut newly_pinned_objs);
    }
}

struct RemoveDeadPinnings;

impl GCWork<Ruby> for RemoveDeadPinnings {
    fn do_work(&mut self, _worker: &mut GCWorker<Ruby>, _mmtk: &'static MMTK<Ruby>) {
        log::debug!("Removing dead Pinnings...");

        let registry = &crate::binding().pinning_registry;
        {
            let mut pinning_objs = registry
                .pinning_objs
                .try_lock()
                .expect("PinningRegistry should not have races during GC.");

            pinning_objs.retain_mut(|obj| {
                if obj.is_live() {
                    let new_obj = obj.get_forwarded_object().unwrap_or(*obj);
                    *obj = new_obj;
                    true
                } else {
                    log::trace!("  Dead Pinning removed: {}", *obj);
                    false
                }
            });
        }
    }
}

struct UnpinPinnedObjects {
    objs: Vec<ObjectReference>,
}

impl GCWork<Ruby> for UnpinPinnedObjects {
    fn do_work(&mut self, _worker: &mut GCWorker<Ruby>, _mmtk: &'static MMTK<Ruby>) {
        log::debug!("Unpinning pinned objects...");

        for obj in self.objs.iter() {
            let unpinned = memory_manager::unpin_object(*obj);
            debug_assert!(unpinned);
        }
    }
}