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:
@@ -144,6 +144,22 @@ pub struct TrapInformation {
|
||||
/// Information about traps associated with the functions where the traps are placed.
|
||||
pub type Traps = PrimaryMap<DefinedFuncIndex, Vec<TrapInformation>>;
|
||||
|
||||
/// The offset within a function of a GC safepoint, and its associated stack
|
||||
/// map.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct StackMapInformation {
|
||||
/// The offset of the GC safepoint within the function's native code. It is
|
||||
/// relative to the beginning of the function.
|
||||
pub code_offset: binemit::CodeOffset,
|
||||
|
||||
/// The stack map for identifying live GC refs at the GC safepoint.
|
||||
pub stack_map: binemit::Stackmap,
|
||||
}
|
||||
|
||||
/// Information about GC safepoints and their associated stack maps within each
|
||||
/// function.
|
||||
pub type StackMaps = PrimaryMap<DefinedFuncIndex, Vec<StackMapInformation>>;
|
||||
|
||||
/// An error while compiling WebAssembly to machine code.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CompileError {
|
||||
|
||||
Reference in New Issue
Block a user