-
Notifications
You must be signed in to change notification settings - Fork 73
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
Non-null slots (Edge) that don't contain object references, either. #1031
Labels
P-normal
Priority: Normal.
Comments
The old issue #626 proposed a function |
github-merge-queue bot
pushed a commit
that referenced
this issue
Dec 20, 2023
This PR does two things: 1. `ProcessEdgesWork::process_edge` will skip slots that are not holding references (i.e. if `Edge::load()` returns `ObjectReference::NULL`). 2. `ProcessEdgesWork::process_edge` will skip the `Edge::store()` if the object is not moved. Doing (1) removes unnecessary invocations of `trace_object()` as well as the subsequent `Edge::store()`. It also allows slots to hold non-reference tagged values. In that case, the VM binding can return `ObjectReference::NULL` in `Edge::load()` so that mmtk-core will simply skip the slot, fixing #1031 Doing (2) removes unnecessary `Edge::store()` operations in the case where the objects are not moved during `trace_object`. It reduces the STW time in most cases, fixing #574 Fixes: #1031 Fixes: #574
k-sareen
pushed a commit
to k-sareen/mmtk-core
that referenced
this issue
Apr 11, 2024
This PR does two things: 1. `ProcessEdgesWork::process_edge` will skip slots that are not holding references (i.e. if `Edge::load()` returns `ObjectReference::NULL`). 2. `ProcessEdgesWork::process_edge` will skip the `Edge::store()` if the object is not moved. Doing (1) removes unnecessary invocations of `trace_object()` as well as the subsequent `Edge::store()`. It also allows slots to hold non-reference tagged values. In that case, the VM binding can return `ObjectReference::NULL` in `Edge::load()` so that mmtk-core will simply skip the slot, fixing mmtk#1031 Doing (2) removes unnecessary `Edge::store()` operations in the case where the objects are not moved during `trace_object`. It reduces the STW time in most cases, fixing mmtk#574 Fixes: mmtk#1031 Fixes: mmtk#574
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TL;DR: If a slot can hold things other than valid references and 0 (usually represent null), the current
ProcessEdgesWork
implementations cannot support it. But it is necessary for VMs that use tagged reference, such as CRuby. To solve the problem, we need toProcessEdgesWork::process_edge
return immediately if a slot (Edge
) reports it holds null, orEdge::update(updater: FnOnce(ObjectReference) -> Option<ObjectReference>)
so that the VM binding implement it and decide whether to calltrace_object
depending on whether the slot actually holds an object reference.ObjectReference::NULL
because there is no uniform way to represent null references. Enforce thatObjectReference
is always valid, and useOption<ObjectReference>
for nullable object reference.Problem: tagged references
In Ruby, a slot holds a tagged reference. It can store small integers, special values (such as
true
,false
andnil
), etc. in addition to valid object references. And, in fact,nil
is not represented as numerical 0. It is represented as 4, andfalse
is represented as 0. Other values (such as small integers) have bit tags such that the last three bits are not all 0.OK with object-enqueuing
Previously, the Ruby VM only uses
Scanning::scan_object_and_trace_edges
, and bypassedEdge
completely. Because the loading, tracing and storing is totally implemented by the binding, there is no problem handling slots that hold non-reference values. The following code snippet is an excerpt from my work-in-progress mmtk-ruby binding that traces an array in a loop.Not working with slot-enqueuing
Recently I am working on supporting slices (basically an array of slots), and a slice can be iterated to get each slot (as
Edge
). Then I am facing a problem.When scanning an object, we enqueue slots (
Edge
) or slices (MemorySlice
). The point is, we don't load from the slots or slices when scanning.Binding code:
The MMTk core will simply iterate through the slice (
MemorySlice
) and get individual slots (Edge
). The processing of each slot is the same as the status quo.ProcessEdgesWork
simply does load-trace-store for eachEdge
.Note that
process_edge
callsself.trace_object(object)
immediately afterslot.load()
, without checking whetherobject
is null or not.trace_object
methods ofPlanProcessEdges
and each concrete space check ifobject
is null or not. But if it is null, those implementations simply return the original object reference. Also note that in MMTk core, anObjectReference
is defined to be null if its value is 0.process_edge
callsslot.store()
regardless of whether it called thetrace_object
of any concrete space or not. It is a property of the concrete implementation ofProcessEdgesWork
. In other words, it blindly writes to the slot regardless of whetherobject
is null or not.This works for VMs where a slot either holds a valid object reference or
null
(represented as 0). OpenJDK is one such VM, and it works without problem.But for Ruby, it is a problem. In Ruby, a slot that holds 0 represents
false
, andnil
is represented as 4. Other non-reference values have non-zero values, too. So it is impossible for theprocess_edges
function shown above to distinguish between slots that hold object references and slots that don't.The binding can tell mmtk-core whether a slot holds an object reference by returning 0 from
Edge::load
.By doing this, mmtk-core will see either a valid object reference or a null pointer represented as 0, but not any other tagged non-reference values. However, this is not enough, because
ProcessEdgesWork::process_edge
will unconditionally store to the slot. If the slot holds any non-reference value, theslot.store(new_object)
invocation will overwrite the slot with 0. In Ruby, this bug will manifest as some slots mysteriously becomefalse
while it previously holds other values, such as small integers.Solutions
Simple workaround
One simple workaround is letting
ProcessEdgesWork::process_edge
ignore null references. That is, if the reference is null, don't callstore
, or preferrably don't even calltrace_object
at all.And we should extend the contract of
Edge::load()
so that the VM binding is allowed to returnObjectReference::NULL
when the slot actually holds a meaningful non-reference value, such as a tagged small integer.Add an
Edge::update
methodAnother solution is adding a method
Edge::update
. The semantics is updating the slot with a callback. If the slot doesn't hold a reference,Edge::update
(implemented by the binding) won't calltrace_object
(implemented by mmtk-core) in the first place. With this method available,ProcessEdgesWork::process_edge
will callEdge::update
instead of callingEdge::load
andEdge::store
, separately. MMTk-core can provide a default implementation ofEdge::update
that is based onload
andstore
, so that existing bindings don't need to be changed.And
ProcessEdgesWork::process_edge
will call the updater instead.For Ruby, it will work as well as ignoring
ObjectReference::NULL
returned fromslot.load()
. For other VMs where slots hold object references together with a tag, theupdate
method will have an advantage. It can cache the tag obtained from the load, and re-apply the tag before storing the updated object reference into the slot. With separated load and store calls, however, the store operation will need to load from the slot again to retrieve the tag, because MMTk-core assumesObjectReference
is an address to somewhere in the object, and is not aware of tags.Removing
ObjectReference::NULL
One aggressive refactoring we may do is removing
ObjectReference::NULL
, and require allObjectReference
in mmtk-core to be valid (i.e. not null). Reasons are:Maybe
typeNone
is a proper objectnil
is 4,false
is 0VALUE
type (alias ofunsigned long
) can hold object reference, small integer,true
,false
,nil
, symbols, etc.From MMTk core's point of view, a slot either holds an object reference or not. If it does not, MMTk core should simply skip it.
In Rust, the type to represent the absence of something is
Option<T>
. We should useOption<ObjectReference>
in places where there may or may not be an object reference, such as the return value ofEdge::load()
, and the result ofObjectReference::get_forwarded_object
(which already returnsOption<ObjectReference>
).We can further require
ObjectReference
to be non-null, which is reasonable because no valid object can be allocated at zero address or any very low address. ThenObjectReference
can be backed bystd::num::NonZeroUsize
instead ofusize
, so thatOption<ObjectReference>
will have the same layout asOption<NonZeroUsize>
which has the same size asusize
. See: https://doc.rust-lang.org/std/num/struct.NonZeroUsize.html and https://doc.rust-lang.org/std/option/index.html#representationThen no space needs to check for NULL reference in any of the
trace_object
methods because it is guaranteed to be non-null in the first place. Actually they shouldn't check fornull
even for now, because the dispatcher (SFT or the if-else chain derived fromPlanTraceObject
) will not dispatchnull
to any spaces.There is only one case where references need to be cleared during GC. It is when processing weak references and finalizers. But those clearing operations should be VM-specific. Currently it is implemented by
ReferenceGlue::clear_referent
which is VM-specific, anyway.Since this is not a trivial workaround, we need to discuss though this in the group as a design question.
The text was updated successfully, but these errors were encountered: