-
Notifications
You must be signed in to change notification settings - Fork 78
Generalise weak reference processing for other languages #700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2d4d55b
d69338d
5003220
eedeeea
24e5bab
da8a052
acb9bf5
86e64a1
792c361
abde77a
147d76b
ebb8b95
b79c041
91acf85
5aec01c
ba194ba
165b3fa
b6829d1
d41c004
bd18421
0104959
a8b4204
d09bb79
ec513bd
0e5fee1
381d5cd
4d6856f
3591280
8f62b5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,9 +108,9 @@ impl<C: GCWorkContext> Release<C> { | |
| impl<C: GCWorkContext + 'static> GCWork<C::VM> for Release<C> { | ||
| fn do_work(&mut self, worker: &mut GCWorker<C::VM>, mmtk: &'static MMTK<C::VM>) { | ||
| trace!("Release Global"); | ||
|
|
||
| self.plan.base().gc_trigger.policy.on_gc_release(mmtk); | ||
|
|
||
| <C::VM as VMBinding>::VMCollection::vm_release(); | ||
| // We assume this is the only running work packet that accesses plan at the point of execution | ||
| #[allow(clippy::cast_ref_to_mut)] | ||
| let plan_mut: &mut C::PlanType = unsafe { &mut *(self.plan as *const _ as *mut _) }; | ||
|
|
@@ -252,24 +252,190 @@ impl<VM: VMBinding> GCWork<VM> for EndOfGC { | |
|
|
||
| impl<VM: VMBinding> CoordinatorWork<VM> for EndOfGC {} | ||
|
|
||
| /// Delegate to the VM binding for reference processing. | ||
| /// This implements `ObjectTracer` by forwarding the `trace_object` calls to the wrapped | ||
| /// `ProcessEdgesWork` instance. | ||
| struct ProcessEdgesWorkTracer<E: ProcessEdgesWork> { | ||
| process_edges_work: E, | ||
| stage: WorkBucketStage, | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> ObjectTracer for ProcessEdgesWorkTracer<E> { | ||
| /// Forward the `trace_object` call to the underlying `ProcessEdgesWork`, | ||
| /// and flush as soon as the underlying buffer of `process_edges_work` is full. | ||
| /// | ||
| /// This function is inlined because `trace_object` is probably the hottest function in MMTk. | ||
| /// If this function is called in small closures, please profile the program and make sure the | ||
| /// closure is inlined, too. | ||
| #[inline(always)] | ||
| fn trace_object(&mut self, object: ObjectReference) -> ObjectReference { | ||
| let result = self.process_edges_work.trace_object(object); | ||
| self.flush_if_full(); | ||
| result | ||
| } | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> ProcessEdgesWorkTracer<E> { | ||
| #[inline(always)] | ||
| fn flush_if_full(&mut self) { | ||
| if self.process_edges_work.nodes.is_full() { | ||
| self.flush(); | ||
| } | ||
| } | ||
|
|
||
| pub fn flush_if_not_empty(&mut self) { | ||
| if !self.process_edges_work.nodes.is_empty() { | ||
| self.flush(); | ||
| } | ||
| } | ||
|
|
||
| #[cold] | ||
| fn flush(&mut self) { | ||
| let next_nodes = self.process_edges_work.pop_nodes(); | ||
| assert!(!next_nodes.is_empty()); | ||
| let work_packet = self.process_edges_work.create_scan_work(next_nodes, false); | ||
| let worker = self.process_edges_work.worker(); | ||
| worker.scheduler().work_buckets[self.stage].add(work_packet); | ||
| } | ||
| } | ||
|
|
||
| /// This type implements `ObjectTracerContext` by creating a temporary `ProcessEdgesWork` during | ||
| /// the call to `with_tracer`, making use of its `trace_object` method. It then creates work | ||
| /// packets using the methods of the `ProcessEdgesWork` and add the work packet into the given | ||
| /// `stage`. | ||
| struct ProcessEdgesWorkTracerContext<E: ProcessEdgesWork> { | ||
| stage: WorkBucketStage, | ||
| phantom_data: PhantomData<E>, | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> Clone for ProcessEdgesWorkTracerContext<E> { | ||
| fn clone(&self) -> Self { | ||
| Self { ..*self } | ||
| } | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> ObjectTracerContext<E::VM> for ProcessEdgesWorkTracerContext<E> { | ||
| type TracerType = ProcessEdgesWorkTracer<E>; | ||
|
|
||
| fn with_tracer<R, F>(&self, worker: &mut GCWorker<E::VM>, func: F) -> R | ||
| where | ||
| F: FnOnce(&mut Self::TracerType) -> R, | ||
| { | ||
| let mmtk = worker.mmtk; | ||
|
|
||
| // Prepare the underlying ProcessEdgesWork | ||
| let mut process_edges_work = E::new(vec![], false, mmtk); | ||
| // FIXME: This line allows us to omit the borrowing lifetime of worker. | ||
| // We should refactor ProcessEdgesWork so that it uses `worker` locally, not as a member. | ||
| process_edges_work.set_worker(worker); | ||
|
|
||
| // Cretae the tracer. | ||
| let mut tracer = ProcessEdgesWorkTracer { | ||
| process_edges_work, | ||
| stage: self.stage, | ||
| }; | ||
|
|
||
| // The caller can use the tracer here. | ||
| let result = func(&mut tracer); | ||
|
|
||
| // Flush the queued nodes. | ||
| tracer.flush_if_not_empty(); | ||
|
|
||
| result | ||
| } | ||
| } | ||
|
|
||
| /// Delegate to the VM binding for weak reference processing. | ||
| /// | ||
| /// Some VMs (e.g. v8) do not have a Java-like global weak reference storage, and the | ||
| /// processing of those weakrefs may be more complex. For such case, we delegate to the | ||
| /// VM binding to process weak references. | ||
| #[derive(Default)] | ||
| pub struct VMProcessWeakRefs<E: ProcessEdgesWork>(PhantomData<E>); | ||
| /// | ||
| /// NOTE: This will replace `{Soft,Weak,Phantom}RefProcessing` and `Finalization` in the future. | ||
| pub struct VMProcessWeakRefs<E: ProcessEdgesWork> { | ||
| phantom_data: PhantomData<E>, | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> VMProcessWeakRefs<E> { | ||
| pub fn new() -> Self { | ||
| Self(PhantomData) | ||
| Self { | ||
| phantom_data: PhantomData, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> GCWork<E::VM> for VMProcessWeakRefs<E> { | ||
| fn do_work(&mut self, worker: &mut GCWorker<E::VM>, _mmtk: &'static MMTK<E::VM>) { | ||
| trace!("ProcessWeakRefs"); | ||
| <E::VM as VMBinding>::VMCollection::process_weak_refs(worker); // TODO: Pass a factory/callback to decide what work packet to create. | ||
| trace!("VMProcessWeakRefs"); | ||
|
|
||
| let stage = WorkBucketStage::VMRefClosure; | ||
|
|
||
| let need_to_repeat = { | ||
| let tracer_factory = ProcessEdgesWorkTracerContext::<E> { | ||
| stage, | ||
| phantom_data: PhantomData, | ||
| }; | ||
| <E::VM as VMBinding>::VMScanning::process_weak_refs(worker, tracer_factory) | ||
| }; | ||
|
|
||
| if need_to_repeat { | ||
| // Schedule Self as the new sentinel so we'll call `process_weak_refs` again after the | ||
| // current transitive closure. | ||
| let new_self = Box::new(Self::new()); | ||
|
|
||
| worker.scheduler().work_buckets[stage].set_sentinel(new_self); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, this is used for performing weak-ref tracing in a loop. You can use the sentinel packet to start the transitive closure again and again until reaching a steady state. However, I think OpenJDK may also use the sentinel packet as a "state transition" to start a new phase for a different type of weak-ref processing. For example, OpenJDK does soft-ref processing first (which will start a round of closure), and then phantom processing later (and another round of closure). If the VM is able to set a different sentinel packet itself other than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To implement state transition, we can embed a state machine in the weak reference processor, and multiplex the operations for different states through the single It is possible to add a way to specify the next work packet to be scheduled as the "sentinel" packet. But that may be more complicated (as it has to expose details such as "sentinel") than simply returning |
||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Delegate to the VM binding for forwarding weak references. | ||
| /// | ||
| /// Some VMs (e.g. v8) do not have a Java-like global weak reference storage, and the | ||
| /// processing of those weakrefs may be more complex. For such case, we delegate to the | ||
| /// VM binding to process weak references. | ||
| /// | ||
| /// NOTE: This will replace `RefForwarding` and `ForwardFinalization` in the future. | ||
| pub struct VMForwardWeakRefs<E: ProcessEdgesWork> { | ||
| phantom_data: PhantomData<E>, | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> VMForwardWeakRefs<E> { | ||
| pub fn new() -> Self { | ||
| Self { | ||
| phantom_data: PhantomData, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<E: ProcessEdgesWork> GCWork<E::VM> for VMForwardWeakRefs<E> { | ||
| fn do_work(&mut self, worker: &mut GCWorker<E::VM>, _mmtk: &'static MMTK<E::VM>) { | ||
| trace!("VMForwardWeakRefs"); | ||
|
|
||
| let stage = WorkBucketStage::VMRefForwarding; | ||
|
|
||
| let tracer_factory = ProcessEdgesWorkTracerContext::<E> { | ||
| stage, | ||
| phantom_data: PhantomData, | ||
| }; | ||
| <E::VM as VMBinding>::VMScanning::forward_weak_refs(worker, tracer_factory) | ||
| } | ||
| } | ||
|
|
||
| /// This work packet calls `Collection::post_forwarding`. | ||
| /// | ||
| /// NOTE: This will replace `RefEnqueue` in the future. | ||
| /// | ||
| /// NOTE: Although this work packet runs in parallel with the `Release` work packet, it does not | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We cannot guarantee this. The binding may access the plan in their implementation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. While The timing of I think what we can do is telling the VM binding not to access the |
||
| /// access the `Plan` instance. | ||
| #[derive(Default)] | ||
| pub struct VMPostForwarding<VM: VMBinding> { | ||
| phantom_data: PhantomData<VM>, | ||
| } | ||
|
|
||
| impl<VM: VMBinding> GCWork<VM> for VMPostForwarding<VM> { | ||
| fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) { | ||
| trace!("VMPostForwarding start"); | ||
| <VM as VMBinding>::VMCollection::post_forwarding(worker.tls); | ||
| trace!("VMPostForwarding end"); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -678,38 +844,22 @@ pub trait ScanObjectsWork<VM: VMBinding>: GCWork<VM> + Sized { | |
|
|
||
| // If any object does not support edge-enqueuing, we process them now. | ||
| if !scan_later.is_empty() { | ||
| // We create an instance of E to use its `trace_object` method and its object queue. | ||
| let mut process_edges_work = Self::E::new(vec![], false, mmtk); | ||
| let mut closure = |object| process_edges_work.trace_object(object); | ||
|
|
||
| // Scan objects and trace their edges at the same time. | ||
| for object in scan_later.iter().copied() { | ||
| <VM as VMBinding>::VMScanning::scan_object_and_trace_edges( | ||
| tls, | ||
| object, | ||
| &mut closure, | ||
| ); | ||
| self.post_scan_object(object); | ||
| } | ||
|
|
||
| // Create work packets to scan adjacent objects. We skip ProcessEdgesWork and create | ||
| // object-scanning packets directly, because the edges are already traced. | ||
| if !process_edges_work.nodes.is_empty() { | ||
| let next_nodes = process_edges_work.nodes.take(); | ||
| let make_packet = |nodes| { | ||
| let work_packet = self.make_another(nodes); | ||
| memory_manager::add_work_packet(mmtk, WorkBucketStage::Closure, work_packet); | ||
| }; | ||
|
|
||
| // Divide the resulting nodes into appropriately sized packets. | ||
| if next_nodes.len() <= Self::E::CAPACITY { | ||
| make_packet(next_nodes); | ||
| } else { | ||
| for chunk in next_nodes.chunks(Self::E::CAPACITY) { | ||
| make_packet(chunk.into()); | ||
| } | ||
| let object_tracer_context = ProcessEdgesWorkTracerContext::<Self::E> { | ||
| stage: WorkBucketStage::Closure, | ||
| phantom_data: PhantomData, | ||
| }; | ||
|
|
||
| object_tracer_context.with_tracer(worker, |object_tracer| { | ||
| // Scan objects and trace their edges at the same time. | ||
| for object in scan_later.iter().copied() { | ||
| <VM as VMBinding>::VMScanning::scan_object_and_trace_edges( | ||
| tls, | ||
| object, | ||
| object_tracer, | ||
| ); | ||
| self.post_scan_object(object); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.