-
Notifications
You must be signed in to change notification settings - Fork 78
Description
Although Mutator is supposed to be a thread-local data structure of a mutator thread, some GCWork instances modifies the Mutator instances on behalf of the mutators. These include:
PrepareMutatorReleaseMutatorScanStackRoot
Remember that GCWork implements Send but not Sync. (It is Send because it can be distributed among workers; it is not Sync because a GCWork is supposed to be visible to only one thread at a time.)
Currently, those structs contain &mut mutator. PrepareMutator is a GCWork, and it needs to implements Send. Because it contains a &mut Mutator, it requires Mutator to implement Send, too. Similar is true for ReleaseMutator and ScanStackRoot.
p.s. Changing &mut Mutator to &Mutator will still require Mutator to implement Send, because PrepareMutator implements Send.
Can we make Mutator implement Send?
(Update: As we discussed later in #543 (comment), it probably should implement Mutator currently implements Send, but we probably don't want Mutator to implement Send, because it is supposed to be local to a mutator thread.Send after all. The Send trait may make sense during initialization.)
If Mutator implements Send (the status quo), there will be other consequences:
PrepareMutatorand its friends contain&mut mutator.- It requires
Mutatorto beSend - Currently,
MutatorContextis declared aspub trait MutatorContext<VM: VMBinding>: Send + 'static
- It requires
Mutatorcontains aBox<dyn Barrier>which can contain aObjectRememberingBarrier.- It will require
Barrierto implementSend. - Currently,
Barrieris declared aspub trait Barrier: 'static + Send
- It will require
ObjectRememberingBarriercontains a&MMTK.- This requiers
&MMTKto implementSend, which in turn requiresMMTKto implementSync. (In Rust,TimplementsSyncif and only if&TimplementsSend)
- This requiers
MMTKcontains anArc<GCWorkScheduler>GCWorkSchedulercontains manyWorkBucket.WorkBucketcontains manyPrioritizedWork.PrioritizedWorkcontains aBox<dyn GCWork<VM>>. Because it isdyn, it can be anyGCWork, includingPrepareMutatorand others.- Because
MMTKimplementsSync,GCWorkneeds to implementSync. This contradicts with our design thatGCWorkdoes not implementSync.
- Because
From a different point of view, having ObjectRememberingBarrier in Mutator closes the loop from one work to any other work. Any work that contains &mut mutator can potentially see any other GCWork instances. This allows one worker to peek into another worker's work. Remember that GCWork is not Sync. Not implementing Sync means it cannot be visible to two threads at the same time.
But why is it working so far?
In scheduler.rs, there is a line:
unsafe impl<VM: VMBinding> Sync for GCWorkScheduler<VM> {}That line broke the dependency chain shown above.
Solution?
Let's think about what should implement Sync. Shared data structures need to implement Sync.
MMTKneeds to implementSync.GCWorkSchedulerneeds to implementSync.GCWorkerSharedneeds to implementSync.
But we shouldn't need to write anything because the Rust compiler can figure out if MMTK satisfies the need of Sync.
And what shouldn't implement Sync?
Mutatorshould not implementSync, because it should be local to a mutator. But GC also needs to mutate it.Mutator::barrier: This should probably be notSync, because only the mutator thread ever execute it. GC never touches it. And to some degree, it shouldn't exist at all, becauseMutatorhas a reference toPlan. A barrier is "what to do when reading/writing a reference field". It is the responsibility of thePlanwhich describes the GC algorithm.Mutator::allocator: This probably belongs to the part shared between the mutator thread and GC threads.
Should we do the same refactoring as we did for GC workers, i.e. splitting Mutator into a part shared with GC threads, and the part local to the mutator thread? Mutator is #[repr(C)], and the structure is visible to the VM.