externref: implement stack map-based garbage collection
For host VM code, we use plain reference counting, where cloning increments
the reference count, and dropping decrements it. We can avoid many of the
on-stack increment/decrement operations that typically plague the
performance of reference counting via Rust's ownership and borrowing system.
Moving a `VMExternRef` avoids mutating its reference count, and borrowing it
either avoids the reference count increment or delays it until if/when the
`VMExternRef` is cloned.
When passing a `VMExternRef` into compiled Wasm code, we don't want to do
reference count mutations for every compiled `local.{get,set}`, nor for
every function call. Therefore, we use a variation of **deferred reference
counting**, where we only mutate reference counts when storing
`VMExternRef`s somewhere that outlives the activation: into a global or
table. Simultaneously, we over-approximate the set of `VMExternRef`s that
are inside Wasm function activations. Periodically, we walk the stack at GC
safe points, and use stack map information to precisely identify the set of
`VMExternRef`s inside Wasm activations. Then we take the difference between
this precise set and our over-approximation, and decrement the reference
count for each of the `VMExternRef`s that are in our over-approximation but
not in the precise set. Finally, the over-approximation is replaced with the
precise set.
The `VMExternRefActivationsTable` implements the over-approximized set of
`VMExternRef`s referenced by Wasm activations. Calling a Wasm function and
passing it a `VMExternRef` moves the `VMExternRef` into the table, and the
compiled Wasm function logically "borrows" the `VMExternRef` from the
table. Similarly, `global.get` and `table.get` operations clone the gotten
`VMExternRef` into the `VMExternRefActivationsTable` and then "borrow" the
reference out of the table.
When a `VMExternRef` is returned to host code from a Wasm function, the host
increments the reference count (because the reference is logically
"borrowed" from the `VMExternRefActivationsTable` and the reference count
from the table will be dropped at the next GC).
For more general information on deferred reference counting, see *An
Examination of Deferred Reference Counting and Cycle Detection* by Quinane:
https://openresearch-repository.anu.edu.au/bitstream/1885/42030/2/hon-thesis.pdf
cc #929
Fixes #1804
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
//
|
||||
// struct VMContext {
|
||||
// interrupts: *const VMInterrupts,
|
||||
// externref_activations_table: *mut VMExternRefActivationsTable,
|
||||
// stack_map_registry: *mut StackMapRegistry,
|
||||
// signature_ids: [VMSharedSignatureIndex; module.num_signature_ids],
|
||||
// imported_functions: [VMFunctionImport; module.num_imported_functions],
|
||||
// imported_tables: [VMTableImport; module.num_imported_tables],
|
||||
@@ -286,9 +288,23 @@ impl VMOffsets {
|
||||
0
|
||||
}
|
||||
|
||||
/// The offset of the `VMExternRefActivationsTable` member.
|
||||
pub fn vmctx_externref_activations_table(&self) -> u32 {
|
||||
self.vmctx_interrupts()
|
||||
.checked_add(u32::from(self.pointer_size))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// The offset of the `*mut StackMapRegistry` member.
|
||||
pub fn vmctx_stack_map_registry(&self) -> u32 {
|
||||
self.vmctx_externref_activations_table()
|
||||
.checked_add(u32::from(self.pointer_size))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// The offset of the `signature_ids` array.
|
||||
pub fn vmctx_signature_ids_begin(&self) -> u32 {
|
||||
self.vmctx_interrupts()
|
||||
self.vmctx_stack_map_registry()
|
||||
.checked_add(u32::from(self.pointer_size))
|
||||
.unwrap()
|
||||
}
|
||||
@@ -591,6 +607,19 @@ impl VMOffsets {
|
||||
}
|
||||
}
|
||||
|
||||
/// Offsets for `VMExternRefActivationsTable`.
|
||||
impl VMOffsets {
|
||||
/// Return the offset for `VMExternRefActivationsTable::next`.
|
||||
pub fn vm_extern_ref_activation_table_next(&self) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
/// Return the offset for `VMExternRefActivationsTable::end`.
|
||||
pub fn vm_extern_ref_activation_table_end(&self) -> u32 {
|
||||
self.pointer_size.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Target specific type for shared signature index.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TargetSharedSignatureIndex(u32);
|
||||
|
||||
Reference in New Issue
Block a user