Conversation
One is EdgeTracer and the other is ProcessSlotsWork.
Most work packets that used to depend on ProcessEdgesWork now depend on EdgeTracer.
Yes. ProcessEdgesWork must die!
We now use DefaultProcessSlots for all of them.
We use DefaultProcessSlots for it.
|
The refactoring looks clean. The following are some observations after quickly reading through the changes. Some are just observations and no action is needed for this PR.
|
| } | ||
|
|
||
| fn is_concurrent() -> bool { | ||
| true |
There was a problem hiding this comment.
This type is also used in STW, right?
There was a problem hiding this comment.
No. src/plan/concurrent/immix/gc_work.rs defines two GCWorkContext implementations.
ConcurrentImmixSTWGCWorkContextwhich usesPlanTraceObjectConcurrentImmixGCWorkContext<T: TracePolicy>which uses an externally suppliedTracePolicyimplementation.
ConcurrentImmix::schedule_concurrent_marking_{initial,final}_pause create instances of StopMutators::<ConcurrentImmixGCWorkContext<ConcurrentRootTracePolicy<VM, Self, TRACE_KIND_FAST>>>. That selects ConcurrentRootTracePolicy.
This means ConcurrentRootTracePolicy is only used in concurrent GC, but not STW GC.
There was a problem hiding this comment.
Kunshan and I talked about this. What I meant is that this type is used in both the actual concurrent marking, and the concurrent pauses.
The only use of this method right now is when we decide whether to push work with notifying workers, or not notifying workers. If we push work to the current pause, we should notify workers so they can pick up the work quick. If we push work to the concurrent stage, we don't need to notify workers. But I don't feel we can tell the difference from this method.
| let work = | ||
| O2OTP::from_mmtk(mmtk).create_scan_work(root_objects_to_scan, mmtk, self.bucket); | ||
| if O2OTP::is_concurrent() { | ||
| worker.scheduler().work_buckets[self.bucket].add_no_notify(work); |
There was a problem hiding this comment.
I didn't find where this add_no_notify was from. Did the old code just use the normal add?
| for slot in self.slots.iter() { | ||
| if let Some(object) = slot.load() { | ||
| let new_object = self.policy.trace_object(worker, object, &mut queue); | ||
| if T::may_move_objects() && new_object != object { |
There was a problem hiding this comment.
Hopefully the compiler can inline may_move_objects. It was a constant OVERWRITE_REFERENCE.
There was a problem hiding this comment.
We can change it to a constant just to be safe.
Alternative name of
|
And remove TracePolicy::is_concurrent
Now the DefaultProcessSlots and DefaultScanObjects work packets create each other with cloned TracePolicy. ConcurrentImmix uses a completely different RootsWorkFactory to spawn all root-processing work packets.
Note that Concurrent GC still breaks in sanity GC like before.
TODO:
ObjectsClosurein favor for lambda expression|enqueued_object| { ... }.ScanObjectsandPlanScanObjects. Remove theScanObjectsWorktrait.TracePolicycustomize the work packets for processing root slots and root nodes.TracePolicy::is_concurrent()method because it is ambiguous. We should letConcurrentMarkingTracePolicyselect a different work packet than the defaultProcessRootNodes.Traceinstead ofTracePolicy."malloc_ms"Skipped due to existing bug. See malloc MarkSweep crashes during sweeping #1479Currently, many parts of mmtk-core rely on the
ProcessEdgesWorktrait merely for the capability of tracing objects, while theProcessEdgesWorkwas historically designed mainly for processing slots. This has been one major problem that makes the mmtk-core code base hard to understand and hard to maintain. We refactor the code to eliminate such unnecessary dependency.Adding the
TracetraitWe introduce the
Tracetrait.Each implementation provides essential functionalities of a trace, including:
trace_object: This is the most important function.post_scan_object: Used by certain spaces such asImmixSpacemay_move_objects: A hint for eliding store-back when tracing a slot.After this change, there will be the following implementations of
Trace. Different implementations mostly differ in the way how they dispatchtrace_object, but some also customize other methods:SFTTrace: The trace policy that dispatchestrace_objectcalls via the SFT.PlanTrace: The trace policy that dispatchestrace_objectvia thePlanTraceObject::trace_objectmethod of a plan.UnsupportedTrace: A dummy implementation for unsupported cases, such as "tracing pinning roots" in plans that don't support pinning.GenNurseryTrace: The trace policy that dispatchestrace_objecttoGenerationalPlanExt::trace_object_nursery.ConcurrentMarkingTrace: The trace policy for concurrent marking. It dispatchestrace_policylikePlanTrace, but creates different work packets to support concurrent marking.Some trace policies, such as
PlanTrace,GenNurseryTraceandConcurrentMarkingTrace, uses theconst KIND: TraceKindgeneric parameter to support different behaviors in different traces.GCWorkContextnow lets traces selectTraceinstead ofProcessEdgesWorkto customize the behavior of default and pinning traces.Removing the
ProcessEdgesWorktraitWe removed the
ProcessEdgesWorktrait (fixes #599) and theProcessEdgesBasestruct (fixes #1040).The
SFTProcessEdges,PlanProcessEdgesandGenNurseryProcessEdgeswork packets previously processed slots in different ways. They are replaced with one unifiedTracingProcessSlots<T: Trace>work packet that encapsulates an instance ofpolicy: Tand callspolicy.trace_objectto trace objects.Similarly, we unified
ScanObjectsandPlanScanObjectstoTracingProcessNodes. Also note that theScanObjectsWorkwork packets not only scan objects, but also do node-enqueue tracing. We name itTracingProcessNodesto be consistent.We still keep
trait ObjectTracerandtrait ObjectTracerContextas part of the VM binding API, allowing VMs to implementScanning::process_weak_refswithout changes. But internally, the oldProcessEdgesWorkTracerContextis replaced withTracingTracerContext<T: Trace>, andProcessEdgesWorkTraceris replaced withTracingObjectTracer<T: Trace>. They implements the mechanism of expanding the transitive closure by tracing.Existing work packets that depended on
E: ProcessEdgesWorkfor the sole purpose of expanding the transitive closure (which includes most types and methods related to weak reference and finalization processing) are now parameterized with<T: Trace>. Such work packets can useTto instantiateTracingTracerContext<T: Trace>.The
Finalizable::keep_alivemethod used to have a type parameter<E: ProcessEdgesWork>. It is now parameterized with<OT: ObjectTracer>.Fixing ConcurrentImmix work packets
The
concurrent_marking_work::ProcessRootSlotswork packet is removed in favor for theConcurrentMarkingRootsWorkFactory. By allowingGCWorkContextto select aRootsWorkFactoryimplementation, concurrent marking can createConcurrentTraceObjectswork packets directly from roots (including both root slots and root nodes) and put the work packet in theConcurrentbucket. This fixes the issued mentioned in #1455 and makes the worked around in #1454 unnecessary.By not relying on the
ProcessEdgesWorktrait, we are able to bypass theprocess_slots,process_slotandtrace_objectmethods inProcessEdgesWorkwhich are not designed for node-enqueuing tracing, and implement a traditional depth-first searching algorithm using a simple loop and a deque. This greatly simplified the implementation of theConcurrentTraceObjectswork packet.Rewriting the sanity checker
With the
Tracetrait extracted, it is trivial to rewrite the tracing loop in a two-level tracing/scanning loop usingTrace::trace_object. However, since every invocation oftrace_objectrequires a mutex lock on theSanityCheckeritself, and the sanity checker does not depend on any plan or space, we completely removed its dependency onTraceor anyTracingProcess{Slots,Nodes,PinningRoots}work packets. Instead, we implemented the whole sanity tracing loop in a singleSanityClosurework packet.The sanity checker now collects roots in the
RootsWorkFactoryimplementations instead ofTracingProcess{Slots,Nodes,PinningRoots}work packets.The issue #1469 is no longer reproducible after this change.
Minor changes
Renaming work packets
Some work packets are renamed to reflect the fact that they are all related to tracing GC and more accurately reflect their true natures.
ProcessEdgesWorkand{SFT,Plan}ProcessSlots->TracingProcessSlotsScanObjectsWorkand{,Plan}ScanObjects->TracingProcessNodesProcessRootNodes->TracingProcessPinningRootsProcessEdgesWorkObjectTracerContext->TracingTracerContextProcessEdgesWorkObjectTracer->TracingObjectTracerClosure implements
ObjectTracerFnMut(ObjectReference)now implementsObjectTracer. This allows a closure to be used intrace_object:OptionObjectQueueand SFTOption<ObjectReference>now implements a one-elementObjectQueue, and it is aliased asOptionObjectQueue. This allows decouplingSFT::sft_trace_objectfrom the concreteVectorObjectQueuetype by saving the enqueued object to a temporary variable ofOption<ObjectReference>type before forwarding it to a generic<Q: ObjectQueue>instance.Extracting
scanning_helperWe introduced the
scanning_helpermodule. It contains functions that scan objects usingscan_objectorscan_object_and_trace_edgesdepending on the VM binding's preference (VM::VMScanning::support_slot_enqueuing). It is intended to help writing simple textbook-style tracing loops that scan objects and trace objects all in one function.Lifetime in
ObjectTracerContextThe method
ObjectTracerContext::with_tracernow takes a lifetime parameter<'w>which is the lifetime during which the&'w mut GCWorkerreference can be used. However, this doesn't break existing VM binding code. This fixes the last work item in #921Things not done in this PR
As we mentioned above, we revealed that some types are closely related to tracing GC, and in fact they take up half of the source code lines in
src/scheduler/gc_work.rs. We should move them to the Rust module for tracing GC, i.e.mmtk::plan::tracingor its sub-module. However, to make this PR easy to review, we let the code stay at the locations of their older counterparts (i.e.TracingProcessNodesnear the oldScanObjectsWork). We shall make the change in a future PR and fix #1279Extracting the
Tracetrait allows theDefaultProcessSlots::gc_workto be implemented using a very succinct loop. It is attempting to replace the double enqueuing strategy (Vec<Slot>->Vec<ObjectReference>->Vec<Slot>) with a traditional loop that visits slots and scans objects together. But we are not doing it now. By not making significant algorithmic changes to production plans, we expect this PR to have similar performance as before. We can leave that refactoring to the future.@caizixian also proposed a change to make
TraceKinda trait instead of an alias ofu8. We do not apply that change in this PR. Note that there is an alternative way to refactorTraceKind. Instead of makingTraceKinda trait, we removeTraceKind, but make oneTraceimplementations for each existingTraceKind. We need to discuss this further to decide which refactoring route to take.Acknowledgement and Related work
@caizixian independently made an attempt to do similar refactorings. It uses the trait name
TracePolicy.Fixes: #599
Fixes: #921
Fixes: #1040
Fixes: #1455
Fixes: #1469