TL;DR: The struct Mutator is too complicated, but VM bindings only care about one or two fields. Only expose those fields as #[repr(C)] and conceal others.
Problem
The whole struct Mutator is exposed to the VM binding as [repr(C)] because some of its fields need to be used by C code or JIT-compiled machine code to implement fast paths.
However, struct Mutator is too complicated. It has many Allocators organised as arrays, and may fields that are not consumable by C code (such as &dyn references).
Note: The Rust Reference discourages users from depending on the layout of &dyn. This section contains the following paragraph:
Note: Though you should not rely on this, all pointers to DSTs are currently twice the size of the size of usize and have the same alignment.
Bindings only need a few fields in the Mutator struct.
For implementing the bump-pointer allocation fast path, the binding only needs the address of the cursor and the limit fields.
For implementing object-remembering barrier fast paths, the binding only checks the unlogged bit. That only needs to access the metadata which is either on the side or in the header. It only needs the Mutator struct on the slow path.
However, due to the complexity of the Mutator struct, C has to duplicate the structure definition in C in order to access fields deep inside the structure of Mutator. See:
Split the allocator into two parts, one specifically for fast-path allocation, and the other for everything else. For ImmixAllocator, we define it as
struct ImmixAllocator {
tls: ...,
fast: *mut ImmixAllocatorFast,
// other fields go here
}
#[repr(C)]
struct ImmixAllocatorFast {
cursor: Address,
limit: Address,
}
Let the VM binding maintain its own fast-path data structure
MMTk core doesn't need to mark any fields of Mutator as #[repr(C)]. But the VM binding lets each thread store the cursor and the limit anywhere in its thread-local storage.
TL;DR: The
struct Mutator
is too complicated, but VM bindings only care about one or two fields. Only expose those fields as#[repr(C)]
and conceal others.Problem
The whole
struct Mutator
is exposed to the VM binding as[repr(C)]
because some of its fields need to be used by C code or JIT-compiled machine code to implement fast paths.However,
struct Mutator
is too complicated. It has many Allocators organised as arrays, and may fields that are not consumable by C code (such as&dyn
references).Note: The Rust Reference discourages users from depending on the layout of
&dyn
. This section contains the following paragraph:Bindings only need a few fields in the
Mutator
struct.cursor
and thelimit
fields.Mutator
struct on the slow path.However, due to the complexity of the
Mutator
struct, C has to duplicate the structure definition in C in order to access fields deep inside the structure of Mutator. See:And may still need to compute the offsets manually. (I wonder why the
offsetof
macro in the standard library doesn't work.) See:And careful developers assert the size of the structures are the same as what it was when the structs were copied to C. See:
Proposal
Instead of making the entire
Mutator
struct#[repr(C)]
, we only make#[repr(C)]
structs for important fields. For example,Then we can transform
Allocator
types so that they contain such structs.Then we can still get those
fast
fields in Rust:mutator.allocators.bump_allocator[2].fast
To get pointers to those
fast
fields in C, VM bindings can provide wrapper functions. (Note that thefast
fields arepub
now.)To compute the offset of the
fast
fields, the VM binding either(char*)fast - (char*)mutator
, orAlternatives
Splitting the allocator
Split the allocator into two parts, one specifically for fast-path allocation, and the other for everything else. For ImmixAllocator, we define it as
Let the VM binding maintain its own fast-path data structure
MMTk core doesn't need to mark any fields of
Mutator
as#[repr(C)]
. But the VM binding lets each thread store the cursor and the limit anywhere in its thread-local storage.But right before entering slow path, the rust-side of the binding synchronizes the cursor and the limit into the Allocator struct.