1use atomic::Ordering;
2
3use crate::plan::tracing::{ObjectQueue, OptionObjectQueue};
4use crate::policy::sft::GCWorkerMutRef;
5use crate::policy::sft::SFT;
6use crate::policy::space::{CommonSpace, Space};
7use crate::util::alloc::allocator::AllocationOptions;
8use crate::util::constants::BYTES_IN_PAGE;
9use crate::util::heap::{FreeListPageResource, PageResource};
10use crate::util::metadata;
11use crate::util::object_enum::ClosureObjectEnumerator;
12use crate::util::object_enum::ObjectEnumerator;
13use crate::util::opaque_pointer::*;
14use crate::util::treadmill::TreadMill;
15use crate::util::{Address, ObjectReference};
16use crate::vm::ObjectModel;
17use crate::vm::VMBinding;
18
19#[allow(unused)]
20const PAGE_MASK: usize = !(BYTES_IN_PAGE - 1);
21const MARK_BIT: u8 = 0b01;
22const NURSERY_BIT: u8 = 0b10;
23const LOS_BIT_MASK: u8 = 0b11;
24
25pub struct LargeObjectSpace<VM: VMBinding> {
28 common: CommonSpace<VM>,
29 pr: FreeListPageResource<VM>,
30 mark_state: u8,
31 in_nursery_gc: bool,
32 treadmill: TreadMill,
33 clear_log_bit_on_sweep: bool,
34}
35
36impl<VM: VMBinding> SFT for LargeObjectSpace<VM> {
37 fn name(&self) -> &'static str {
38 self.get_name()
39 }
40 fn is_live(&self, object: ObjectReference) -> bool {
41 self.test_mark_bit(object, self.mark_state)
42 }
43 #[cfg(feature = "object_pinning")]
44 fn pin_object(&self, _object: ObjectReference) -> bool {
45 false
46 }
47 #[cfg(feature = "object_pinning")]
48 fn unpin_object(&self, _object: ObjectReference) -> bool {
49 false
50 }
51 #[cfg(feature = "object_pinning")]
52 fn is_object_pinned(&self, _object: ObjectReference) -> bool {
53 true
54 }
55 fn is_movable(&self) -> bool {
56 false
57 }
58 #[cfg(feature = "sanity")]
59 fn is_sane(&self) -> bool {
60 true
61 }
62
63 fn initialize_object_metadata(&self, object: ObjectReference, _bytes: usize) {
64 #[cfg(feature = "vo_bit")]
66 crate::util::metadata::vo_bit::set_vo_bit(object);
67 #[cfg(all(feature = "vo_bit", debug_assertions))]
68 {
69 use crate::util::constants::LOG_BYTES_IN_PAGE;
70 let vo_addr = object.to_raw_address();
71 let offset_from_page_start = vo_addr & ((1 << LOG_BYTES_IN_PAGE) - 1) as usize;
72 debug_assert!(
73 offset_from_page_start < crate::util::metadata::vo_bit::VO_BIT_WORD_TO_REGION,
74 "The raw address of ObjectReference is not in the first 512 bytes of a page. The internal pointer searching for LOS won't work."
75 );
76 }
77
78 let allocate_as_live = self.should_allocate_as_live();
79 let into_nursery = !allocate_as_live;
80
81 {
83 let mark_nursery_state = if into_nursery {
84 self.mark_state | NURSERY_BIT
85 } else {
86 self.mark_state
87 };
88
89 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.store_atomic::<VM, u8>(
90 object,
91 mark_nursery_state,
92 None,
93 Ordering::SeqCst,
94 );
95 }
96
97 if self.common.unlog_allocated_object {
99 debug_assert!(self.common.needs_log_bit);
100 debug_assert!(
101 !allocate_as_live,
102 "Currently only ConcurrentImmix can allocate as live, and it doesn't unlog allocated objects in LOS."
103 );
104
105 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
106 } else {
107 #[cfg(debug_assertions)]
108 if self.common.needs_log_bit {
109 debug_assert_eq!(
110 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.load_atomic::<VM, u8>(
111 object,
112 None,
113 Ordering::Acquire
114 ),
115 0
116 );
117 }
118 }
119
120 self.treadmill.add_to_treadmill(object, into_nursery);
122 }
123
124 #[cfg(feature = "vo_bit")]
125 fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
126 crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
127 }
128 #[cfg(feature = "vo_bit")]
129 fn find_object_from_internal_pointer(
130 &self,
131 ptr: Address,
132 max_search_bytes: usize,
133 ) -> Option<ObjectReference> {
134 use crate::{util::metadata::vo_bit, MMAPPER};
135
136 let mmap_granularity = MMAPPER.granularity();
137
138 let mut mapped_grain = Address::MAX;
142
143 let mut cur_page = ptr.align_down(BYTES_IN_PAGE);
145 let low_page = ptr
146 .saturating_sub(max_search_bytes)
147 .align_down(BYTES_IN_PAGE);
148 while cur_page >= low_page {
149 if cur_page < mapped_grain {
150 if !cur_page.is_mapped() {
151 return None;
153 }
154 mapped_grain = cur_page.align_down(mmap_granularity);
156 }
157 if vo_bit::get_raw_vo_bit_word(cur_page) != 0 {
162 for offset in 0..vo_bit::VO_BIT_WORD_TO_REGION {
164 let addr = cur_page + offset;
165 if unsafe { vo_bit::is_vo_addr(addr) } {
166 return vo_bit::is_internal_ptr_from_vo_bit::<VM>(addr, ptr);
167 }
168 }
169 unreachable!(
170 "We found vo bit in the raw word, but we cannot find the exact address"
171 );
172 }
173
174 cur_page -= BYTES_IN_PAGE;
175 }
176 None
177 }
178 fn sft_trace_object(
179 &self,
180 queue: &mut OptionObjectQueue,
181 object: ObjectReference,
182 _worker: GCWorkerMutRef,
183 ) -> ObjectReference {
184 self.trace_object(queue, object)
185 }
186
187 fn debug_print_object_info(&self, object: ObjectReference) {
188 println!("marked = {}", self.test_mark_bit(object, self.mark_state));
189 println!("nursery = {}", self.is_in_nursery(object));
190 self.common.debug_print_object_global_info(object);
191 }
192}
193
194impl<VM: VMBinding> Space<VM> for LargeObjectSpace<VM> {
195 fn as_space(&self) -> &dyn Space<VM> {
196 self
197 }
198 fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
199 self
200 }
201 fn get_page_resource(&self) -> &dyn PageResource<VM> {
202 &self.pr
203 }
204 fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
205 Some(&mut self.pr)
206 }
207
208 fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap) {
209 self.common().initialize_sft(self.as_sft(), sft_map)
210 }
211
212 fn common(&self) -> &CommonSpace<VM> {
213 &self.common
214 }
215
216 fn release_multiple_pages(&mut self, start: Address) {
217 self.pr.release_pages(start);
218 }
219
220 fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
221 assert!(
226 self.treadmill.is_collect_nursery_empty(),
227 "Collection nursery is not empty"
228 );
229 assert!(
230 self.treadmill.is_from_space_empty(),
231 "From-space is not empty"
232 );
233
234 self.treadmill.enumerate_objects(enumerator, false);
237 }
238
239 fn clear_side_log_bits(&self) {
240 let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
241 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
242 });
243 self.treadmill.enumerate_objects(&mut enumerator, true);
246 }
247
248 fn set_side_log_bits(&self) {
249 let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
250 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
251 });
252 self.treadmill.enumerate_objects(&mut enumerator, true);
254 }
255}
256
257use crate::scheduler::GCWorker;
258use crate::util::copy::CopySemantics;
259
260impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for LargeObjectSpace<VM> {
261 fn trace_object<Q: ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(
262 &self,
263 queue: &mut Q,
264 object: ObjectReference,
265 _copy: Option<CopySemantics>,
266 _worker: &mut GCWorker<VM>,
267 ) -> ObjectReference {
268 self.trace_object(queue, object)
269 }
270 fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
271 false
272 }
273}
274
275impl<VM: VMBinding> LargeObjectSpace<VM> {
276 pub fn new(
277 args: crate::policy::space::PlanCreateSpaceArgs<VM>,
278 protect_memory_on_release: bool,
279 clear_log_bit_on_sweep: bool,
280 ) -> Self {
281 let is_discontiguous = args.vmrequest.is_discontiguous();
282 let vm_map = args.vm_map;
283 let common = CommonSpace::new(args.into_policy_args(
284 false,
285 false,
286 metadata::extract_side_metadata(&[*VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC]),
287 ));
288 let mut pr = if is_discontiguous {
289 FreeListPageResource::new_discontiguous(vm_map)
290 } else {
291 FreeListPageResource::new_contiguous(common.start, common.extent, vm_map)
292 };
293 pr.protect_memory_on_release = if protect_memory_on_release {
294 Some(common.mmap_protection())
295 } else {
296 None
297 };
298 LargeObjectSpace {
299 pr,
300 common,
301 mark_state: 0,
302 in_nursery_gc: false,
303 treadmill: TreadMill::new(),
304 clear_log_bit_on_sweep,
305 }
306 }
307
308 pub fn prepare(&mut self, full_heap: bool) {
309 if full_heap {
310 self.mark_state = MARK_BIT - self.mark_state;
311 }
312 self.treadmill.flip(full_heap);
313 self.in_nursery_gc = !full_heap;
314 }
315
316 pub fn release(&mut self, full_heap: bool) {
317 debug_assert!(self.treadmill.is_alloc_nursery_empty());
321
322 self.sweep_large_pages(true);
323 debug_assert!(self.treadmill.is_collect_nursery_empty());
324 if full_heap {
325 self.sweep_large_pages(false);
326 debug_assert!(self.treadmill.is_from_space_empty());
327 }
328 }
329
330 #[allow(clippy::collapsible_if)]
333 pub fn trace_object<Q: ObjectQueue>(
334 &self,
335 queue: &mut Q,
336 object: ObjectReference,
337 ) -> ObjectReference {
338 #[cfg(feature = "vo_bit")]
339 debug_assert!(
340 crate::util::metadata::vo_bit::is_vo_bit_set(object),
341 "{:x}: VO bit not set",
342 object
343 );
344 let nursery_object = self.is_in_nursery(object);
345 trace!(
346 "LOS object {} {} a nursery object",
347 object,
348 if nursery_object { "is" } else { "is not" }
349 );
350 if !self.in_nursery_gc || nursery_object {
351 if self.test_and_mark(object, self.mark_state) {
354 trace!("LOS object {} is being marked now", object);
355 self.treadmill.copy(object, nursery_object);
356 if self.common.unlog_traced_object {
360 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
361 .mark_as_unlogged::<VM>(object, Ordering::SeqCst);
362 }
363 queue.enqueue(object);
364 } else {
365 trace!(
366 "LOS object {} is not being marked now, it was marked before",
367 object
368 );
369 }
370 }
371 object
372 }
373
374 fn sweep_large_pages(&mut self, sweep_nursery: bool) {
375 let sweep = |object: ObjectReference| {
376 #[cfg(feature = "vo_bit")]
377 crate::util::metadata::vo_bit::unset_vo_bit(object);
378 if self.clear_log_bit_on_sweep {
380 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
381 }
382 self.pr
383 .release_pages(get_super_page(object.to_object_start::<VM>()));
384 };
385 if sweep_nursery {
386 for object in self.treadmill.collect_nursery() {
387 sweep(object);
388 }
389 } else {
390 for object in self.treadmill.collect_mature() {
391 sweep(object)
392 }
393 }
394 }
395
396 pub(crate) fn enumerate_to_space_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
399 debug_assert!(self.treadmill.is_alloc_nursery_empty());
402 self.treadmill.enumerate_objects(enumerator, false);
404 }
405
406 pub fn allocate_pages(
408 &self,
409 tls: VMThread,
410 pages: usize,
411 alloc_options: AllocationOptions,
412 ) -> Address {
413 self.acquire(tls, pages, alloc_options)
414 }
415
416 fn test_and_mark(&self, object: ObjectReference, value: u8) -> bool {
421 loop {
422 let mask = if self.in_nursery_gc {
423 LOS_BIT_MASK
424 } else {
425 MARK_BIT
426 };
427 let old_value = VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
428 object,
429 None,
430 Ordering::SeqCst,
431 );
432 let mark_bit = old_value & mask;
433 if mark_bit == value {
434 return false;
435 }
436 if VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC
438 .compare_exchange_metadata::<VM, u8>(
439 object,
440 old_value,
441 old_value & !LOS_BIT_MASK | value,
442 None,
443 Ordering::SeqCst,
444 Ordering::SeqCst,
445 )
446 .is_ok()
447 {
448 break;
449 }
450 }
451 true
452 }
453
454 fn test_mark_bit(&self, object: ObjectReference, value: u8) -> bool {
455 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
456 object,
457 None,
458 Ordering::SeqCst,
459 ) & MARK_BIT
460 == value
461 }
462
463 fn is_in_nursery(&self, object: ObjectReference) -> bool {
465 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
466 object,
467 None,
468 Ordering::Relaxed,
469 ) & NURSERY_BIT
470 == NURSERY_BIT
471 }
472
473 pub fn is_marked(&self, object: ObjectReference) -> bool {
474 self.test_mark_bit(object, self.mark_state)
475 }
476}
477
478fn get_super_page(cell: Address) -> Address {
479 cell.align_down(BYTES_IN_PAGE)
480}