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:
PrepareMutator
ReleaseMutator
ScanStackRoot
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:
PrepareMutator
and its friends contain&mut mutator
.- It requires
Mutator
to beSend
- Currently,
MutatorContext
is declared aspub trait MutatorContext<VM: VMBinding>: Send + 'static
- It requires
Mutator
contains aBox<dyn Barrier>
which can contain aObjectRememberingBarrier
.- It will require
Barrier
to implementSend
. - Currently,
Barrier
is declared aspub trait Barrier: 'static + Send
- It will require
ObjectRememberingBarrier
contains a&MMTK
.- This requiers
&MMTK
to implementSend
, which in turn requiresMMTK
to implementSync
. (In Rust,T
implementsSync
if and only if&T
implementsSend
)
- This requiers
MMTK
contains anArc<GCWorkScheduler>
GCWorkScheduler
contains manyWorkBucket
.WorkBucket
contains manyPrioritizedWork
.PrioritizedWork
contains aBox<dyn GCWork<VM>>
. Because it isdyn
, it can be anyGCWork
, includingPrepareMutator
and others.- Because
MMTK
implementsSync
,GCWork
needs to implementSync
. This contradicts with our design thatGCWork
does 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
.
MMTK
needs to implementSync
.GCWorkScheduler
needs to implementSync
.GCWorkerShared
needs 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
?
Mutator
should 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, becauseMutator
has a reference toPlan
. A barrier is "what to do when reading/writing a reference field". It is the responsibility of thePlan
which 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.