Refactor how signatures/trampolines are stored in Store

This commit refactors where trampolines and signature information is
stored within a `Store`, namely moving them from
`wasmtime_runtime::Instance` instead to `Store` itself. The goal here is
to remove an allocation inside of an `Instance` and make them a bit
cheaper to create. Additionally this should open up future possibilities
like not creating duplicate trampolines for signatures already in the
`Store` when using `Func::new`.
This commit is contained in:
Alex Crichton
2020-10-25 15:54:21 -07:00
parent de4af90af6
commit 3887881800
14 changed files with 159 additions and 181 deletions

View File

@@ -540,7 +540,10 @@ impl Func {
pub fn ty(&self) -> FuncType {
// Signatures should always be registered in the store's registry of
// shared signatures, so we should be able to unwrap safely here.
let wft = self.instance.store.lookup_signature(self.sig_index());
let signatures = self.instance.store.signatures().borrow();
let (wft, _, _) = signatures
.lookup_shared(self.sig_index())
.expect("signature should be registered");
// This is only called with `Export::Function`, and since it's coming
// from wasmtime_runtime itself we should support all the types coming
@@ -550,19 +553,19 @@ impl Func {
/// Returns the number of parameters that this function takes.
pub fn param_arity(&self) -> usize {
let sig = self
.instance
.store
.lookup_signature(unsafe { self.export.anyfunc.as_ref().type_index });
let signatures = self.instance.store.signatures().borrow();
let (sig, _, _) = signatures
.lookup_shared(self.sig_index())
.expect("signature should be registered");
sig.params.len()
}
/// Returns the number of results this function produces.
pub fn result_arity(&self) -> usize {
let sig = self
.instance
.store
.lookup_signature(unsafe { self.export.anyfunc.as_ref().type_index });
let signatures = self.instance.store.signatures().borrow();
let (sig, _, _) = signatures
.lookup_shared(self.sig_index())
.expect("signature should be registered");
sig.returns.len()
}
@@ -649,8 +652,12 @@ impl Func {
// on that module as well, so unwrap the result here since otherwise
// it's a bug in wasmtime.
let trampoline = instance
.trampoline(unsafe { export.anyfunc.as_ref().type_index })
.expect("failed to retrieve trampoline from module");
.store
.signatures()
.borrow()
.lookup_shared(unsafe { export.anyfunc.as_ref().type_index })
.expect("failed to retrieve trampoline from module")
.2;
Func {
instance,

View File

@@ -20,7 +20,7 @@ fn instantiate(
let instance = unsafe {
let instance = compiled_module.instantiate(
imports,
&mut store.signatures_mut(),
&store.lookup_shared_signature(compiled_module.module()),
config.memory_creator.as_ref().map(|a| a as _),
store.interrupts(),
host,
@@ -161,12 +161,8 @@ impl Instance {
bail!("cross-`Engine` instantiation is not currently supported");
}
let host_info = Box::new({
let frame_info_registration = module.register_frame_info();
store.register_jit_code(&module);
store.register_stack_maps(&module);
frame_info_registration
});
store.register_module(&module);
let host_info = Box::new(module.register_frame_info());
let handle = with_imports(store, module.compiled_module(), imports, |imports| {
instantiate(store, module.compiled_module(), imports, host_info)
@@ -295,7 +291,8 @@ fn with_imports<R>(
// functions registered with that type, so `func` is guaranteed
// to not match.
let ty = store
.signatures_mut()
.signatures()
.borrow()
.lookup(&m.signatures[m.functions[i]].0)
.ok_or_else(|| anyhow!("function types incompatible"))?;
if !func.matches_expected(ty) {

View File

@@ -242,6 +242,7 @@ mod linker;
mod module;
mod r#ref;
mod runtime;
mod sig_registry;
mod trampoline;
mod trap;
mod types;

View File

@@ -1,4 +1,5 @@
use crate::externals::MemoryCreator;
use crate::sig_registry::SignatureRegistry;
use crate::trampoline::{MemoryCreatorProxy, StoreInstanceHandle};
use crate::Module;
use anyhow::{bail, Result};
@@ -16,13 +17,12 @@ use wasmparser::WasmFeatures;
#[cfg(feature = "cache")]
use wasmtime_cache::CacheConfig;
use wasmtime_environ::settings::{self, Configurable, SetError};
use wasmtime_environ::{ir, isa, isa::TargetIsa, wasm, Tunables};
use wasmtime_environ::{isa, isa::TargetIsa, wasm, Tunables};
use wasmtime_jit::{native, CompilationStrategy, Compiler};
use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent};
use wasmtime_runtime::{
debug_builtins, InstanceHandle, RuntimeMemoryCreator, SignalHandler, SignatureRegistry,
StackMapRegistry, VMExternRef, VMExternRefActivationsTable, VMInterrupts,
VMSharedSignatureIndex,
debug_builtins, InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry,
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex,
};
// Runtime Environment
@@ -915,38 +915,21 @@ impl Store {
.map(|x| x as _)
}
pub(crate) fn lookup_signature(&self, sig_index: VMSharedSignatureIndex) -> wasm::WasmFuncType {
self.inner
.signatures
.borrow()
.lookup_wasm(sig_index)
.expect("failed to lookup signature")
pub(crate) fn signatures(&self) -> &RefCell<SignatureRegistry> {
&self.inner.signatures
}
pub(crate) fn lookup_wasm_and_native_signatures(
&self,
sig_index: VMSharedSignatureIndex,
) -> (wasm::WasmFuncType, ir::Signature) {
self.inner
.signatures
.borrow()
.lookup_wasm_and_native_signatures(sig_index)
.expect("failed to lookup signature")
}
pub(crate) fn register_signature(
&self,
wasm_sig: wasm::WasmFuncType,
native: ir::Signature,
) -> VMSharedSignatureIndex {
self.inner
.signatures
.borrow_mut()
.register(wasm_sig, native)
}
pub(crate) fn signatures_mut(&self) -> std::cell::RefMut<'_, SignatureRegistry> {
self.inner.signatures.borrow_mut()
pub(crate) fn lookup_shared_signature<'a>(
&'a self,
module: &'a wasmtime_environ::Module,
) -> impl Fn(wasm::SignatureIndex) -> VMSharedSignatureIndex + 'a {
move |index| {
let (wasm, _native) = &module.signatures[index];
self.signatures()
.borrow()
.lookup(wasm)
.expect("signature not previously registered")
}
}
/// Returns whether or not the given address falls within the JIT code
@@ -959,7 +942,32 @@ impl Store {
.any(|(start, end)| *start <= addr && addr < *end)
}
pub(crate) fn register_jit_code(&self, module: &Module) {
pub(crate) fn register_module(&self, module: &Module) {
// All modules register their JIT code in a store for two reasons
// currently:
//
// * First we only catch signals/traps if the program counter falls
// within the jit code of an instantiated wasm module. This ensures
// we don't catch accidental Rust/host segfaults.
//
// * Second when generating a backtrace we'll use this mapping to
// only generate wasm frames for instruction pointers that fall
// within jit code.
self.register_jit_code(module);
// We need to know about all the stack maps of all instantiated modules
// so when performing a GC we know about all wasm frames that we find
// on the stack.
self.register_stack_maps(module);
// Signatures are loaded into our `SignatureRegistry` here
// once-per-module (and once-per-signature). This allows us to create
// a `Func` wrapper for any function in the module, which requires that
// we know about the signature and trampoline for all instances.
self.register_signatures(module);
}
fn register_jit_code(&self, module: &Module) {
let mut ranges = module.compiled_module().jit_code_ranges();
// Checking of we already registered JIT code ranges by searching
// first range start.
@@ -977,7 +985,7 @@ impl Store {
}
}
pub(crate) fn register_stack_maps(&self, module: &Module) {
fn register_stack_maps(&self, module: &Module) {
let module = &module.compiled_module();
self.stack_map_registry()
.register_stack_maps(module.stack_maps().map(|(func, stack_maps)| unsafe {
@@ -990,6 +998,15 @@ impl Store {
}));
}
fn register_signatures(&self, module: &Module) {
let trampolines = module.compiled_module().trampolines();
let module = module.compiled_module().module();
let mut signatures = self.signatures().borrow_mut();
for (index, (wasm, native)) in module.signatures.iter() {
signatures.register(wasm, native, trampolines[index]);
}
}
pub(crate) unsafe fn add_instance(&self, handle: InstanceHandle) -> StoreInstanceHandle {
self.inner.instances.borrow_mut().push(handle.clone());
StoreInstanceHandle {

View File

@@ -0,0 +1,86 @@
//! Implement a registry of function signatures, for fast indirect call
//! signature checking.
use std::collections::{hash_map, HashMap};
use std::convert::TryFrom;
use wasmtime_environ::{ir, wasm::WasmFuncType};
use wasmtime_runtime::{VMSharedSignatureIndex, VMTrampoline};
/// WebAssembly requires that the caller and callee signatures in an indirect
/// call must match. To implement this efficiently, keep a registry of all
/// signatures, shared by all instances, so that call sites can just do an
/// index comparison.
#[derive(Debug, Default)]
pub struct SignatureRegistry {
// Map from a wasm actual function type to the index that it is assigned,
// shared amongst all wasm modules.
wasm2index: HashMap<WasmFuncType, VMSharedSignatureIndex>,
// Map of all known wasm function signatures in this registry. This is
// keyed by `VMSharedSignatureIndex` above.
index_map: Vec<Entry>,
}
#[derive(Debug)]
struct Entry {
// The WebAssembly type signature, using wasm types.
wasm: WasmFuncType,
// The native signature we're using for this wasm type signature.
native: ir::Signature,
// The native trampoline used to invoke this type signature from `Func`.
// Note that the code memory for this trampoline is not owned by this
// type, but instead it's expected to be owned by the store that this
// registry lives within.
trampoline: VMTrampoline,
}
impl SignatureRegistry {
/// Register a signature and return its unique index.
pub fn register(
&mut self,
wasm: &WasmFuncType,
native: &ir::Signature,
trampoline: VMTrampoline,
) -> VMSharedSignatureIndex {
let len = self.wasm2index.len();
match self.wasm2index.entry(wasm.clone()) {
hash_map::Entry::Occupied(entry) => *entry.get(),
hash_map::Entry::Vacant(entry) => {
// Keep `signature_hash` len under 2**32 -- VMSharedSignatureIndex::new(std::u32::MAX)
// is reserved for VMSharedSignatureIndex::default().
assert!(
len < std::u32::MAX as usize,
"Invariant check: signature_hash.len() < std::u32::MAX"
);
debug_assert_eq!(len, self.index_map.len());
let index = VMSharedSignatureIndex::new(u32::try_from(len).unwrap());
self.index_map.push(Entry {
wasm: wasm.clone(),
native: native.clone(),
trampoline,
});
entry.insert(index);
index
}
}
}
/// Looks up a shared index from the wasm signature itself.
pub fn lookup(&self, wasm: &WasmFuncType) -> Option<VMSharedSignatureIndex> {
self.wasm2index.get(wasm).cloned()
}
/// Looks up information known about a shared signature index.
///
/// Note that for this operation to be semantically correct the `idx` must
/// have previously come from a call to `register` of this same object.
pub fn lookup_shared(
&self,
idx: VMSharedSignatureIndex,
) -> Option<(&WasmFuncType, &ir::Signature, VMTrampoline)> {
self.index_map
.get(idx.bits() as usize)
.map(|e| (&e.wasm, &e.native, e.trampoline))
}
}

View File

@@ -4,43 +4,35 @@ use crate::trampoline::StoreInstanceHandle;
use crate::Store;
use anyhow::Result;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::Module;
use wasmtime_runtime::{
Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody,
VMFunctionImport, VMSharedSignatureIndex, VMTrampoline,
VMFunctionImport,
};
pub(crate) fn create_handle(
module: Module,
store: &Store,
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
state: Box<dyn Any>,
func_imports: &[VMFunctionImport],
) -> Result<StoreInstanceHandle> {
let mut imports = Imports::default();
imports.functions = func_imports;
// Compute indices into the shared signature table.
let signatures = module
.signatures
.values()
.map(|(wasm, native)| store.register_signature(wasm.clone(), native.clone()))
.collect::<PrimaryMap<_, _>>();
let module = Arc::new(module);
let module2 = module.clone();
unsafe {
let handle = InstanceHandle::new(
Arc::new(module),
module,
Arc::new(()),
&finished_functions,
trampolines,
imports,
store.memory_creator(),
signatures.into_boxed_slice(),
&store.lookup_shared_signature(&module2),
state,
store.interrupts(),
store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,

View File

@@ -6,7 +6,6 @@ use crate::{FuncType, Store, Trap};
use anyhow::Result;
use std::any::Any;
use std::cmp;
use std::collections::HashMap;
use std::mem;
use std::panic::{self, AssertUnwindSafe};
use wasmtime_environ::entity::PrimaryMap;
@@ -215,18 +214,16 @@ pub fn create_handle_with_function(
let pointer_type = isa.pointer_type();
let sig = ft.get_wasmtime_signature(pointer_type);
let wft = ft.to_wasm_func_type();
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut module = Module::new();
let mut finished_functions = PrimaryMap::new();
let mut trampolines = HashMap::new();
let mut code_memory = CodeMemory::new();
// First up we manufacture a trampoline which has the ABI specified by `ft`
// and calls into `stub_fn`...
let sig_id = module
.signatures
.push((ft.to_wasm_func_type(), sig.clone()));
let sig_id = module.signatures.push((wft.clone(), sig.clone()));
let func_id = module.functions.push(sig_id);
module
.exports
@@ -244,8 +241,10 @@ pub fn create_handle_with_function(
&sig,
mem::size_of::<u128>(),
)?;
let sig_id = store.register_signature(ft.to_wasm_func_type(), sig);
trampolines.insert(sig_id, trampoline);
store
.signatures()
.borrow_mut()
.register(&wft, &sig, trampoline);
// Next up we wrap everything up into an `InstanceHandle` by publishing our
// code memory (makes it executable) and ensuring all our various bits of
@@ -256,7 +255,6 @@ pub fn create_handle_with_function(
module,
store,
finished_functions,
trampolines,
Box::new(trampoline_state),
&[],
)
@@ -272,21 +270,21 @@ pub unsafe fn create_handle_with_raw_function(
) -> Result<StoreInstanceHandle> {
let pointer_type = store.engine().compiler().isa().pointer_type();
let sig = ft.get_wasmtime_signature(pointer_type);
let wft = ft.to_wasm_func_type();
let mut module = Module::new();
let mut finished_functions = PrimaryMap::new();
let mut trampolines = HashMap::new();
let sig_id = module
.signatures
.push((ft.to_wasm_func_type(), sig.clone()));
let sig_id = module.signatures.push((wft.clone(), sig.clone()));
let func_id = module.functions.push(sig_id);
module
.exports
.insert(String::new(), EntityIndex::Function(func_id));
finished_functions.push(func);
let sig_id = store.register_signature(ft.to_wasm_func_type(), sig);
trampolines.insert(sig_id, trampoline);
store
.signatures()
.borrow_mut()
.register(&wft, &sig, trampoline);
create_handle(module, store, finished_functions, trampolines, state, &[])
create_handle(module, store, finished_functions, state, &[])
}

View File

@@ -35,10 +35,12 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
Val::FuncRef(Some(f)) => {
// Add a function import to the stub module, and then initialize
// our global with a `ref.func` to grab that imported function.
let signatures = store.signatures().borrow();
let shared_sig_index = f.sig_index();
let local_sig_index = module
.signatures
.push(store.lookup_wasm_and_native_signatures(shared_sig_index));
let (wasm, native, _) = signatures
.lookup_shared(shared_sig_index)
.expect("signature not registered");
let local_sig_index = module.signatures.push((wasm.clone(), native.clone()));
let func_index = module.functions.push(local_sig_index);
module.num_imported_funcs = 1;
module
@@ -66,7 +68,6 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
module,
store,
PrimaryMap::new(),
Default::default(),
Box::new(()),
&func_imports,
)?;

View File

@@ -29,14 +29,7 @@ pub fn create_handle_with_memory(
.exports
.insert(String::new(), EntityIndex::Memory(memory_id));
create_handle(
module,
store,
PrimaryMap::new(),
Default::default(),
Box::new(()),
&[],
)
create_handle(module, store, PrimaryMap::new(), Box::new(()), &[])
}
struct LinearMemoryProxy {

View File

@@ -27,12 +27,5 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
.exports
.insert(String::new(), EntityIndex::Table(table_id));
create_handle(
module,
store,
PrimaryMap::new(),
Default::default(),
Box::new(()),
&[],
)
create_handle(module, store, PrimaryMap::new(), Box::new(()), &[])
}