Files
wasmtime/crates/environ/src/compilation.rs
Nick Fitzgerald f30ce1fe97 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
2020-06-15 09:39:37 -07:00

188 lines
5.9 KiB
Rust

//! A `Compilation` contains the compiled function bodies for a WebAssembly
//! module.
use crate::cache::ModuleCacheDataTupleType;
use crate::CacheConfig;
use crate::ModuleTranslation;
use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
use serde::{Deserialize, Serialize};
use std::ops::Range;
use thiserror::Error;
/// Compiled function: machine code body, jump table offsets, and unwind information.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct CompiledFunction {
/// The function body.
pub body: Vec<u8>,
/// The jump tables offsets (in the body).
pub jt_offsets: ir::JumpTableOffsets,
/// The unwind information.
pub unwind_info: Option<UnwindInfo>,
}
type Functions = PrimaryMap<DefinedFuncIndex, CompiledFunction>;
/// The result of compiling a WebAssembly module's functions.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub struct Compilation {
/// Compiled machine code for the function bodies.
functions: Functions,
}
impl Compilation {
/// Creates a compilation artifact from a contiguous function buffer and a set of ranges
pub fn new(functions: Functions) -> Self {
Self { functions }
}
/// Allocates the compilation result with the given function bodies.
pub fn from_buffer(
buffer: Vec<u8>,
functions: impl IntoIterator<Item = (Range<usize>, ir::JumpTableOffsets)>,
) -> Self {
Self::new(
functions
.into_iter()
.map(|(body_range, jt_offsets)| CompiledFunction {
body: buffer[body_range].to_vec(),
jt_offsets,
unwind_info: None, // not implemented for lightbeam currently
})
.collect(),
)
}
/// Gets the bytes of a single function
pub fn get(&self, func: DefinedFuncIndex) -> &CompiledFunction {
&self.functions[func]
}
/// Gets the number of functions defined.
pub fn len(&self) -> usize {
self.functions.len()
}
/// Returns whether there are no functions defined.
pub fn is_empty(&self) -> bool {
self.functions.is_empty()
}
/// Gets functions jump table offsets.
pub fn get_jt_offsets(&self) -> PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets> {
self.functions
.iter()
.map(|(_, func)| func.jt_offsets.clone())
.collect::<PrimaryMap<DefinedFuncIndex, _>>()
}
}
impl<'a> IntoIterator for &'a Compilation {
type IntoIter = Iter<'a>;
type Item = <Self::IntoIter as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
Iter {
iterator: self.functions.iter(),
}
}
}
pub struct Iter<'a> {
iterator: <&'a Functions as IntoIterator>::IntoIter,
}
impl<'a> Iterator for Iter<'a> {
type Item = &'a CompiledFunction;
fn next(&mut self) -> Option<Self::Item> {
self.iterator.next().map(|(_, b)| b)
}
}
/// A record of a relocation to perform.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Relocation {
/// The relocation code.
pub reloc: binemit::Reloc,
/// Relocation target.
pub reloc_target: RelocationTarget,
/// The offset where to apply the relocation.
pub offset: binemit::CodeOffset,
/// The addend to add to the relocation value.
pub addend: binemit::Addend,
}
/// Destination function. Can be either user function or some special one, like `memory.grow`.
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub enum RelocationTarget {
/// The user function index.
UserFunc(FuncIndex),
/// A compiler-generated libcall.
LibCall(ir::LibCall),
/// Jump table index.
JumpTable(FuncIndex, ir::JumpTable),
}
/// Relocations to apply to function bodies.
pub type Relocations = PrimaryMap<DefinedFuncIndex, Vec<Relocation>>;
/// Information about trap.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct TrapInformation {
/// The offset of the trapping instruction in native code. It is relative to the beginning of the function.
pub code_offset: binemit::CodeOffset,
/// Location of trapping instruction in WebAssembly binary module.
pub source_loc: ir::SourceLoc,
/// Code of the trap.
pub trap_code: ir::TrapCode,
}
/// 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 {
/// A wasm translation error occured.
#[error("WebAssembly translation error")]
Wasm(#[from] WasmError),
/// A compilation error occured.
#[error("Compilation error: {0}")]
Codegen(String),
/// A compilation error occured.
#[error("Debug info is not supported with this configuration")]
DebugInfoNotSupported,
}
/// An implementation of a compiler from parsed WebAssembly module to native code.
pub trait Compiler {
/// Compile a parsed module with the given `TargetIsa`.
fn compile_module(
translation: &ModuleTranslation,
isa: &dyn isa::TargetIsa,
cache_config: &CacheConfig,
) -> Result<ModuleCacheDataTupleType, CompileError>;
}