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);
26
27const MARK_BIT: u8 = 0b01;
28const NURSERY_BIT: u8 = 0b10;
29#[allow(unused)]
30const LOS_BIT_MASK: u8 = 0b11;
31
32mod mark_nursery_bits_states {
105 use super::NURSERY_BIT;
106
107 pub fn is_nursery(state: u8) -> bool {
109 state & NURSERY_BIT == NURSERY_BIT
110 }
111
112 pub fn is_marked(state: u8, mark_state: u8) -> bool {
115 state == mark_state
116 }
117}
118
119pub struct LargeObjectSpace<VM: VMBinding> {
122 common: CommonSpace<VM>,
123 pr: FreeListPageResource<VM>,
124 mark_state: u8,
125 in_nursery_gc: bool,
126 treadmill: TreadMill,
127 clear_log_bit_on_sweep: bool,
128 pub num_pages_released_lazy: AtomicUsize,
129 pub rc_enabled: bool,
130 pub(crate) rc: RefCountHelper<VM>,
131 pub is_end_of_satb_or_full_gc: bool,
132 pub(crate) bump_page_reuse_count: AtomicBool,
137}
138
139impl<VM: VMBinding> SFT for LargeObjectSpace<VM> {
140 fn name(&self) -> &'static str {
141 self.get_name()
142 }
143 fn is_live(&self, object: ObjectReference) -> bool {
144 if self.rc_enabled {
145 if self.is_end_of_satb_or_full_gc {
146 return self.is_marked(object) && self.rc.count(object) > 0;
147 }
148 return self.rc.count(object) > 0;
149 }
150 self.is_marked(object)
151 }
152 fn is_reachable(&self, object: ObjectReference) -> bool {
153 if self.rc_enabled {
154 self.test_mark_bit(object, self.mark_state) && self.rc.count(object) > 0
155 } else {
156 self.is_live(object)
157 }
158 }
159 #[cfg(feature = "object_pinning")]
160 fn pin_object(&self, _object: ObjectReference) -> bool {
161 false
162 }
163 #[cfg(feature = "object_pinning")]
164 fn unpin_object(&self, _object: ObjectReference) -> bool {
165 false
166 }
167 #[cfg(feature = "object_pinning")]
168 fn is_object_pinned(&self, _object: ObjectReference) -> bool {
169 true
170 }
171 fn is_movable(&self) -> bool {
172 false
173 }
174 #[cfg(feature = "sanity")]
175 fn is_sane(&self) -> bool {
176 true
177 }
178
179 fn initialize_object_metadata(&self, object: ObjectReference, bytes: usize) {
180 #[cfg(feature = "vo_bit")]
182 crate::util::metadata::vo_bit::set_vo_bit(object);
183 #[cfg(all(feature = "vo_bit", debug_assertions))]
184 {
185 use crate::util::constants::LOG_BYTES_IN_PAGE;
186 let vo_addr = object.to_raw_address();
187 let offset_from_page_start = vo_addr & ((1 << LOG_BYTES_IN_PAGE) - 1) as usize;
188 debug_assert!(
189 offset_from_page_start < crate::util::metadata::vo_bit::VO_BIT_WORD_TO_REGION,
190 "The raw address of ObjectReference is not in the first 512 bytes of a page. The internal pointer searching for LOS won't work."
191 );
192 }
193
194 if self.rc_enabled {
195 self.treadmill.add_to_treadmill(object, true);
197 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.store_atomic::<VM, u8>(
199 object,
200 self.mark_state,
201 None,
202 Ordering::SeqCst,
203 );
204 if self.bump_page_reuse_count.load(Ordering::Acquire) {
206 for off in (0..bytes).step_by(BYTES_IN_PAGE) {
207 let a = object.to_raw_address() + off;
208 let count = LOS_PAGE_REUSE_COUNT.load_atomic::<u8>(a, Ordering::SeqCst);
209 let new_count = if count == u8::MAX { 0 } else { count + 1 };
210 LOS_PAGE_REUSE_COUNT.store_atomic::<u8>(a, new_count, Ordering::SeqCst);
211 }
212 }
213 return;
214 }
215
216 let allocate_as_live = self.should_allocate_as_live();
217 let into_nursery = !allocate_as_live;
218
219 {
220 let mark_nursery_state = if into_nursery {
221 NURSERY_BIT
226 } else {
227 self.mark_state
232 };
233
234 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.store_atomic::<VM, u8>(
235 object,
236 mark_nursery_state,
237 None,
238 Ordering::SeqCst,
239 );
240 }
241
242 if self.common.unlog_allocated_object {
244 debug_assert!(self.common.needs_log_bit);
245 debug_assert!(
246 !allocate_as_live,
247 "Currently only ConcurrentImmix can allocate as live, and it doesn't unlog allocated objects in LOS."
248 );
249
250 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
251 } else {
252 #[cfg(debug_assertions)]
253 if self.common.needs_log_bit {
254 debug_assert_eq!(
255 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.load_atomic::<VM, u8>(
256 object,
257 None,
258 Ordering::Acquire
259 ),
260 0
261 );
262 }
263 }
264
265 self.treadmill.add_to_treadmill(object, into_nursery);
267 }
268
269 #[cfg(feature = "vo_bit")]
270 fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
271 crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
272 }
273 #[cfg(feature = "vo_bit")]
274 fn find_object_from_internal_pointer(
275 &self,
276 ptr: Address,
277 max_search_bytes: usize,
278 ) -> Option<ObjectReference> {
279 use crate::{util::metadata::vo_bit, MMAPPER};
280
281 let mmap_granularity = MMAPPER.granularity();
282
283 let mut mapped_grain = Address::MAX;
287
288 let mut cur_page = ptr.align_down(BYTES_IN_PAGE);
290 let low_page = ptr
291 .saturating_sub(max_search_bytes)
292 .align_down(BYTES_IN_PAGE);
293 while cur_page >= low_page {
294 if cur_page < mapped_grain {
295 if !cur_page.is_mapped() {
296 return None;
298 }
299 mapped_grain = cur_page.align_down(mmap_granularity);
301 }
302 if vo_bit::get_raw_vo_bit_word(cur_page) != 0 {
307 for offset in 0..vo_bit::VO_BIT_WORD_TO_REGION {
309 let addr = cur_page + offset;
310 if unsafe { vo_bit::is_vo_addr(addr) } {
311 return vo_bit::is_internal_ptr_from_vo_bit::<VM>(addr, ptr);
312 }
313 }
314 unreachable!(
315 "We found vo bit in the raw word, but we cannot find the exact address"
316 );
317 }
318
319 cur_page -= BYTES_IN_PAGE;
320 }
321 None
322 }
323 fn sft_trace_object(
324 &self,
325 queue: &mut OptionObjectQueue,
326 object: ObjectReference,
327 _worker: GCWorkerMutRef,
328 ) -> ObjectReference {
329 self.trace_object(queue, object)
330 }
331
332 fn debug_print_object_info(&self, object: ObjectReference) {
333 let mark_nursery_state = VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC
334 .load_atomic::<VM, u8>(object, None, Ordering::SeqCst);
335 let mark_state = self.mark_state;
336 println!("mark_nursery_state = 0b{:02b}", mark_nursery_state);
337 println!("LOS mark state = {}", mark_state);
338 println!("marked = {}", self.is_marked(object));
339 if self.rc_enabled {
340 let rc = self.rc.count(object);
341 println!("RC = {}", rc);
342 } else {
343 let is_in_nursery = mark_nursery_bits_states::is_nursery(mark_nursery_state);
344 println!("is in nursery = {}", is_in_nursery);
345 }
346 self.common.debug_print_object_global_info(object);
347 }
348}
349
350impl<VM: VMBinding> Space<VM> for LargeObjectSpace<VM> {
351 fn as_space(&self) -> &dyn Space<VM> {
352 self
353 }
354 fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
355 self
356 }
357 fn get_page_resource(&self) -> &dyn PageResource<VM> {
358 &self.pr
359 }
360 fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
361 Some(&mut self.pr)
362 }
363
364 fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap) {
365 self.common().initialize_sft(self.as_sft(), sft_map)
366 }
367
368 fn common(&self) -> &CommonSpace<VM> {
369 &self.common
370 }
371
372 fn release_multiple_pages(&mut self, start: Address) {
373 self.pr.release_pages(start);
374 }
375
376 fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
377 assert!(
382 self.treadmill.is_collect_nursery_empty(),
383 "Collection nursery is not empty"
384 );
385 assert!(
386 self.treadmill.is_from_space_empty(),
387 "From-space is not empty"
388 );
389
390 self.treadmill.enumerate_objects(enumerator, false);
393 }
394
395 fn clear_side_log_bits(&self) {
396 let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
397 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
398 });
399 self.treadmill.enumerate_objects(&mut enumerator, true);
402 }
403
404 fn set_side_log_bits(&self) {
405 let mut enumerator = ClosureObjectEnumerator::<_, VM>::new(|object| {
406 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(object, Ordering::SeqCst);
407 });
408 self.treadmill.enumerate_objects(&mut enumerator, true);
410 }
411}
412
413use crate::scheduler::GCWorker;
414use crate::util::copy::CopySemantics;
415
416impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for LargeObjectSpace<VM> {
417 fn trace_object<Q: ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(
418 &self,
419 queue: &mut Q,
420 object: ObjectReference,
421 _copy: Option<CopySemantics>,
422 _worker: &mut GCWorker<VM>,
423 ) -> ObjectReference {
424 self.trace_object(queue, object)
425 }
426 fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
427 false
428 }
429}
430
431impl<VM: VMBinding> LargeObjectSpace<VM> {
432 pub fn new(
433 args: crate::policy::space::PlanCreateSpaceArgs<VM>,
434 protect_memory_on_release: bool,
435 clear_log_bit_on_sweep: bool,
436 ) -> Self {
437 let is_discontiguous = args.vmrequest.is_discontiguous();
438 let vm_map = args.vm_map;
439 let rc_enabled = args.constraints.rc_enabled;
440 let specs = if rc_enabled {
441 vec![
442 *VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC,
443 MetadataSpec::OnSide(LOS_PAGE_REUSE_COUNT),
444 ]
445 } else {
446 vec![*VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC]
447 };
448 let policy_args =
449 args.into_policy_args(false, false, metadata::extract_side_metadata(&specs));
450 let common = CommonSpace::new(policy_args);
451 let mut pr = if is_discontiguous {
452 FreeListPageResource::new_discontiguous(vm_map)
453 } else {
454 FreeListPageResource::new_contiguous(common.start, common.extent, vm_map)
455 };
456 pr.protect_memory_on_release = if protect_memory_on_release {
457 Some(common.mmap_protection())
458 } else {
459 None
460 };
461 LargeObjectSpace {
462 pr,
463 common,
464 mark_state: 0,
465 in_nursery_gc: false,
466 treadmill: TreadMill::new(),
467 clear_log_bit_on_sweep,
468 num_pages_released_lazy: Default::default(),
469 rc_enabled: false,
470 rc: RefCountHelper::NEW,
471 is_end_of_satb_or_full_gc: false,
472 bump_page_reuse_count: AtomicBool::new(false),
473 }
474 }
475
476 fn release_object(&self, object: ObjectReference) -> usize {
477 let start = get_super_page(object.to_object_start::<VM>());
478 #[cfg(feature = "vo_bit")]
479 crate::util::metadata::vo_bit::unset_vo_bit(object);
480 if self.rc_enabled {
481 debug_assert_eq!(self.rc.count(object), 0);
482 let pages = self.pr.get_pages(start);
483 VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
487 .as_spec()
488 .extract_side_spec()
489 .bzero_metadata(start, pages * BYTES_IN_PAGE);
490 }
491 self.pr.release_pages(start)
492 }
493
494 pub fn release_rc_nursery_objects(&self) {
495 debug_assert!(self.rc_enabled);
496 for o in self.treadmill.collect_alloc_nursery() {
498 if self.rc.count(o) == 0 {
499 self.release_object(o);
500 } else {
501 self.treadmill.add_to_treadmill(o, false);
502 }
503 }
504 }
505
506 pub fn prepare(&mut self, full_heap: bool) {
507 if full_heap {
508 self.mark_state = MARK_BIT - self.mark_state;
509 }
510 self.num_pages_released_lazy.store(0, Ordering::Relaxed);
511 if self.rc_enabled {
512 return;
513 }
514 self.treadmill.flip(full_heap);
515 self.in_nursery_gc = !full_heap;
516 }
517
518 pub fn release(&mut self, full_heap: bool) {
519 if self.rc_enabled {
520 self.release_rc_nursery_objects();
521 return;
522 }
523 debug_assert!(self.treadmill.is_alloc_nursery_empty());
527
528 self.sweep_large_pages(true);
529 debug_assert!(self.treadmill.is_collect_nursery_empty());
530 if full_heap {
531 self.sweep_large_pages(false);
532 debug_assert!(self.treadmill.is_from_space_empty());
533 }
534 }
535
536 #[allow(clippy::collapsible_if)]
539 pub fn trace_object<Q: ObjectQueue>(
540 &self,
541 queue: &mut Q,
542 object: ObjectReference,
543 ) -> ObjectReference {
544 #[cfg(feature = "vo_bit")]
545 debug_assert!(
546 crate::util::metadata::vo_bit::is_vo_bit_set(object),
547 "{:x}: VO bit not set",
548 object
549 );
550
551 if self.rc_enabled {
552 if self.test_and_mark(object).is_ok() {
553 queue.enqueue(object);
554 }
555 return object;
556 }
557
558 if let Ok(old_state) = self.test_and_mark(object) {
563 let was_nursery_object = mark_nursery_bits_states::is_nursery(old_state);
564 trace!(
565 "Marked LOS object {}. It {} a nursery object",
566 object,
567 if was_nursery_object { "was" } else { "was not" }
568 );
569 self.treadmill.copy(object, was_nursery_object);
571 if self.common.unlog_traced_object {
575 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
576 .mark_as_unlogged::<VM>(object, Ordering::SeqCst);
577 }
578 queue.enqueue(object);
579 } else {
580 trace!("LOS object {} is already marked", object);
581 }
582
583 object
584 }
585
586 fn sweep_large_pages(&mut self, sweep_nursery: bool) {
587 let sweep = |object: ObjectReference| {
588 if self.clear_log_bit_on_sweep {
593 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.clear::<VM>(object, Ordering::SeqCst);
594 }
595 self.release_object(object);
596 };
597 if sweep_nursery {
598 for object in self.treadmill.collect_nursery() {
599 sweep(object);
600 }
601 } else {
602 for object in self.treadmill.collect_mature() {
603 sweep(object)
604 }
605 }
606 }
607
608 pub(crate) fn enumerate_to_space_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
611 debug_assert!(self.treadmill.is_alloc_nursery_empty());
614 self.treadmill.enumerate_objects(enumerator, false);
616 }
617
618 pub fn allocate_pages(
620 &self,
621 tls: VMThread,
622 pages: usize,
623 alloc_options: AllocationOptions,
624 ) -> Address {
625 self.acquire(tls, pages, alloc_options)
626 }
627
628 pub fn attempt_mark(&self, object: ObjectReference) -> bool {
630 self.test_and_mark(object).is_ok()
631 }
632
633 pub fn rc_free(&self, o: ObjectReference) {
634 if self.treadmill.remove_mature(o) {
635 let pages = self.release_object(o);
636 self.num_pages_released_lazy
637 .fetch_add(pages, Ordering::Relaxed);
638 }
639 }
640
641 fn test_and_mark(&self, object: ObjectReference) -> Result<u8, u8> {
647 let mark_state = self.mark_state;
648 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.fetch_update_metadata::<VM, u8, _>(
649 object,
650 Ordering::SeqCst,
651 Ordering::SeqCst,
652 |old_value| {
653 (!mark_nursery_bits_states::is_marked(old_value, mark_state)).then_some(mark_state)
654 },
655 )
656 }
657
658 fn test_mark_bit(&self, object: ObjectReference, value: u8) -> bool {
660 VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC.load_atomic::<VM, u8>(
661 object,
662 None,
663 Ordering::SeqCst,
664 ) & MARK_BIT
665 == value
666 }
667
668 pub fn sweep_rc_mature_objects_after_satb(&self, is_live: &impl Fn(ObjectReference) -> bool) {
669 self.treadmill.retain_mature(|o| {
670 if !is_live(*o) {
671 self.rc.set(*o, 0);
672 let pages = self.release_object(*o);
673 self.num_pages_released_lazy
674 .fetch_add(pages, Ordering::Relaxed);
675 false
676 } else {
677 true
678 }
679 });
680 }
681
682 pub fn is_marked(&self, object: ObjectReference) -> bool {
684 let mark_nursery_state = VM::VMObjectModel::LOCAL_LOS_MARK_NURSERY_SPEC
685 .load_atomic::<VM, u8>(object, None, Ordering::SeqCst);
686
687 mark_nursery_bits_states::is_marked(mark_nursery_state, self.mark_state)
688 }
689}
690
691fn get_super_page(cell: Address) -> Address {
692 cell.align_down(BYTES_IN_PAGE)
693}