Description
TL;DR: Edge::store
is not suitable for write barrier. We need an alternative design.
Problem
Currently, the write barrier API and implementation assumes that the value to write into the slot is the object reference itself.
fn object_reference_write(
&mut self,
src: ObjectReference,
slot: VM::VMEdge,
target: ObjectReference,
) {
self.object_reference_write_pre(src, slot, target);
slot.store(target);
self.object_reference_write_post(src, slot, target);
}
However, this is not true for VMs where slots that hold object references also have tag bits.
In V8, for example, if an object reference is 0x000002001234abc0
, the actual value stored in the slot will be 0x000002001234abc1, with the lowest bit being 1 to indicate that this slot is holding a reference, not a small integer (SMI).
The Edge::store
method is intended for forwarding the slot in copying GC. When forwarding, the Edge::store
method should preserve the tag bits. Take V8 as an example. If the slot previously holds a strong reference, e.g. 0x000002001234abc1
, its tag bits will be 0x000002001234abc1 & 0b11
which is 0b01
. After forwarding the object, assume the object is moved to 0x000002005678def0
, the new value will be 0x000002005678def0 | (0x000002001234abc1 & 0b11)
, which is 0x000002005678def1
.
However, if the same algorithm is used for the slot.store
in the write barrier, it will be a problem. If the slot used to hold a small integer of value 0x20
(32 in decimal), the actual value in the slot will be 0x40
(0x20 << 1
). If we store an object reference 0x000002005678def0
to the slot, then it will attempt to store 0x000002005678def0 | (0x40 & 0b11)
which is still 0x000002005678def0
. Since the lowest bit is 0, V8 will think the slot holds a small integer 0x1002b3c6f78
, which is wrong.
Another example is Lua. A Lua slot is a C struct with a tag and a union.
/*
** Union of all Lua values
*/
typedef union Value {
struct GCObject *gc; /* collectable objects */
void *p; /* light userdata */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
/* not used, but may avoid warnings for uninitialized value */
lu_byte ub;
} Value;
/*
** Tagged Values. This is the basic representation of values in Lua:
** an actual value plus a tag with its type.
*/
#define TValuefields
typedef struct TValue {
Value value_;
lu_byte tt_
} TValue;
When forwarding a reference, the Edge::store
method only needs to update value_.p
; when executing a (full/subsuming) write barrier, it should update both value_.p
and tt_
.
The crux is, the store
operation for updating a slot for forwarding reference is not the same as the store
operation to mutate the object graph.
Solutions
One obvious way to solve the problem is calling object_reference_write_pre
and object_reference_write_post
separately, leaving the actual store operation to the VM.
Another way is leave part of the subsuming write barrier as a callback to a VM-supplied function so that the VM can customise store operation.
Any way, we need to mention in the documentation of Edge::store
that it is intended to update the slot for forwarding.