Open wks opened 1 year ago
The LXR branch added several call sites of Edge::load()
and Edge::store()
. They are for updating slots, too, and can be adapted to the update
method, too.
There is one use case in the LXR branch that used compare_exchange
to update the slot.
fn process_remset_edge(&mut self, slot: EdgeOf<Self>, i: usize) {
let object = slot.load();
if object != self.refs[i] {
return;
}
let new_object = self.trace_object(object);
if Self::OVERWRITE_REFERENCE && new_object != object && !new_object.is_null() {
if slot.to_address().is_mapped() {
debug_assert!(self.remset_recorded_edges);
// Don't do the store if the original is already overwritten
let _ =
slot.compare_exchange(object, new_object, Ordering::SeqCst, Ordering::SeqCst);
} else {
slot.store(new_object);
}
}
super::record_edge_for_validation(slot, new_object);
}
It indicates that another thread may be mutating the edge at the same time. That may indicate that we need an atomic variant of the update
method, with the option to skip the update if another thread updated the same slot concurrently.
Note: Ruby currently doesn't require this change in order to work with slot (Edge) enqueuing. But V8 will need this change to efficiently forward references while preserving tags.
Proposal
I propose a new method for the
Edge
trait:Edge::update
.The semantics of
Edge::update
is:updater
.Some(new_object)
, update the slot so that it holds a reference tonew_object
.None
, do nothing.The
updater
is usually implemented byProcessEdgesWork::process_edge
to calltrace_object
and forward the slot.Rationale
Supporting tagged union of references and values
https://github.com/mmtk/mmtk-core/issues/626 described the need to support slots that hold non-ref values in addition to null (such as tagged values including small integers). By letting
Edge::update
decide whether to call theupdater
, the VM binding will have a chance to decode the tagged pointer, and choose not to call theupdater
(which callstrace_object
) if the slot holds a small integer or special non-reference values such astrue
,false
,nil
, etc.Supporting object references with tags.
Ruby never store object references together with tags. If a slot holds an object reference, its last three bits are zero, making the whole word a valid object reference.
If a VM stores object reference together with a tag, then the VM needs to preserve the tag while updating the reference.
Edge::update
allows the VM binding to preserve the tag during the call.If the
Edge
trait only has theload
andstore
, it will be sub-optimal. Theload()
method can remove the tag and give mmtk-core only the object reference. But thestore()
method will have to load from the slot again to retrieve the tag.Supporting slots with an offset
Similar to slots with a tag, the
update
method can re-apply the offset when storing. However, unlike references with tags, because the offset is usually known when scanning the object, it does not need to load from the slot again even if we are usingload()
andstore()
directly.When do we need it?
The
load()
andstore()
method is currently enough to support Ruby.V8 may have a problem because according to https://v8.dev/blog/pointer-compression, if a slot holds a reference, the lowest bit will be 1, and the second lowest bit will indicate whether the reference is strong or weak. Currently the
v8-support
branch ofmmtk/mmtk-core
is hacked so thatProcessEdgesWork::process_edge
remvoes the tag before callingtrace_object
. This makes themmtk-core
specific to v8. See: https://github.com/mmtk/mmtk-core/blob/dc62d10625e5fc2e65f61d1469fc9a659af7d0d7/src/scheduler/gc_work.rs#L459-L469