mmtk/plan/lxr/
mature_evac.rs1use std::sync::Mutex;
2use std::{cell::UnsafeCell, marker::PhantomData};
3
4use crate::plan::concurrent::Pause;
5use crate::plan::global::Plan;
6use crate::plan::lxr::gc_work::mature_evac::SelectDefragBlocks;
7use crate::plan::lxr::gc_work::mature_evac::SELECT_DEFRAG_BLOCK_JOB_COUNTER;
8use crate::policy::immix::block::{Block, BlockState};
9use crate::policy::immix::line::Line;
10use crate::policy::immix::ImmixSpace;
11use crate::policy::space::Space;
12use crate::scheduler::WorkBucketStage;
13use crate::util::linear_scan::Region;
14use crate::util::metadata::side_metadata::spec_defs::{IX_LINE_REUSE_COUNT, LOS_PAGE_REUSE_COUNT};
15use crate::util::ObjectReference;
16use crate::{
17 plan::lxr::LXR,
18 scheduler::GCWork,
19 vm::{slot::Slot, VMBinding},
20};
21
22use super::gc_work::mature_evac::EvacuateMatureObjects;
23use crate::util::constants::LOG_BYTES_IN_PAGE;
24use atomic::Ordering;
25use crossbeam::queue::SegQueue;
26use std::sync::atomic::AtomicUsize;
27
28#[repr(C)]
29pub struct RemSetEntry<VM: VMBinding>(VM::VMSlot, u8);
30
31impl<VM: VMBinding> RemSetEntry<VM> {
32 fn encode(slot: VM::VMSlot, ix: bool) -> Self {
33 let reuse = if ix {
34 IX_LINE_REUSE_COUNT.load_atomic::<u8>(slot.to_address(), Ordering::SeqCst)
35 } else {
36 LOS_PAGE_REUSE_COUNT.load_atomic::<u8>(slot.to_address(), Ordering::SeqCst)
37 };
38 Self(slot, reuse)
39 }
40
41 pub fn decode(&self) -> (VM::VMSlot, u8) {
42 (self.0, self.1)
43 }
44}
45
46pub struct MatureEvecRemSet<VM: VMBinding> {
47 pub gc_buffers: Vec<UnsafeCell<Vec<RemSetEntry<VM>>>>,
48 pub global_packets: Mutex<Vec<Box<dyn GCWork<VM>>>>,
49 local_packets: Vec<UnsafeCell<Vec<Box<dyn GCWork<VM>>>>>,
50 _p: PhantomData<VM>,
51 size: AtomicUsize,
52}
53
54unsafe impl<VM: VMBinding> Send for MatureEvecRemSet<VM> {}
55unsafe impl<VM: VMBinding> Sync for MatureEvecRemSet<VM> {}
56
57impl<VM: VMBinding> MatureEvecRemSet<VM> {
58 pub fn new(workers: usize) -> Self {
59 let mut rs = Self {
60 gc_buffers: vec![],
61 global_packets: Mutex::new(vec![]),
62 local_packets: vec![],
63 _p: PhantomData,
64 size: AtomicUsize::new(0),
65 };
66 rs.gc_buffers
67 .resize_with(workers, || UnsafeCell::new(vec![]));
68 rs.local_packets
69 .resize_with(workers, || UnsafeCell::new(vec![]));
70 rs
71 }
72
73 #[allow(clippy::mut_from_ref)]
74 fn gc_buffer(&self, id: usize) -> &mut Vec<RemSetEntry<VM>> {
75 unsafe { &mut *self.gc_buffers[id].get() }
76 }
77
78 pub fn flush_all(&self) {
79 let mut mature_evac_remsets = self.global_packets.lock().unwrap();
80 self.size.store(0, Ordering::SeqCst);
81 for id in 0..self.gc_buffers.len() {
82 if !self.gc_buffer(id).is_empty() {
83 let remset = std::mem::take(self.gc_buffer(id));
84 mature_evac_remsets.push(Box::new(EvacuateMatureObjects::new(remset)));
85 }
86 }
87 for id in 0..self.local_packets.len() {
88 let buf = unsafe { &mut *self.local_packets[id].get() };
89 if !buf.is_empty() {
90 let packets = std::mem::take(buf);
91 for p in packets {
92 mature_evac_remsets.push(p);
93 }
94 }
95 }
96 }
97
98 pub fn take_global_packets(&self) -> Vec<Box<dyn GCWork<VM>>> {
99 let mut mature_evac_remsets = self.global_packets.lock().unwrap();
100 std::mem::take(&mut *mature_evac_remsets)
101 }
102
103 #[cold]
104 fn flush(&self, id: usize) {
105 if !self.gc_buffer(id).is_empty() {
106 let remset = std::mem::take(self.gc_buffer(id));
107 self.size.fetch_add(remset.len(), Ordering::SeqCst);
108 let w = EvacuateMatureObjects::new(remset);
109 let packet_buffer = unsafe { &mut *self.local_packets[id].get() };
110 packet_buffer.push(Box::new(w));
111 }
112 }
113
114 pub fn record(&self, s: VM::VMSlot, _o: ObjectReference, lxr: &LXR<VM>) {
115 let id = crate::scheduler::current_worker_ordinal().unwrap();
116 let ix = lxr.immix_space.address_in_space(s.to_address());
117 self.gc_buffer(id).push(RemSetEntry::<VM>::encode(s, ix));
118 if self.gc_buffer(id).len() >= EvacuateMatureObjects::<VM>::CAPACITY {
119 self.flush(id)
120 }
121 }
122}
123
124#[derive(Default)]
125pub struct MatureEvacuationSet {
126 pub fragmented_blocks: SegQueue<Vec<(Block, usize)>>,
127 pub fragmented_blocks_size: AtomicUsize,
128 pub blocks_in_fragmented_chunks: SegQueue<Vec<(Block, usize)>>,
129 pub blocks_in_fragmented_chunks_size: AtomicUsize,
130 pub defrag_blocks: Mutex<Vec<Block>>,
131 pub num_defrag_blocks: AtomicUsize,
132}
133
134impl MatureEvacuationSet {
135 pub fn sweep_mature_evac_candidates<VM: VMBinding>(&self, space: &ImmixSpace<VM>) {
137 let mut defrag_blocks: Vec<Block> =
138 std::mem::take(&mut *self.defrag_blocks.lock().unwrap());
139 if defrag_blocks.is_empty() {
140 return;
141 }
142 while let Some(block) = defrag_blocks.pop() {
143 if !block.is_defrag_source() || block.get_state() == BlockState::Unallocated {
144 continue;
146 }
147 block.clear_rc_table();
148 block.clear_striddle_table();
149 block.rc_sweep_mature::<VM>(space, true);
150 assert!(!block.is_defrag_source());
151 }
152 }
153
154 pub fn schedule_defrag_selection_packets<VM: VMBinding>(&self, space: &ImmixSpace<VM>) {
155 let tasks =
156 space
157 .chunk_map
158 .generate_tasks_batched(space.scheduler().num_workers(), |chunks| {
159 Box::new(SelectDefragBlocks {
160 chunks,
161 defrag_threshold: 1,
162 })
163 });
164 self.fragmented_blocks_size.store(0, Ordering::SeqCst);
165 SELECT_DEFRAG_BLOCK_JOB_COUNTER.store(tasks.len(), Ordering::SeqCst);
166 space.scheduler().work_buckets[WorkBucketStage::Unconstrained].bulk_add(tasks);
167 }
168
169 pub fn skip_block(b: Block) -> bool {
170 let s = b.get_state();
171 b.is_defrag_source() || s == BlockState::Unallocated || s == BlockState::Nursery
172 }
173
174 fn select_fragmented_blocks(
175 &self,
176 selected_blocks: &mut Vec<Block>,
177 copy_bytes: &mut usize,
178 max_copy_bytes: usize,
179 ) {
180 let mut blocks = Vec::with_capacity(self.fragmented_blocks_size.load(Ordering::SeqCst));
181 while let Some(mut x) = self.fragmented_blocks.pop() {
182 blocks.append(&mut x);
183 }
184 blocks.sort_by_key(|x| x.1);
185 while let Some((block, _dead_bytes)) = blocks.pop() {
186 if Self::skip_block(block) {
187 continue;
188 }
189 block.set_as_defrag_source(true);
190 selected_blocks.push(block);
191 *copy_bytes += (Block::BYTES - (block.calc_dead_lines() << Line::LOG_BYTES)) >> 1;
192 if *copy_bytes >= max_copy_bytes {
193 break;
194 }
195 }
196 }
197
198 #[allow(clippy::assertions_on_constants)]
199 pub fn select_mature_evacuation_candidates<VM: VMBinding>(&self, lxr: &LXR<VM>) {
200 debug_assert!(crate::plan::lxr::MATURE_EVACUATION);
201 if lxr.current_pause().unwrap() == Pause::Full {
202 let los = lxr.common().get_los();
205 los.release_rc_nursery_objects();
206 }
207 let available_clean_pages_for_defrag = if lxr.current_pause().unwrap() == Pause::Full {
209 lxr.get_total_pages()
210 .saturating_sub(lxr.get_used_pages())
211 .max(lxr.immix_space.defrag_headroom_pages())
212 } else {
213 lxr.immix_space.defrag_headroom_pages()
214 };
215 let max_copy_bytes = available_clean_pages_for_defrag << LOG_BYTES_IN_PAGE;
216 let mut copy_bytes = 0usize;
217 let mut selected_blocks = vec![];
218 self.select_fragmented_blocks(&mut selected_blocks, &mut copy_bytes, max_copy_bytes);
219 self.num_defrag_blocks
220 .store(selected_blocks.len(), Ordering::SeqCst);
221 let mut defrag_blocks = self.defrag_blocks.lock().unwrap();
222 *defrag_blocks = selected_blocks;
223 assert!(self.fragmented_blocks.is_empty());
225 assert!(self.blocks_in_fragmented_chunks.is_empty());
226 self.fragmented_blocks_size.store(0, Ordering::SeqCst);
227 self.blocks_in_fragmented_chunks_size
228 .store(0, Ordering::SeqCst);
229 }
230}