Skip to content

Commit f78de4e

Browse files
authored
Finalizable type (#609)
This PR adds a binding-defined type for finalizable objects. For some languages like Julia, their finalizers are actually object + function pointer, and one object can be registered with multiple finalize functions. To support this, we allow a binding to define their finalizable object type. By default, we provide an implementation that the finalizable is just an `ObjectReference`. This PR also adds APIs to allow a binding to manually pop finalizable objects that have been registered. This is cherry-picked from #578. This allows bindings to implement the finalize-on-exit behavior, or any other language-specific finalize behavior (e.g. Julia allows manually finalizing objects).
1 parent 6410f0a commit f78de4e

File tree

6 files changed

+172
-46
lines changed

6 files changed

+172
-46
lines changed

src/memory_manager.rs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::util::heap::layout::vm_layout_constants::HEAP_END;
2222
use crate::util::heap::layout::vm_layout_constants::HEAP_START;
2323
use crate::util::opaque_pointer::*;
2424
use crate::util::{Address, ObjectReference};
25+
use crate::vm::ReferenceGlue;
2526
use crate::vm::VMBinding;
2627
use std::sync::atomic::Ordering;
2728

@@ -472,7 +473,10 @@ pub fn harness_end<VM: VMBinding>(mmtk: &'static MMTK<VM>) {
472473
/// Arguments:
473474
/// * `mmtk`: A reference to an MMTk instance
474475
/// * `object`: The object that has a finalizer
475-
pub fn add_finalizer<VM: VMBinding>(mmtk: &'static MMTK<VM>, object: ObjectReference) {
476+
pub fn add_finalizer<VM: VMBinding>(
477+
mmtk: &'static MMTK<VM>,
478+
object: <VM::VMReferenceGlue as ReferenceGlue<VM>>::FinalizableType,
479+
) {
476480
if *mmtk.options.no_finalizer {
477481
warn!("add_finalizer() is called when no_finalizer = true");
478482
}
@@ -488,9 +492,11 @@ pub fn add_finalizer<VM: VMBinding>(mmtk: &'static MMTK<VM>, object: ObjectRefer
488492
///
489493
/// Arguments:
490494
/// * `mmtk`: A reference to an MMTk instance.
491-
pub fn get_finalized_object<VM: VMBinding>(mmtk: &'static MMTK<VM>) -> Option<ObjectReference> {
495+
pub fn get_finalized_object<VM: VMBinding>(
496+
mmtk: &'static MMTK<VM>,
497+
) -> Option<<VM::VMReferenceGlue as ReferenceGlue<VM>>::FinalizableType> {
492498
if *mmtk.options.no_finalizer {
493-
warn!("get_object_for_finalization() is called when no_finalizer = true");
499+
warn!("get_finalized_object() is called when no_finalizer = true");
494500
}
495501

496502
mmtk.finalizable_processor
@@ -499,6 +505,46 @@ pub fn get_finalized_object<VM: VMBinding>(mmtk: &'static MMTK<VM>) -> Option<Ob
499505
.get_ready_object()
500506
}
501507

508+
/// Pop all the finalizers that were registered for finalization. The returned objects may or may not be ready for
509+
/// finalization. After this call, MMTk's finalizer processor should have no registered finalizer any more.
510+
///
511+
/// This is useful for some VMs which require all finalizable objects to be finalized on exit.
512+
///
513+
/// Arguments:
514+
/// * `mmtk`: A reference to an MMTk instance.
515+
pub fn get_all_finalizers<VM: VMBinding>(
516+
mmtk: &'static MMTK<VM>,
517+
) -> Vec<<VM::VMReferenceGlue as ReferenceGlue<VM>>::FinalizableType> {
518+
if *mmtk.options.no_finalizer {
519+
warn!("get_all_finalizers() is called when no_finalizer = true");
520+
}
521+
522+
mmtk.finalizable_processor
523+
.lock()
524+
.unwrap()
525+
.get_all_finalizers()
526+
}
527+
528+
/// Pop finalizers that were registered and associated with a certain object. The returned objects may or may not be ready for finalization.
529+
/// This is useful for some VMs that may manually execute finalize method for an object.
530+
///
531+
/// Arguments:
532+
/// * `mmtk`: A reference to an MMTk instance.
533+
/// * `object`: the given object that MMTk will pop its finalizers
534+
pub fn get_finalizers_for<VM: VMBinding>(
535+
mmtk: &'static MMTK<VM>,
536+
object: ObjectReference,
537+
) -> Vec<<VM::VMReferenceGlue as ReferenceGlue<VM>>::FinalizableType> {
538+
if *mmtk.options.no_finalizer {
539+
warn!("get_finalizers() is called when no_finalizer = true");
540+
}
541+
542+
mmtk.finalizable_processor
543+
.lock()
544+
.unwrap()
545+
.get_finalizers_for(object)
546+
}
547+
502548
/// Get the number of workers. MMTk spawns worker threads for the 'threads' defined in the options.
503549
/// So the number of workers is derived from the threads option. Note the feature single_worker overwrites
504550
/// the threads option, and force one worker thread.

src/mmtk.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::util::options::{Options, UnsafeOptionsWrapper};
1111
use crate::util::reference_processor::ReferenceProcessors;
1212
#[cfg(feature = "sanity")]
1313
use crate::util::sanity::sanity_checker::SanityChecker;
14+
use crate::vm::ReferenceGlue;
1415
use crate::vm::VMBinding;
1516
use std::default::Default;
1617
use std::sync::atomic::{AtomicBool, Ordering};
@@ -43,7 +44,8 @@ pub static SFT_MAP: InitializeOnce<SFTMap<'static>> = InitializeOnce::new();
4344
pub struct MMTK<VM: VMBinding> {
4445
pub(crate) plan: Box<dyn Plan<VM = VM>>,
4546
pub(crate) reference_processors: ReferenceProcessors,
46-
pub(crate) finalizable_processor: Mutex<FinalizableProcessor>,
47+
pub(crate) finalizable_processor:
48+
Mutex<FinalizableProcessor<<VM::VMReferenceGlue as ReferenceGlue<VM>>::FinalizableType>>,
4749
pub(crate) options: Arc<UnsafeOptionsWrapper>,
4850
pub(crate) scheduler: Arc<GCWorkScheduler<VM>>,
4951
#[cfg(feature = "sanity")]
@@ -76,7 +78,9 @@ impl<VM: VMBinding> MMTK<VM> {
7678
MMTK {
7779
plan,
7880
reference_processors: ReferenceProcessors::new(),
79-
finalizable_processor: Mutex::new(FinalizableProcessor::new()),
81+
finalizable_processor: Mutex::new(FinalizableProcessor::<
82+
<VM::VMReferenceGlue as ReferenceGlue<VM>>::FinalizableType,
83+
>::new()),
8084
options,
8185
scheduler,
8286
#[cfg(feature = "sanity")]

src/util/finalizable_processor.rs

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::scheduler::gc_work::ProcessEdgesWork;
22
use crate::scheduler::{GCWork, GCWorker};
3-
use crate::util::{ObjectReference, VMWorkerThread};
3+
use crate::util::ObjectReference;
4+
use crate::util::VMWorkerThread;
5+
use crate::vm::Finalizable;
46
use crate::vm::{Collection, VMBinding};
57
use crate::MMTK;
68
use std::marker::PhantomData;
@@ -9,18 +11,18 @@ use std::marker::PhantomData;
911
// TODO: we should consider if we want to merge FinalizableProcessor with ReferenceProcessor,
1012
// and treat final reference as a special reference type in ReferenceProcessor.
1113
#[derive(Default)]
12-
pub struct FinalizableProcessor {
14+
pub struct FinalizableProcessor<F: Finalizable> {
1315
/// Candidate objects that has finalizers with them
14-
candidates: Vec<ObjectReference>,
16+
candidates: Vec<F>,
1517
/// Index into candidates to record where we are up to in the last scan of the candidates.
1618
/// Index after nursery_index are new objects inserted after the last GC.
1719
nursery_index: usize,
1820
/// Objects that can be finalized. They are actually dead, but we keep them alive
1921
/// until the binding pops them from the queue.
20-
ready_for_finalize: Vec<ObjectReference>,
22+
ready_for_finalize: Vec<F>,
2123
}
2224

23-
impl FinalizableProcessor {
25+
impl<F: Finalizable> FinalizableProcessor<F> {
2426
pub fn new() -> Self {
2527
Self {
2628
candidates: vec![],
@@ -29,22 +31,12 @@ impl FinalizableProcessor {
2931
}
3032
}
3133

32-
pub fn add(&mut self, object: ObjectReference) {
34+
pub fn add(&mut self, object: F) {
3335
self.candidates.push(object);
3436
}
3537

36-
fn get_forwarded_finalizable<E: ProcessEdgesWork>(
37-
e: &mut E,
38-
object: ObjectReference,
39-
) -> ObjectReference {
40-
e.trace_object(object)
41-
}
42-
43-
fn return_for_finalize<E: ProcessEdgesWork>(
44-
e: &mut E,
45-
object: ObjectReference,
46-
) -> ObjectReference {
47-
e.trace_object(object)
38+
fn forward_finalizable_reference<E: ProcessEdgesWork>(e: &mut E, finalizable: &mut F) {
39+
finalizable.keep_alive::<E>(e);
4840
}
4941

5042
pub fn scan<E: ProcessEdgesWork>(&mut self, tls: VMWorkerThread, e: &mut E, nursery: bool) {
@@ -55,29 +47,29 @@ impl FinalizableProcessor {
5547
// theoratically we could do the following loop at any time in a GC (not necessarily after closure phase).
5648
// But we have to iterate through candidates after closure.
5749
self.candidates.append(&mut self.ready_for_finalize);
50+
debug_assert!(self.ready_for_finalize.is_empty());
5851

59-
for reff in self
60-
.candidates
61-
.drain(start..)
62-
.collect::<Vec<ObjectReference>>()
63-
{
52+
for mut f in self.candidates.drain(start..).collect::<Vec<F>>() {
53+
let reff = f.get_reference();
6454
trace!("Pop {:?} for finalization", reff);
6555
if reff.is_live() {
66-
let res = FinalizableProcessor::get_forwarded_finalizable(e, reff);
67-
trace!("{:?} is live, push {:?} back to candidates", reff, res);
68-
self.candidates.push(res);
56+
FinalizableProcessor::<F>::forward_finalizable_reference(e, &mut f);
57+
trace!("{:?} is live, push {:?} back to candidates", reff, f);
58+
self.candidates.push(f);
6959
continue;
7060
}
7161

72-
let retained = FinalizableProcessor::return_for_finalize(e, reff);
73-
self.ready_for_finalize.push(retained);
74-
trace!(
75-
"{:?} is not live, push {:?} to ready_for_finalize",
76-
reff,
77-
retained
78-
);
62+
// We should not at this point mark the object as live. A binding may register an object
63+
// multiple times with different finalizer methods. If we mark the object as live here, and encounter
64+
// the same object later in the candidates list (possibly with a different finalizer method),
65+
// we will erroneously think the object never died, and won't push it to the ready_to_finalize
66+
// queue.
67+
// So we simply push the object to the ready_for_finalize queue, and mark them as live objects later.
68+
self.ready_for_finalize.push(f);
7969
}
80-
e.flush();
70+
71+
// Keep the finalizable objects alive.
72+
self.forward_finalizable(e, nursery);
8173

8274
self.nursery_index = self.candidates.len();
8375

@@ -87,20 +79,51 @@ impl FinalizableProcessor {
8779
pub fn forward_candidate<E: ProcessEdgesWork>(&mut self, e: &mut E, _nursery: bool) {
8880
self.candidates
8981
.iter_mut()
90-
.for_each(|reff| *reff = FinalizableProcessor::get_forwarded_finalizable(e, *reff));
82+
.for_each(|f| FinalizableProcessor::<F>::forward_finalizable_reference(e, f));
9183
e.flush();
9284
}
9385

9486
pub fn forward_finalizable<E: ProcessEdgesWork>(&mut self, e: &mut E, _nursery: bool) {
9587
self.ready_for_finalize
9688
.iter_mut()
97-
.for_each(|reff| *reff = FinalizableProcessor::get_forwarded_finalizable(e, *reff));
89+
.for_each(|f| FinalizableProcessor::<F>::forward_finalizable_reference(e, f));
9890
e.flush();
9991
}
10092

101-
pub fn get_ready_object(&mut self) -> Option<ObjectReference> {
93+
pub fn get_ready_object(&mut self) -> Option<F> {
10294
self.ready_for_finalize.pop()
10395
}
96+
97+
pub fn get_all_finalizers(&mut self) -> Vec<F> {
98+
let mut ret = std::mem::take(&mut self.candidates);
99+
let ready_objects = std::mem::take(&mut self.ready_for_finalize);
100+
101+
ret.extend(ready_objects);
102+
ret
103+
}
104+
105+
pub fn get_finalizers_for(&mut self, object: ObjectReference) -> Vec<F> {
106+
// Drain filter for finalizers that equal to 'object':
107+
// * for elements that equal to 'object', they will be removed from the original vec, and returned.
108+
// * for elements that do not equal to 'object', they will be left in the original vec.
109+
// TODO: We should replace this with `vec.drain_filter()` when it is stablized.
110+
let drain_filter = |vec: &mut Vec<F>| -> Vec<F> {
111+
let mut i = 0;
112+
let mut ret = vec![];
113+
while i < vec.len() {
114+
if vec[i].get_reference() == object {
115+
let val = vec.remove(i);
116+
ret.push(val);
117+
} else {
118+
i += 1;
119+
}
120+
}
121+
ret
122+
};
123+
let mut ret: Vec<F> = drain_filter(&mut self.candidates);
124+
ret.extend(drain_filter(&mut self.ready_for_finalize));
125+
ret
126+
}
104127
}
105128

106129
#[derive(Default)]

src/vm/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub use self::collection::Collection;
2727
pub use self::collection::GCThreadContext;
2828
pub use self::object_model::specs::*;
2929
pub use self::object_model::ObjectModel;
30+
pub use self::reference_glue::Finalizable;
3031
pub use self::reference_glue::ReferenceGlue;
3132
pub use self::scanning::EdgeVisitor;
3233
pub use self::scanning::Scanning;

src/vm/reference_glue.rs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,22 @@ use crate::util::ObjectReference;
33
use crate::util::VMWorkerThread;
44
use crate::vm::VMBinding;
55

6-
/// VM-specific methods for reference processing.
6+
/// VM-specific methods for reference processing, including weak references, and finalizers.
7+
/// We handle weak references and finalizers differently:
8+
/// * for weak references, we assume they are implemented as normal reference objects (also known as weak objects)
9+
/// with a referent that is actually weakly reachable. This trait provides a few methods to access
10+
/// the referent of such an reference object.
11+
/// * for finalizers, we provide a `Finalizable` trait, and require bindings to specify a type
12+
/// that implements `Finalizable`. When the binding registers or pops a finalizable object
13+
/// from MMTk, the specified type is used for the finalizable objects. For most languages,
14+
/// they can just use `ObjectReference` for the finalizable type, meaning that they are registering
15+
/// and popping a normal object reference as finalizable objects.
716
pub trait ReferenceGlue<VM: VMBinding> {
17+
/// The type of finalizable objects. This type is used when the binding registers and pops finalizable objects.
18+
type FinalizableType: Finalizable;
19+
20+
// TODO: Should we also move the following methods about weak references to a trait (similar to the `Finalizable` trait)?
21+
822
/// Weak and soft references always clear the referent
923
/// before enqueueing.
1024
///
@@ -16,20 +30,20 @@ pub trait ReferenceGlue<VM: VMBinding> {
1630
});
1731
}
1832

19-
/// Get the referent from a reference.
33+
/// Get the referent from a weak reference object.
2034
///
2135
/// Arguments:
2236
/// * `object`: The object reference.
2337
fn get_referent(object: ObjectReference) -> ObjectReference;
2438

25-
/// Set the referent in a reference.
39+
/// Set the referent in a weak reference object.
2640
///
2741
/// Arguments:
2842
/// * `reff`: The object reference for the reference.
2943
/// * `referent`: The referent object reference.
3044
fn set_referent(reff: ObjectReference, referent: ObjectReference);
3145

32-
/// For reference types, if the referent is cleared during GC, the reference
46+
/// For weak reference types, if the referent is cleared during GC, the reference
3347
/// will be added to a queue, and MMTk will call this method to inform
3448
/// the VM about the changes for those references. This method is used
3549
/// to implement Java's ReferenceQueue.
@@ -38,3 +52,39 @@ pub trait ReferenceGlue<VM: VMBinding> {
3852
/// MMTk will no longer keep these references alive once this method is returned.
3953
fn enqueue_references(references: &[ObjectReference], tls: VMWorkerThread);
4054
}
55+
56+
use crate::scheduler::gc_work::ProcessEdgesWork;
57+
58+
/// A finalizable object for MMTk. MMTk needs to know the actual object reference in the type,
59+
/// while a binding can use this type to store some runtime information about finalizable objects.
60+
/// For example, for bindings that allows multiple finalizer methods with one object, they can define
61+
/// the type as a tuple of `(object, finalize method)`, and register different finalizer methods to MMTk
62+
/// for the same object.
63+
/// The implementation should mark theird method implementations as inline for performance.
64+
pub trait Finalizable: std::fmt::Debug + Send {
65+
/// Load the object reference.
66+
fn get_reference(&self) -> ObjectReference;
67+
/// Store the object reference.
68+
fn set_reference(&mut self, object: ObjectReference);
69+
/// Keep the heap references in the finalizable object alive. For example, the reference itself needs to be traced. However,
70+
/// if the finalizable object includes other heap references, the implementation should trace them as well.
71+
/// Note that trace_object() may move objects so we need to write the new reference in case that it is moved.
72+
fn keep_alive<E: ProcessEdgesWork>(&mut self, trace: &mut E);
73+
}
74+
75+
/// This provides an implementation of `Finalizable` for `ObjectReference`. Most bindings
76+
/// should be able to use `ObjectReference` as `ReferenceGlue::FinalizableType`.
77+
impl Finalizable for ObjectReference {
78+
#[inline(always)]
79+
fn get_reference(&self) -> ObjectReference {
80+
*self
81+
}
82+
#[inline(always)]
83+
fn set_reference(&mut self, object: ObjectReference) {
84+
*self = object;
85+
}
86+
#[inline(always)]
87+
fn keep_alive<E: ProcessEdgesWork>(&mut self, trace: &mut E) {
88+
*self = trace.trace_object(*self);
89+
}
90+
}

vmbindings/dummyvm/src/reference_glue.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use crate::DummyVM;
66
pub struct VMReferenceGlue {}
77

88
impl ReferenceGlue<DummyVM> for VMReferenceGlue {
9+
type FinalizableType = ObjectReference;
10+
911
fn set_referent(_reference: ObjectReference, _referent: ObjectReference) {
1012
unimplemented!()
1113
}

0 commit comments

Comments
 (0)