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::metadata::side_metadata::spec_defs::LOS_PAGE_REUSE_COUNT;
12use crate::util::metadata::MetadataSpec;
13use crate::util::object_enum::ClosureObjectEnumerator;
14use crate::util::object_enum::ObjectEnumerator;
15use crate::util::opaque_pointer::*;
16use crate::util::rc::RefCountHelper;
17use crate::util::treadmill::TreadMill;
18use crate::util::{Address, ObjectReference};
19use crate::vm::ObjectModel;
20use crate::vm::VMBinding;
21use std::sync::atomic::AtomicBool;
22use std::sync::atomic::AtomicUsize;
23
24#[allow(unused)]
25const PAGE_MASK: usize = !(BYTES_IN_PAGE - 1);
26const MARK_BIT: u8 = 0b01;
27const NURSERY_BIT: u8 = 0b10;
28const LOS_BIT_MASK: u8 = 0b11;
29
30pub struct LargeObjectSpace<VM: VMBinding> {
33 common: CommonSpace<VM>,
34 pr: FreeListPageResource<VM>,
35 mark_state: u8,
36 in_nursery_gc: bool,
37 treadmill: TreadMill,
38 clear_log_bit_on_sweep: bool,
39 pub num_pages_released_lazy: AtomicUsize,
40 pub rc_enabled: bool,
41 pub(crate) rc: RefCountHelper<VM>,
42 pub is_end_of_satb_or_full_gc: bool,
43 pub(crate) bump_page_reuse_count: AtomicBool,
48}
49
50impl<VM: VMBinding> SFT for LargeObjectSpace<VM> {
51 fn name(&self) -> &'static str {
52 self.get_name()
53 }
54 fn is_live(&self, object: ObjectReference) -> bool {
55 if self.rc_enabled {
56 if self.is_end_of_satb_or_full_gc {
57 return self.is_marked(object) && self.rc.count(object) > 0;
58 }
59 return self.rc.count(object) > 0;
60 }
61 self.test_mark_bit(object, self.mark_state)
62 }
63 fn is_reachable(&self, object: ObjectReference) -> bool {
64 if self.rc_enabled {
65 self.test_mark_bit(object, self.mark_state) && self.rc.count(object) > 0
66 } else {
67 self.is_live(object)
68 }
69 }
70 #[cfg(feature = "object_pinning")]
71 fn pin_object(&self, _object: ObjectReference) -> bool {
72 false
73 }
74 #[cfg(feature = "object_pinning")]
75 fn unpin_object(&self, _object: ObjectReference) -> bool {
76 false
77 }
78 #[cfg(feature = "object_pinning")]
79 fn is_object_pinned(&self, _object: ObjectReference) -> bool {
80 true
81 }
82 fn is_movable(&self) -> bool {
83 false
84 }
85 #[cfg(feature = "sanity")]
86 fn is_sane(&self) -> bool {
87 true
88 }
89
90 fn initialize_object_metadata(&self, object: ObjectReference, bytes: usize) {
91 #[cfg(feature = "vo_bit")]
93 crate::util::metadata::vo_bit::set_vo_bit(object);
94 #[cfg(all(feature = "vo_bit", debug_assertions))]
95 {
96 use crate::util::constants::LOG_BYTES_IN_PAGE;
97 let vo_addr = object.to_raw_address();
98 let offset_from_page_start = vo_addr & ((1 << LOG_BYTES_IN_PAGE) - 1) as usize;
99 debug_assert!(
100 offset_from_page_start < crate::util::metadata::vo_bit::VO_BIT_WORD_TO_REGION,
101 "The raw address of ObjectReference is not in the first 512 bytes of a page. The internal pointer searching for LOS won't work."
102 );
103 }
104
105 if self.rc_enabled {
106 self.treadmill.add_to_treadmill(object, true);
108 self.test_and_mark(object, self.mark_state);
110 if self.bump_page_reuse_count.load(Ordering::Acquire) {
112 for off in (0..bytes).step_by(BYTES_IN_PAGE) {
113 let a = object.to_raw_address() + off;
114 let count = LOS_PAGE_REUSE_COUNT.load_atomic::<u8>(a, Ordering::SeqCst);
115 let new_count = if count == u8::MAX { 0 } else { count + 1 };
116 LOS_PAGE_REUSE_COUNT.store_atomic::<u8>(a, new_count, Ordering::SeqCst);
117 }
118 }
119 return;
120 }
121
122 let allocate_as_live = self.should_allocate_as_live();
123 let into_nursery = !allocate_as_live;
124
125 {
127 let mark_nursery_state = if into_nursery {
128 self.mark_state | NURSERY_BIT
129 } else {
130 self.mark_state
131 };
132
133 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.store_atomic::<VM, u8>(
134 object,
135 mark_nursery_state,
136 None,
137 Ordering::SeqCst,
138 );
139 }
140
141 if self.common.unlog_allocated_object {
143 debug_assert!(self.common.needs_log_bit);
144 debug_assert!(
145 !allocate_as_live,
146 "Currently only ConcurrentImmix can allocate as live, and it doesn't unlog allocated objects in LOS."
147 );
148
149 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
150 } else {
151 #[cfg(debug_assertions)]
152 if self.common.needs_log_bit {
153 debug_assert_eq!(
154 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.load_atomic::<VM, u8>(
155 object,
156 None,
157 Ordering::Acquire
158 ),
159 0
160 );
161 }
162 }
163
164 self.treadmill.add_to_treadmill(object, into_nursery);
166 }
167
168 #[cfg(feature = "vo_bit")]
169 fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
170 crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
171 }
172 #[cfg(feature = "vo_bit")]
173 fn find_object_from_internal_pointer(
174 &self,
175 ptr: Address,
176 max_search_bytes: usize,
177 ) -> Option<ObjectReference> {
178 use crate::{util::metadata::vo_bit, MMAPPER};
179
180 let mmap_granularity = MMAPPER.granularity();
181
182 let mut mapped_grain = Address::MAX;
186
187 let mut cur_page = ptr.align_down(BYTES_IN_PAGE);
189 let low_page = ptr
190 .saturating_sub(max_search_bytes)
191 .align_down(BYTES_IN_PAGE);
192 while cur_page >= low_page {
193 if cur_page < mapped_grain {
194 if !cur_page.is_mapped() {
195 return None;
197 }
198 mapped_grain = cur_page.align_down(mmap_granularity);
200 }
201 if vo_bit::get_raw_vo_bit_word(cur_page) != 0 {
206 for offset in 0..vo_bit::VO_BIT_WORD_TO_REGION {
208 let addr = cur_page + offset;
209 if unsafe { vo_bit::is_vo_addr(addr) } {
210 return vo_bit::is_internal_ptr_from_vo_bit::<VM>(addr, ptr);
211 }
212 }
213 unreachable!(
214 "We found vo bit in the raw word, but we cannot find the exact address"
215 );
216 }
217
218 cur_page -= BYTES_IN_PAGE;
219 }
220 None
221 }
222 fn sft_trace_object(
223 &self,
224 queue: &mut OptionObjectQueue,
225 object: ObjectReference,
226 _worker: GCWorkerMutRef,
227 ) -> ObjectReference {
228 self.trace_object(queue, object)
229 }
230
231 fn debug_print_object_info(&self, object: ObjectReference) {
232 println!("marked = {}", self.test_mark_bit(object, self.mark_state));
233 println!("nursery = {}", self.is_in_nursery(object));
234 self.common.debug_print_object_global_info(object);
235 }
236}
237
238impl<VM: VMBinding> Space<VM> for LargeObjectSpace<VM> {
239 fn as_space(&self) -> &dyn Space<VM> {
240 self
241 }
242 fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
243 self
244 }
245 fn get_page_resource(&self) -> &dyn PageResource<VM> {
246 &self.pr
247 }
248 fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
249 Some(&mut self.pr)
250 }
251
252 fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap) {
253 self.common().initialize_sft(self.as_sft(), sft_map)
254 }
255
256 fn common(&self) -> &CommonSpace<VM> {
257 &self.common
258 }
259
260 fn release_multiple_pages(&mut self, start: Address) {
261 self.pr.release_pages(start);
262 }
263
264 fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
265 assert!(
270 self.treadmill.is_collect_nursery_empty(),
271 "Collection nursery is not empty"
272 );
273 assert!(
274 self.treadmill.is_from_space_empty(),
275 "From-space is not empty"
276 );
277
278 self.treadmill.enumerate_objects(enumerator, false);
281 }
282
283 fn clear_side_log_bits(&self) {
284 let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
285 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
286 });
287 self.treadmill.enumerate_objects(&mut enumerator, true);
290 }
291
292 fn set_side_log_bits(&self) {
293 let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
294 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
295 });
296 self.treadmill.enumerate_objects(&mut enumerator, true);
298 }
299}
300
301use crate::scheduler::GCWorker;
302use crate::util::copy::CopySemantics;
303
304impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for LargeObjectSpace<VM> {
305 fn trace_object<Q: ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(
306 &self,
307 queue: &mut Q,
308 object: ObjectReference,
309 _copy: Option<CopySemantics>,
310 _worker: &mut GCWorker<VM>,
311 ) -> ObjectReference {
312 self.trace_object(queue, object)
313 }
314 fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
315 false
316 }
317}
318
319impl<VM: VMBinding> LargeObjectSpace<VM> {
320 pub fn new(
321 args: crate::policy::space::PlanCreateSpaceArgs<VM>,
322 protect_memory_on_release: bool,
323 clear_log_bit_on_sweep: bool,
324 ) -> Self {
325 let is_discontiguous = args.vmrequest.is_discontiguous();
326 let vm_map = args.vm_map;
327 let rc_enabled = args.constraints.rc_enabled;
328 let specs = if rc_enabled {
329 vec![
330 *VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC,
331 MetadataSpec::OnSide(LOS_PAGE_REUSE_COUNT),
332 ]
333 } else {
334 vec![*VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC]
335 };
336 let policy_args =
337 args.into_policy_args(false, false, metadata::extract_side_metadata(&specs));
338 let common = CommonSpace::new(policy_args);
339 let mut pr = if is_discontiguous {
340 FreeListPageResource::new_discontiguous(vm_map)
341 } else {
342 FreeListPageResource::new_contiguous(common.start, common.extent, vm_map)
343 };
344 pr.protect_memory_on_release = if protect_memory_on_release {
345 Some(common.mmap_protection())
346 } else {
347 None
348 };
349 LargeObjectSpace {
350 pr,
351 common,
352 mark_state: 0,
353 in_nursery_gc: false,
354 treadmill: TreadMill::new(),
355 clear_log_bit_on_sweep,
356 num_pages_released_lazy: Default::default(),
357 rc_enabled: false,
358 rc: RefCountHelper::NEW,
359 is_end_of_satb_or_full_gc: false,
360 bump_page_reuse_count: AtomicBool::new(false),
361 }
362 }
363
364 fn release_object(&self, object: ObjectReference) -> usize {
365 let start = get_super_page(object.to_object_start::<VM>());
366 #[cfg(feature = "vo_bit")]
367 crate::util::metadata::vo_bit::unset_vo_bit(object);
368 if self.rc_enabled {
369 debug_assert_eq!(self.rc.count(object), 0);
370 let pages = self.pr.get_pages(start);
371 VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
375 .as_spec()
376 .extract_side_spec()
377 .bzero_metadata(start, pages * BYTES_IN_PAGE);
378 }
379 self.pr.release_pages(start)
380 }
381
382 pub fn release_rc_nursery_objects(&self) {
383 debug_assert!(self.rc_enabled);
384 for o in self.treadmill.collect_alloc_nursery() {
386 if self.rc.count(o) == 0 {
387 self.release_object(o);
388 } else {
389 self.treadmill.add_to_treadmill(o, false);
390 }
391 }
392 }
393
394 pub fn prepare(&mut self, full_heap: bool) {
395 if full_heap {
396 self.mark_state = MARK_BIT - self.mark_state;
397 }
398 self.num_pages_released_lazy.store(0, Ordering::Relaxed);
399 if self.rc_enabled {
400 return;
401 }
402 self.treadmill.flip(full_heap);
403 self.in_nursery_gc = !full_heap;
404 }
405
406 pub fn release(&mut self, full_heap: bool) {
407 if self.rc_enabled {
408 self.release_rc_nursery_objects();
409 return;
410 }
411 debug_assert!(self.treadmill.is_alloc_nursery_empty());
415
416 self.sweep_large_pages(true);
417 debug_assert!(self.treadmill.is_collect_nursery_empty());
418 if full_heap {
419 self.sweep_large_pages(false);
420 debug_assert!(self.treadmill.is_from_space_empty());
421 }
422 }
423
424 #[allow(clippy::collapsible_if)]
427 pub fn trace_object<Q: ObjectQueue>(
428 &self,
429 queue: &mut Q,
430 object: ObjectReference,
431 ) -> ObjectReference {
432 #[cfg(feature = "vo_bit")]
433 debug_assert!(
434 crate::util::metadata::vo_bit::is_vo_bit_set(object),
435 "{:x}: VO bit not set",
436 object
437 );
438 if self.rc_enabled {
439 if self.test_and_mark(object, self.mark_state) {
440 queue.enqueue(object);
441 }
442 return object;
443 }
444 let nursery_object = self.is_in_nursery(object);
445 trace!(
446 "LOS object {} {} a nursery object",
447 object,
448 if nursery_object { "is" } else { "is not" }
449 );
450 if !self.in_nursery_gc || nursery_object {
451 if self.test_and_mark(object, self.mark_state) {
454 trace!("LOS object {} is being marked now", object);
455 self.treadmill.copy(object, nursery_object);
456 if self.common.unlog_traced_object {
460 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
461 .mark_as_unlogged::<VM>(object, Ordering::SeqCst);
462 }
463 queue.enqueue(object);
464 } else {
465 trace!(
466 "LOS object {} is not being marked now, it was marked before",
467 object
468 );
469 }
470 }
471 object
472 }
473
474 fn sweep_large_pages(&mut self, sweep_nursery: bool) {
475 let sweep = |object: ObjectReference| {
476 if self.clear_log_bit_on_sweep {
481 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
482 }
483 self.release_object(object);
484 };
485 if sweep_nursery {
486 for object in self.treadmill.collect_nursery() {
487 sweep(object);
488 }
489 } else {
490 for object in self.treadmill.collect_mature() {
491 sweep(object)
492 }
493 }
494 }
495
496 pub(crate) fn enumerate_to_space_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
499 debug_assert!(self.treadmill.is_alloc_nursery_empty());
502 self.treadmill.enumerate_objects(enumerator, false);
504 }
505
506 pub fn allocate_pages(
508 &self,
509 tls: VMThread,
510 pages: usize,
511 alloc_options: AllocationOptions,
512 ) -> Address {
513 self.acquire(tls, pages, alloc_options)
514 }
515
516 pub fn attempt_mark(&self, object: ObjectReference) -> bool {
517 self.test_and_mark(object, self.mark_state)
518 }
519
520 pub fn rc_free(&self, o: ObjectReference) {
521 if self.treadmill.remove_mature(o) {
522 let pages = self.release_object(o);
523 self.num_pages_released_lazy
524 .fetch_add(pages, Ordering::Relaxed);
525 }
526 }
527
528 fn test_and_mark(&self, object: ObjectReference, value: u8) -> bool {
533 loop {
534 let mask = if self.rc_enabled {
535 MARK_BIT
536 } else if self.in_nursery_gc {
537 LOS_BIT_MASK
538 } else {
539 MARK_BIT
540 };
541 let old_value = VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
542 object,
543 None,
544 Ordering::SeqCst,
545 );
546 let mark_bit = old_value & mask;
547 if mark_bit == value {
548 return false;
549 }
550 if VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC
552 .compare_exchange_metadata::<VM, u8>(
553 object,
554 old_value,
555 old_value & !LOS_BIT_MASK | value,
556 None,
557 Ordering::SeqCst,
558 Ordering::SeqCst,
559 )
560 .is_ok()
561 {
562 break;
563 }
564 }
565 true
566 }
567
568 fn test_mark_bit(&self, object: ObjectReference, value: u8) -> bool {
569 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
570 object,
571 None,
572 Ordering::SeqCst,
573 ) & MARK_BIT
574 == value
575 }
576
577 fn is_in_nursery(&self, object: ObjectReference) -> bool {
579 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
580 object,
581 None,
582 Ordering::Relaxed,
583 ) & NURSERY_BIT
584 == NURSERY_BIT
585 }
586
587 pub fn sweep_rc_mature_objects_after_satb(&self, is_live: &impl Fn(ObjectReference) -> bool) {
588 self.treadmill.retain_mature(|o| {
589 if !is_live(*o) {
590 self.rc.set(*o, 0);
591 let pages = self.release_object(*o);
592 self.num_pages_released_lazy
593 .fetch_add(pages, Ordering::Relaxed);
594 false
595 } else {
596 true
597 }
598 });
599 }
600
601 pub fn is_marked(&self, object: ObjectReference) -> bool {
602 self.test_mark_bit(object, self.mark_state)
603 }
604}
605
606fn get_super_page(cell: Address) -> Address {
607 cell.align_down(BYTES_IN_PAGE)
608}