Add caching. (#134)

* Allow a module to have a different signature registry than the process-specific

* Add core ability to build compiled code caches

* Remove timing printouts

* Serialize/Deserialize memories to reduce copies

* Work more on api

* Relocate local functions relatively before external functions

* Fix incorrect definition in test

* merge errors caused by merge

* Fix emscripten compile

* Fix review comments
This commit is contained in:
Lachlan Sneff
2019-02-06 16:26:45 -08:00
committed by GitHub
parent 2f2f86a4de
commit 8fe9b7eac2
34 changed files with 1768 additions and 291 deletions

View File

@ -0,0 +1,46 @@
use crate::relocation::{ExternalRelocation, TrapSink};
use hashbrown::HashMap;
use wasmer_runtime_core::{
backend::sys::Memory,
cache::{Cache, Error},
module::ModuleInfo,
structures::Map,
types::{LocalFuncIndex, SigIndex},
};
use serde_bench::{deserialize, serialize};
#[derive(Serialize, Deserialize)]
pub struct TrampolineCache {
#[serde(with = "serde_bytes")]
pub code: Vec<u8>,
pub offsets: HashMap<SigIndex, usize>,
}
#[derive(Serialize, Deserialize)]
pub struct BackendCache {
pub external_relocs: Map<LocalFuncIndex, Box<[ExternalRelocation]>>,
pub offsets: Map<LocalFuncIndex, usize>,
pub trap_sink: TrapSink,
pub trampolines: TrampolineCache,
}
impl BackendCache {
pub fn from_cache(cache: Cache) -> Result<(ModuleInfo, Memory, Self), Error> {
let (info, backend_data, compiled_code) = cache.consume();
let backend_cache = deserialize(backend_data.as_slice())
.map_err(|e| Error::DeserializeError(e.to_string()))?;
Ok((info, compiled_code, backend_cache))
}
pub fn into_backend_data(self) -> Result<Vec<u8>, Error> {
let mut buffer = Vec::new();
serialize(&mut buffer, &self).map_err(|e| Error::SerializeError(e.to_string()))?;
Ok(buffer)
}
}

View File

@ -11,7 +11,7 @@ use wasmer_runtime_core::{
backend::{ProtectedCaller, Token},
error::RuntimeResult,
export::Context,
module::{ExportIndex, ModuleInner},
module::{ExportIndex, ModuleInfo, ModuleInner},
types::{FuncIndex, FuncSig, LocalOrImport, SigIndex, Type, Value},
vm::{self, ImportBacking},
};
@ -23,7 +23,7 @@ pub struct Caller {
}
impl Caller {
pub fn new(module: &ModuleInner, handler_data: HandlerData, trampolines: Trampolines) -> Self {
pub fn new(module: &ModuleInfo, handler_data: HandlerData, trampolines: Trampolines) -> Self {
let mut func_export_set = HashSet::new();
for export_index in module.exports.values() {
if let ExportIndex::Func(func_index) = export_index {
@ -118,6 +118,7 @@ fn get_func_from_index(
func_index: FuncIndex,
) -> (*const vm::Func, Context, Arc<FuncSig>, SigIndex) {
let sig_index = *module
.info
.func_assoc
.get(func_index)
.expect("broken invariant, incorrect func index");
@ -141,7 +142,7 @@ fn get_func_from_index(
}
};
let signature = module.sig_registry.lookup_signature(sig_index);
let signature = Arc::clone(&module.info.signatures[sig_index]);
(func_ptr, ctx, signature, sig_index)
}

View File

@ -5,8 +5,7 @@
//! unless you have memory unsafety elsewhere in your code.
use crate::call::sighandler::install_sighandler;
use crate::relocation::{TrapData, TrapSink};
use cranelift_codegen::ir::TrapCode;
use crate::relocation::{TrapCode, TrapData, TrapSink};
use nix::libc::{c_void, siginfo_t};
use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV};
use std::cell::{Cell, UnsafeCell};
@ -36,7 +35,7 @@ unsafe impl Send for HandlerData {}
unsafe impl Sync for HandlerData {}
pub struct HandlerData {
trap_data: TrapSink,
pub trap_data: TrapSink,
buffer_ptr: *const c_void,
buffer_size: usize,
}

View File

@ -4,6 +4,7 @@ use cranelift_codegen::{
ir::{self, InstBuilder},
isa,
};
use cranelift_entity::EntityRef;
use cranelift_wasm::{self, FuncEnvironment, ModuleEnvironment};
use std::mem;
use wasmer_runtime_core::{
@ -162,7 +163,7 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
offset: (local_memory_ptr_offset as i64).into(),
global_type: ptr_type,
}),
self.env.module.memories[local_mem_index],
self.env.module.info.memories[local_mem_index],
)
}
LocalOrImport::Import(import_mem_index) => {
@ -182,7 +183,7 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
offset: (local_memory_ptr_offset as i64).into(),
global_type: ptr_type,
}),
self.env.module.imported_memories[import_mem_index].1,
self.env.module.info.imported_memories[import_mem_index].1,
)
}
};
@ -273,7 +274,7 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
(
table_struct_ptr_ptr,
self.env.module.tables[local_table_index],
self.env.module.info.tables[local_table_index],
)
}
LocalOrImport::Import(import_table_index) => {
@ -295,7 +296,7 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
(
table_struct_ptr_ptr,
self.env.module.imported_tables[import_table_index].1,
self.env.module.info.imported_tables[import_table_index].1,
)
}
};
@ -367,7 +368,8 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
func.import_function(ir::ExtFuncData {
name,
signature,
colocated: false,
// Make this colocated so all calls between local functions are relative.
colocated: true,
})
}
@ -428,9 +430,24 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
pos.ins().trapz(func_ptr, ir::TrapCode::IndirectCallToNull);
let sig_index = self.env.deduplicated[clif_sig_index];
let expected_sig = {
let sig_index_global = pos.func.create_global_value(ir::GlobalValueData::Symbol {
// The index of the `ExternalName` is the undeduplicated, signature index.
name: ir::ExternalName::user(
call_names::SIG_NAMESPACE,
clif_sig_index.index() as u32,
),
offset: 0.into(),
colocated: false,
});
pos.ins().symbol_value(ir::types::I64, sig_index_global)
// let expected_sig = pos.ins().iconst(ir::types::I32, sig_index.index() as i64);
// self.env.deduplicated[clif_sig_index]
};
let expected_sig = pos.ins().iconst(ir::types::I32, sig_index.index() as i64);
let not_equal_flags = pos.ins().ifcmp(found_sig, expected_sig);
pos.ins().trapif(
@ -555,12 +572,12 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
LocalOrImport::Local(local_mem_index) => (
call_names::LOCAL_NAMESPACE,
local_mem_index.index(),
self.env.module.memories[local_mem_index],
self.env.module.info.memories[local_mem_index],
),
LocalOrImport::Import(import_mem_index) => (
call_names::IMPORT_NAMESPACE,
import_mem_index.index(),
self.env.module.imported_memories[import_mem_index].1,
self.env.module.info.imported_memories[import_mem_index].1,
),
};
@ -618,12 +635,12 @@ impl<'env, 'module, 'isa> FuncEnvironment for FuncEnv<'env, 'module, 'isa> {
LocalOrImport::Local(local_mem_index) => (
call_names::LOCAL_NAMESPACE,
local_mem_index.index(),
self.env.module.memories[local_mem_index],
self.env.module.info.memories[local_mem_index],
),
LocalOrImport::Import(import_mem_index) => (
call_names::IMPORT_NAMESPACE,
import_mem_index.index(),
self.env.module.imported_memories[import_mem_index].1,
self.env.module.info.imported_memories[import_mem_index].1,
),
};

View File

@ -1,3 +1,5 @@
#[cfg(feature = "cache")]
mod cache;
mod call;
mod func_env;
mod libcalls;
@ -12,11 +14,23 @@ use cranelift_codegen::{
settings::{self, Configurable},
};
use target_lexicon::Triple;
#[cfg(feature = "cache")]
use wasmer_runtime_core::{
backend::sys::Memory,
cache::{Cache, Error as CacheError},
module::ModuleInfo,
};
use wasmer_runtime_core::{
backend::{Compiler, Token},
error::{CompileError, CompileResult},
module::ModuleInner,
};
#[cfg(feature = "cache")]
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "cache")]
extern crate serde;
use wasmparser::{self, WasmDecoder};
pub struct CraneliftCompiler {}
@ -28,7 +42,7 @@ impl CraneliftCompiler {
}
impl Compiler for CraneliftCompiler {
// Compiles wasm binary to a wasmer module.
/// Compiles wasm binary to a wasmer module.
fn compile(&self, wasm: &[u8], _: Token) -> CompileResult<ModuleInner> {
validate(wasm)?;
@ -36,10 +50,48 @@ impl Compiler for CraneliftCompiler {
let mut module = module::Module::empty();
let module_env = module_env::ModuleEnv::new(&mut module, &*isa);
let func_bodies = module_env.translate(wasm)?;
module.compile(&*isa, func_bodies)
}
/// Create a wasmer Module from an already-compiled cache.
#[cfg(feature = "cache")]
unsafe fn from_cache(&self, cache: Cache, _: Token) -> Result<ModuleInner, CacheError> {
module::Module::from_cache(cache)
}
#[cfg(feature = "cache")]
fn compile_to_backend_cache_data(
&self,
wasm: &[u8],
_: Token,
) -> CompileResult<(Box<ModuleInfo>, Vec<u8>, Memory)> {
validate(wasm)?;
let isa = get_isa();
let mut module = module::Module::empty();
let module_env = module_env::ModuleEnv::new(&mut module, &*isa);
let func_bodies = module_env.translate(wasm)?;
let (info, backend_cache, compiled_code) = module
.compile_to_backend_cache(&*isa, func_bodies)
.map_err(|e| CompileError::InternalError {
msg: format!("{:?}", e),
})?;
let buffer =
backend_cache
.into_backend_data()
.map_err(|e| CompileError::InternalError {
msg: format!("{:?}", e),
})?;
Ok((Box::new(info), buffer, compiled_code))
}
}
fn get_isa() -> Box<isa::TargetIsa> {

View File

@ -1,4 +1,7 @@
#[cfg(feature = "cache")]
use crate::cache::BackendCache;
use crate::{call::Caller, resolver::FuncResolverBuilder, trampoline::Trampolines};
use cranelift_codegen::{ir, isa};
use cranelift_entity::EntityRef;
use cranelift_wasm;
@ -7,11 +10,15 @@ use std::{
ops::{Deref, DerefMut},
ptr::NonNull,
};
#[cfg(feature = "cache")]
use wasmer_runtime_core::{
backend::SigRegistry,
backend::{FuncResolver, ProtectedCaller, Token},
backend::sys::Memory,
cache::{Cache, Error as CacheError},
};
use wasmer_runtime_core::{
backend::{Backend, FuncResolver, ProtectedCaller, Token},
error::{CompileResult, RuntimeResult},
module::ModuleInner,
module::{ModuleInfo, ModuleInner, StringTable},
structures::{Map, TypedIndex},
types::{
FuncIndex, FuncSig, GlobalIndex, LocalFuncIndex, MemoryIndex, SigIndex, TableIndex, Type,
@ -59,24 +66,30 @@ impl Module {
func_resolver: Box::new(Placeholder),
protected_caller: Box::new(Placeholder),
memories: Map::new(),
globals: Map::new(),
tables: Map::new(),
info: ModuleInfo {
memories: Map::new(),
globals: Map::new(),
tables: Map::new(),
imported_functions: Map::new(),
imported_memories: Map::new(),
imported_tables: Map::new(),
imported_globals: Map::new(),
imported_functions: Map::new(),
imported_memories: Map::new(),
imported_tables: Map::new(),
imported_globals: Map::new(),
exports: HashMap::new(),
exports: HashMap::new(),
data_initializers: Vec::new(),
elem_initializers: Vec::new(),
data_initializers: Vec::new(),
elem_initializers: Vec::new(),
start_func: None,
start_func: None,
func_assoc: Map::new(),
sig_registry: SigRegistry,
func_assoc: Map::new(),
signatures: Map::new(),
backend: Backend::Cranelift,
namespace_table: StringTable::new(),
name_table: StringTable::new(),
},
},
}
}
@ -86,19 +99,60 @@ impl Module {
isa: &isa::TargetIsa,
functions: Map<LocalFuncIndex, ir::Function>,
) -> CompileResult<ModuleInner> {
let imported_functions_len = self.module.imported_functions.len();
let (func_resolver_builder, handler_data) =
FuncResolverBuilder::new(isa, functions, imported_functions_len)?;
FuncResolverBuilder::new(isa, functions, &self.module.info)?;
self.module.func_resolver = Box::new(func_resolver_builder.finalize()?);
self.module.func_resolver =
Box::new(func_resolver_builder.finalize(&self.module.info.signatures)?);
let trampolines = Trampolines::new(isa, &self.module);
let trampolines = Trampolines::new(isa, &self.module.info);
self.module.protected_caller =
Box::new(Caller::new(&self.module, handler_data, trampolines));
Box::new(Caller::new(&self.module.info, handler_data, trampolines));
Ok(self.module)
}
#[cfg(feature = "cache")]
pub fn compile_to_backend_cache(
self,
isa: &isa::TargetIsa,
functions: Map<LocalFuncIndex, ir::Function>,
) -> CompileResult<(ModuleInfo, BackendCache, Memory)> {
let (func_resolver_builder, handler_data) =
FuncResolverBuilder::new(isa, functions, &self.module.info)?;
let trampolines = Trampolines::new(isa, &self.module.info);
let trampoline_cache = trampolines.to_trampoline_cache();
let (backend_cache, compiled_code) =
func_resolver_builder.to_backend_cache(trampoline_cache, handler_data);
Ok((self.module.info, backend_cache, compiled_code))
}
#[cfg(feature = "cache")]
pub fn from_cache(cache: Cache) -> Result<ModuleInner, CacheError> {
let (info, compiled_code, backend_cache) = BackendCache::from_cache(cache)?;
let (func_resolver_builder, trampolines, handler_data) =
FuncResolverBuilder::new_from_backend_cache(backend_cache, compiled_code, &info)?;
let func_resolver = Box::new(
func_resolver_builder
.finalize(&info.signatures)
.map_err(|e| CacheError::Unknown(format!("{:?}", e)))?,
);
let protected_caller = Box::new(Caller::new(&info, handler_data, trampolines));
Ok(ModuleInner {
func_resolver,
protected_caller,
info,
})
}
}
impl Deref for Module {

View File

@ -3,16 +3,18 @@ use crate::{
module::{Converter, Module},
};
use cranelift_codegen::{ir, isa};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{self, translate_module, FuncTranslator, ModuleEnvironment};
use hashbrown::HashMap;
use std::sync::Arc;
use wasmer_runtime_core::{
error::{CompileError, CompileResult},
module::{DataInitializer, ExportIndex, ImportName, TableInitializer},
module::{
DataInitializer, ExportIndex, ImportName, NameIndex, NamespaceIndex, StringTableBuilder,
TableInitializer,
},
structures::{Map, TypedIndex},
types::{
ElementType, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, Initializer,
LocalFuncIndex, LocalOrImport, MemoryDescriptor, SigIndex, TableDescriptor, Value,
ElementType, GlobalDescriptor, GlobalIndex, GlobalInit, Initializer, LocalFuncIndex,
LocalOrImport, MemoryDescriptor, SigIndex, TableDescriptor, Value,
},
units::Pages,
};
@ -23,8 +25,8 @@ pub struct ModuleEnv<'module, 'isa> {
pub signatures: Map<SigIndex, ir::Signature>,
globals: Map<GlobalIndex, cranelift_wasm::Global>,
func_bodies: Map<LocalFuncIndex, ir::Function>,
pub deduplicated: PrimaryMap<cranelift_wasm::SignatureIndex, SigIndex>,
duplicated: HashMap<SigIndex, cranelift_wasm::SignatureIndex>,
namespace_table_builder: StringTableBuilder<NamespaceIndex>,
name_table_builder: StringTableBuilder<NameIndex>,
}
impl<'module, 'isa> ModuleEnv<'module, 'isa> {
@ -35,14 +37,18 @@ impl<'module, 'isa> ModuleEnv<'module, 'isa> {
signatures: Map::new(),
globals: Map::new(),
func_bodies: Map::new(),
deduplicated: PrimaryMap::new(),
duplicated: HashMap::new(),
namespace_table_builder: StringTableBuilder::new(),
name_table_builder: StringTableBuilder::new(),
}
}
pub fn translate(mut self, wasm: &[u8]) -> CompileResult<Map<LocalFuncIndex, ir::Function>> {
translate_module(wasm, &mut self)
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
self.module.info.namespace_table = self.namespace_table_builder.finish();
self.module.info.name_table = self.name_table_builder.finish();
Ok(self.func_bodies)
}
}
@ -55,12 +61,11 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
/// Declares a function signature to the environment.
fn declare_signature(&mut self, sig: &ir::Signature) {
let clif_sig_index = self.signatures.push(sig.clone());
let func_sig: FuncSig = Converter(sig).into();
let sig_index = self.module.sig_registry.lookup_sig_index(func_sig);
self.deduplicated.push(sig_index);
self.duplicated
.insert(sig_index, Converter(clif_sig_index).into());
self.signatures.push(sig.clone());
self.module
.info
.signatures
.push(Arc::new(Converter(sig).into()));
}
/// Return the signature with the given index.
@ -75,25 +80,34 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
namespace: &'data str,
name: &'data str,
) {
let sig_index = self.deduplicated[clif_sig_index];
self.module.func_assoc.push(sig_index);
// We convert the cranelift signature index to
// a wasmer signature index without deduplicating
// because we'll deduplicate later.
let sig_index = Converter(clif_sig_index).into();
self.module.info.func_assoc.push(sig_index);
let namespace_index = self.namespace_table_builder.register(namespace);
let name_index = self.name_table_builder.register(name);
// Add import names to list of imported functions
self.module.imported_functions.push(ImportName {
namespace: namespace.to_string(),
name: name.to_string(),
self.module.info.imported_functions.push(ImportName {
namespace_index,
name_index,
});
}
/// Return the number of imported funcs.
fn get_num_func_imports(&self) -> usize {
self.module.imported_functions.len()
self.module.info.imported_functions.len()
}
/// Declares the type (signature) of a local function in the module.
fn declare_func_type(&mut self, clif_sig_index: cranelift_wasm::SignatureIndex) {
let sig_index = self.deduplicated[clif_sig_index];
self.module.func_assoc.push(sig_index);
// We convert the cranelift signature index to
// a wasmer signature index without deduplicating
// because we'll deduplicate later.
let sig_index = Converter(clif_sig_index).into();
self.module.info.func_assoc.push(sig_index);
}
/// Return the signature index for the given function index.
@ -101,8 +115,8 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
&self,
func_index: cranelift_wasm::FuncIndex,
) -> cranelift_wasm::SignatureIndex {
let sig_index: SigIndex = self.module.func_assoc[Converter(func_index).into()];
self.duplicated[&sig_index]
let sig_index: SigIndex = self.module.info.func_assoc[Converter(func_index).into()];
Converter(sig_index).into()
}
/// Declares a global to the environment.
@ -134,7 +148,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
};
// Add global ir to the list of globals
self.module.globals.push(GlobalInit { desc, init });
self.module.info.globals.push(GlobalInit { desc, init });
self.globals.push(global);
}
@ -151,9 +165,12 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
_ => false,
});
let namespace_index = self.namespace_table_builder.register(namespace);
let name_index = self.name_table_builder.register(name);
let import_name = ImportName {
namespace: namespace.to_string(),
name: name.to_string(),
namespace_index,
name_index,
};
let desc = GlobalDescriptor {
@ -162,7 +179,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
};
// Add global ir to the list of globals
self.module.imported_globals.push((import_name, desc));
self.module.info.imported_globals.push((import_name, desc));
self.globals.push(global);
}
@ -176,7 +193,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
fn declare_table(&mut self, table: cranelift_wasm::Table) {
use cranelift_wasm::TableElementType;
// Add table ir to the list of tables
self.module.tables.push(TableDescriptor {
self.module.info.tables.push(TableDescriptor {
element: match table.ty {
TableElementType::Func => ElementType::Anyfunc,
_ => unimplemented!(),
@ -195,9 +212,12 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
) {
use cranelift_wasm::TableElementType;
let namespace_index = self.namespace_table_builder.register(namespace);
let name_index = self.name_table_builder.register(name);
let import_name = ImportName {
namespace: namespace.to_string(),
name: name.to_string(),
namespace_index,
name_index,
};
let imported_table = TableDescriptor {
@ -211,6 +231,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
// Add import names to list of imported tables
self.module
.info
.imported_tables
.push((import_name, imported_table));
}
@ -239,7 +260,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
};
// Add table initializer to list of table initializers
self.module.elem_initializers.push(TableInitializer {
self.module.info.elem_initializers.push(TableInitializer {
table_index: Converter(table_index).into(),
base,
elements: elements
@ -251,7 +272,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
/// Declares a memory to the environment
fn declare_memory(&mut self, memory: cranelift_wasm::Memory) {
self.module.memories.push(MemoryDescriptor {
self.module.info.memories.push(MemoryDescriptor {
minimum: Pages(memory.minimum),
maximum: memory.maximum.map(|max| Pages(max)),
shared: memory.shared,
@ -265,9 +286,12 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
namespace: &'data str,
name: &'data str,
) {
let namespace_index = self.namespace_table_builder.register(namespace);
let name_index = self.name_table_builder.register(name);
let import_name = ImportName {
namespace: namespace.to_string(),
name: name.to_string(),
namespace_index,
name_index,
};
let memory = MemoryDescriptor {
@ -277,7 +301,10 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
};
// Add import names to list of imported memories
self.module.imported_memories.push((import_name, memory));
self.module
.info
.imported_memories
.push((import_name, memory));
}
/// Fills a declared memory with bytes at module instantiation.
@ -303,7 +330,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
};
// Add data initializer to list of data initializers
self.module.data_initializers.push(DataInitializer {
self.module.info.data_initializers.push(DataInitializer {
memory_index: Converter(memory_index).into(),
base,
data: data.to_vec(),
@ -312,14 +339,14 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
/// Declares a function export to the environment.
fn declare_func_export(&mut self, func_index: cranelift_wasm::FuncIndex, name: &'data str) {
self.module.exports.insert(
self.module.info.exports.insert(
name.to_string(),
ExportIndex::Func(Converter(func_index).into()),
);
}
/// Declares a table export to the environment.
fn declare_table_export(&mut self, table_index: cranelift_wasm::TableIndex, name: &'data str) {
self.module.exports.insert(
self.module.info.exports.insert(
name.to_string(),
ExportIndex::Table(Converter(table_index).into()),
);
@ -330,7 +357,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
memory_index: cranelift_wasm::MemoryIndex,
name: &'data str,
) {
self.module.exports.insert(
self.module.info.exports.insert(
name.to_string(),
ExportIndex::Memory(Converter(memory_index).into()),
);
@ -341,7 +368,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
global_index: cranelift_wasm::GlobalIndex,
name: &'data str,
) {
self.module.exports.insert(
self.module.info.exports.insert(
name.to_string(),
ExportIndex::Global(Converter(global_index).into()),
);
@ -349,7 +376,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
/// Declares a start function.
fn declare_start_func(&mut self, func_index: cranelift_wasm::FuncIndex) {
self.module.start_func = Some(Converter(func_index).into());
self.module.info.start_func = Some(Converter(func_index).into());
}
/// Provides the contents of a function body.

View File

@ -3,14 +3,16 @@
//! any other calls that this function is doing, so we can "patch" the
//! function addrs in runtime with the functions we need.
use cranelift_codegen::binemit;
pub use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::ir::{self, ExternalName, LibCall, SourceLoc, TrapCode};
use hashbrown::HashMap;
use wasmer_runtime_core::{structures::TypedIndex, types::LocalFuncIndex};
use cranelift_codegen::ir::{self, ExternalName, SourceLoc};
use wasmer_runtime_core::{
structures::TypedIndex,
types::{FuncIndex, SigIndex},
};
pub mod call_names {
pub const LOCAL_NAMESPACE: u32 = 1;
pub const IMPORT_NAMESPACE: u32 = 2;
pub const SIG_NAMESPACE: u32 = 3;
pub const STATIC_MEM_GROW: u32 = 0;
pub const STATIC_MEM_SIZE: u32 = 1;
@ -20,10 +22,33 @@ pub mod call_names {
pub const DYNAMIC_MEM_SIZE: u32 = 5;
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Reloc {
Abs8,
X86PCRel4,
X86CallPCRel4,
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone)]
pub enum LibCall {
Probestack,
CeilF32,
CeilF64,
FloorF32,
FloorF64,
TruncF32,
TruncF64,
NearestF32,
NearestF64,
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct Relocation {
pub struct ExternalRelocation {
/// The relocation code.
pub reloc: binemit::Reloc,
pub reloc: Reloc,
/// The offset where to apply the relocation.
pub offset: binemit::CodeOffset,
/// The addend to add to the relocation value.
@ -32,6 +57,16 @@ pub struct Relocation {
pub target: RelocationType,
}
pub struct LocalRelocation {
/// The offset where to apply the relocation.
pub offset: binemit::CodeOffset,
/// The addend to add to the relocation value.
pub addend: binemit::Addend,
/// Relocation type.
pub target: FuncIndex,
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum VmCallKind {
StaticMemoryGrow,
@ -44,6 +79,7 @@ pub enum VmCallKind {
DynamicMemorySize,
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum VmCall {
Local(VmCallKind),
@ -51,18 +87,20 @@ pub enum VmCall {
}
/// Specify the type of relocation
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub enum RelocationType {
Normal(LocalFuncIndex),
Intrinsic(String),
LibCall(LibCall),
VmCall(VmCall),
Signature(SigIndex),
}
/// Implementation of a relocation sink that just saves all the information for later
pub struct RelocSink {
/// Relocations recorded for the function.
pub func_relocs: Vec<Relocation>,
pub external_relocs: Vec<ExternalRelocation>,
pub local_relocs: Vec<LocalRelocation>,
}
impl binemit::RelocSink for RelocSink {
@ -82,22 +120,30 @@ impl binemit::RelocSink for RelocSink {
name: &ExternalName,
addend: binemit::Addend,
) {
let reloc = match reloc {
binemit::Reloc::Abs8 => Reloc::Abs8,
binemit::Reloc::X86PCRel4 => Reloc::X86PCRel4,
binemit::Reloc::X86CallPCRel4 => Reloc::X86CallPCRel4,
_ => unimplemented!("unimplented reloc type: {}", reloc),
};
match *name {
ExternalName::User {
namespace: 0,
index,
} => {
self.func_relocs.push(Relocation {
reloc,
assert_eq!(reloc, Reloc::X86CallPCRel4);
self.local_relocs.push(LocalRelocation {
offset,
addend,
target: RelocationType::Normal(LocalFuncIndex::new(index as usize)),
target: FuncIndex::new(index as usize),
});
}
ExternalName::User { namespace, index } => {
use self::call_names::*;
let target = RelocationType::VmCall(match namespace {
LOCAL_NAMESPACE => VmCall::Local(match index {
let target = match namespace {
LOCAL_NAMESPACE => RelocationType::VmCall(VmCall::Local(match index {
STATIC_MEM_GROW => VmCallKind::StaticMemoryGrow,
STATIC_MEM_SIZE => VmCallKind::StaticMemorySize,
@ -107,8 +153,8 @@ impl binemit::RelocSink for RelocSink {
DYNAMIC_MEM_GROW => VmCallKind::DynamicMemoryGrow,
DYNAMIC_MEM_SIZE => VmCallKind::DynamicMemorySize,
_ => unimplemented!(),
}),
IMPORT_NAMESPACE => VmCall::Import(match index {
})),
IMPORT_NAMESPACE => RelocationType::VmCall(VmCall::Import(match index {
STATIC_MEM_GROW => VmCallKind::StaticMemoryGrow,
STATIC_MEM_SIZE => VmCallKind::StaticMemorySize,
@ -118,10 +164,11 @@ impl binemit::RelocSink for RelocSink {
DYNAMIC_MEM_GROW => VmCallKind::DynamicMemoryGrow,
DYNAMIC_MEM_SIZE => VmCallKind::DynamicMemorySize,
_ => unimplemented!(),
}),
})),
SIG_NAMESPACE => RelocationType::Signature(SigIndex::new(index as usize)),
_ => unimplemented!(),
});
self.func_relocs.push(Relocation {
};
self.external_relocs.push(ExternalRelocation {
reloc,
offset,
addend,
@ -131,7 +178,7 @@ impl binemit::RelocSink for RelocSink {
ExternalName::TestCase { length, ascii } => {
let (slice, _) = ascii.split_at(length as usize);
let name = String::from_utf8(slice.to_vec()).unwrap();
self.func_relocs.push(Relocation {
self.external_relocs.push(ExternalRelocation {
reloc,
offset,
addend,
@ -139,8 +186,20 @@ impl binemit::RelocSink for RelocSink {
});
}
ExternalName::LibCall(libcall) => {
let libcall = match libcall {
ir::LibCall::CeilF32 => LibCall::CeilF32,
ir::LibCall::FloorF32 => LibCall::FloorF32,
ir::LibCall::TruncF32 => LibCall::TruncF32,
ir::LibCall::NearestF32 => LibCall::NearestF32,
ir::LibCall::CeilF64 => LibCall::CeilF64,
ir::LibCall::FloorF64 => LibCall::FloorF64,
ir::LibCall::TruncF64 => LibCall::TruncF64,
ir::LibCall::NearestF64 => LibCall::NearestF64,
ir::LibCall::Probestack => LibCall::Probestack,
_ => unimplemented!("unimplemented libcall: {}", libcall),
};
let relocation_type = RelocationType::LibCall(libcall);
self.func_relocs.push(Relocation {
self.external_relocs.push(ExternalRelocation {
reloc,
offset,
addend,
@ -159,43 +218,64 @@ impl binemit::RelocSink for RelocSink {
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub enum TrapCode {
StackOverflow,
HeapOutOfBounds,
TableOutOfBounds,
OutOfBounds,
IndirectCallToNull,
BadSignature,
IntegerOverflow,
IntegerDivisionByZero,
BadConversionToInteger,
Interrupt,
User(u16),
}
/// Implementation of a relocation sink that just saves all the information for later
impl RelocSink {
pub fn new() -> RelocSink {
RelocSink {
func_relocs: Vec::new(),
pub fn new() -> Self {
Self {
external_relocs: Vec::new(),
local_relocs: Vec::new(),
}
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub struct TrapData {
pub trapcode: TrapCode,
pub srcloc: SourceLoc,
pub srcloc: u32,
}
/// Simple implementation of a TrapSink
/// that saves the info for later.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
pub struct TrapSink {
trap_datas: HashMap<usize, TrapData>,
trap_datas: Vec<(usize, TrapData)>,
}
impl TrapSink {
pub fn new() -> TrapSink {
TrapSink {
trap_datas: HashMap::new(),
trap_datas: Vec::new(),
}
}
pub fn lookup(&self, offset: usize) -> Option<TrapData> {
self.trap_datas.get(&offset).cloned()
self.trap_datas.get(offset).map(|(_, trap_data)| *trap_data)
}
pub fn drain_local(&mut self, current_func_offset: usize, local: &mut LocalTrapSink) {
local.trap_datas.drain(..).for_each(|(offset, trap_data)| {
self.trap_datas
.insert(current_func_offset + offset, trap_data);
});
self.trap_datas.extend(
local
.trap_datas
.drain(..)
.map(|(offset, trap_data)| (current_func_offset + offset, trap_data)),
);
}
}
@ -210,8 +290,27 @@ impl LocalTrapSink {
}
impl binemit::TrapSink for LocalTrapSink {
fn trap(&mut self, offset: u32, srcloc: SourceLoc, trapcode: TrapCode) {
self.trap_datas
.push((offset as usize, TrapData { trapcode, srcloc }));
fn trap(&mut self, offset: u32, srcloc: SourceLoc, trapcode: ir::TrapCode) {
let trapcode = match trapcode {
ir::TrapCode::StackOverflow => TrapCode::StackOverflow,
ir::TrapCode::HeapOutOfBounds => TrapCode::HeapOutOfBounds,
ir::TrapCode::TableOutOfBounds => TrapCode::TableOutOfBounds,
ir::TrapCode::OutOfBounds => TrapCode::OutOfBounds,
ir::TrapCode::IndirectCallToNull => TrapCode::IndirectCallToNull,
ir::TrapCode::BadSignature => TrapCode::BadSignature,
ir::TrapCode::IntegerOverflow => TrapCode::IntegerOverflow,
ir::TrapCode::IntegerDivisionByZero => TrapCode::IntegerDivisionByZero,
ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
ir::TrapCode::Interrupt => TrapCode::Interrupt,
ir::TrapCode::User(x) => TrapCode::User(x),
};
self.trap_datas.push((
offset as usize,
TrapData {
trapcode,
srcloc: srcloc.bits(),
},
));
}
}

View File

@ -1,39 +1,104 @@
use crate::call::HandlerData;
use crate::libcalls;
use crate::relocation::{
LocalTrapSink, Reloc, RelocSink, Relocation, RelocationType, TrapSink, VmCall, VmCallKind,
#[cfg(feature = "cache")]
use crate::{
cache::{BackendCache, TrampolineCache},
trampoline::Trampolines,
};
use crate::{
call::HandlerData,
libcalls,
relocation::{
ExternalRelocation, LibCall, LocalRelocation, LocalTrapSink, Reloc, RelocSink,
RelocationType, TrapSink, VmCall, VmCallKind,
},
};
use byteorder::{ByteOrder, LittleEndian};
use cranelift_codegen::{ir, isa, Context};
use std::mem;
use std::ptr::{write_unaligned, NonNull};
use std::{
mem,
ptr::{write_unaligned, NonNull},
sync::Arc,
};
#[cfg(feature = "cache")]
use wasmer_runtime_core::cache::Error as CacheError;
use wasmer_runtime_core::{
self,
backend::{
self,
sys::{Memory, Protect},
SigRegistry,
},
error::{CompileError, CompileResult},
structures::{Map, TypedIndex},
types::LocalFuncIndex,
module::ModuleInfo,
structures::{Map, SliceMap, TypedIndex},
types::{FuncSig, LocalFuncIndex, SigIndex},
vm, vmcalls,
};
#[allow(dead_code)]
pub struct FuncResolverBuilder {
resolver: FuncResolver,
relocations: Map<LocalFuncIndex, Vec<Relocation>>,
local_relocs: Map<LocalFuncIndex, Box<[LocalRelocation]>>,
external_relocs: Map<LocalFuncIndex, Box<[ExternalRelocation]>>,
import_len: usize,
}
impl FuncResolverBuilder {
#[cfg(feature = "cache")]
pub fn new_from_backend_cache(
backend_cache: BackendCache,
mut code: Memory,
info: &ModuleInfo,
) -> Result<(Self, Trampolines, HandlerData), CacheError> {
unsafe {
code.protect(.., Protect::ReadWrite)
.map_err(|e| CacheError::Unknown(e.to_string()))?;
}
let handler_data =
HandlerData::new(backend_cache.trap_sink, code.as_ptr() as _, code.size());
Ok((
Self {
resolver: FuncResolver {
map: backend_cache.offsets,
memory: code,
},
local_relocs: Map::new(),
external_relocs: backend_cache.external_relocs,
import_len: info.imported_functions.len(),
},
Trampolines::from_trampoline_cache(backend_cache.trampolines),
handler_data,
))
}
#[cfg(feature = "cache")]
pub fn to_backend_cache(
mut self,
trampolines: TrampolineCache,
handler_data: HandlerData,
) -> (BackendCache, Memory) {
self.relocate_locals();
(
BackendCache {
external_relocs: self.external_relocs,
offsets: self.resolver.map,
trap_sink: handler_data.trap_data,
trampolines,
},
self.resolver.memory,
)
}
pub fn new(
isa: &isa::TargetIsa,
function_bodies: Map<LocalFuncIndex, ir::Function>,
import_len: usize,
info: &ModuleInfo,
) -> CompileResult<(Self, HandlerData)> {
let mut compiled_functions: Vec<Vec<u8>> = Vec::with_capacity(function_bodies.len());
let mut relocations = Map::with_capacity(function_bodies.len());
let mut local_relocs = Map::with_capacity(function_bodies.len());
let mut external_relocs = Map::new();
let mut trap_sink = TrapSink::new();
let mut local_trap_sink = LocalTrapSink::new();
@ -58,7 +123,8 @@ impl FuncResolverBuilder {
total_size += round_up(code_buf.len(), mem::size_of::<usize>());
compiled_functions.push(code_buf);
relocations.push(reloc_sink.func_relocs);
local_relocs.push(reloc_sink.local_relocs.into_boxed_slice());
external_relocs.push(reloc_sink.external_relocs.into_boxed_slice());
}
let mut memory = Memory::with_size(total_size)
@ -98,43 +164,58 @@ impl FuncResolverBuilder {
let handler_data = HandlerData::new(trap_sink, memory.as_ptr() as _, memory.size());
Ok((
Self {
resolver: FuncResolver { map, memory },
relocations,
import_len,
},
handler_data,
))
let mut func_resolver_builder = Self {
resolver: FuncResolver { map, memory },
local_relocs,
external_relocs,
import_len: info.imported_functions.len(),
};
func_resolver_builder.relocate_locals();
Ok((func_resolver_builder, handler_data))
}
pub fn finalize(mut self) -> CompileResult<FuncResolver> {
for (index, relocs) in self.relocations.iter() {
for ref reloc in relocs {
let target_func_address: isize = match reloc.target {
RelocationType::Normal(local_func_index) => {
// This will always be an internal function
// because imported functions are not
// called in this way.
// Adjust from wasm-wide function index to index of locally-defined functions only.
let local_func_index =
LocalFuncIndex::new(local_func_index.index() - self.import_len);
fn relocate_locals(&mut self) {
for (index, relocs) in self.local_relocs.iter() {
for ref reloc in relocs.iter() {
let local_func_index = LocalFuncIndex::new(reloc.target.index() - self.import_len);
let target_func_address =
self.resolver.lookup(local_func_index).unwrap().as_ptr() as usize;
self.resolver.lookup(local_func_index).unwrap().as_ptr() as isize
}
// We need the address of the current function
// because these calls are relative.
let func_addr = self.resolver.lookup(index).unwrap().as_ptr() as usize;
unsafe {
let reloc_address = func_addr + reloc.offset as usize;
let reloc_delta = target_func_address
.wrapping_sub(reloc_address)
.wrapping_add(reloc.addend as usize);
write_unaligned(reloc_address as *mut u32, reloc_delta as u32);
}
}
}
}
pub fn finalize(
mut self,
signatures: &SliceMap<SigIndex, Arc<FuncSig>>,
) -> CompileResult<FuncResolver> {
for (index, relocs) in self.external_relocs.iter() {
for ref reloc in relocs.iter() {
let target_func_address: isize = match reloc.target {
RelocationType::LibCall(libcall) => match libcall {
ir::LibCall::CeilF32 => libcalls::ceilf32 as isize,
ir::LibCall::FloorF32 => libcalls::floorf32 as isize,
ir::LibCall::TruncF32 => libcalls::truncf32 as isize,
ir::LibCall::NearestF32 => libcalls::nearbyintf32 as isize,
ir::LibCall::CeilF64 => libcalls::ceilf64 as isize,
ir::LibCall::FloorF64 => libcalls::floorf64 as isize,
ir::LibCall::TruncF64 => libcalls::truncf64 as isize,
ir::LibCall::NearestF64 => libcalls::nearbyintf64 as isize,
ir::LibCall::Probestack => libcalls::__rust_probestack as isize,
_ => Err(CompileError::InternalError {
msg: format!("unexpected libcall: {}", libcall),
})?,
LibCall::CeilF32 => libcalls::ceilf32 as isize,
LibCall::FloorF32 => libcalls::floorf32 as isize,
LibCall::TruncF32 => libcalls::truncf32 as isize,
LibCall::NearestF32 => libcalls::nearbyintf32 as isize,
LibCall::CeilF64 => libcalls::ceilf64 as isize,
LibCall::FloorF64 => libcalls::floorf64 as isize,
LibCall::TruncF64 => libcalls::truncf64 as isize,
LibCall::NearestF64 => libcalls::nearbyintf64 as isize,
LibCall::Probestack => libcalls::__rust_probestack as isize,
},
RelocationType::Intrinsic(ref name) => match name.as_str() {
"i32print" => i32_print as isize,
@ -181,10 +262,15 @@ impl FuncResolverBuilder {
}
},
},
RelocationType::Signature(sig_index) => {
let sig_index =
SigRegistry.lookup_sig_index(Arc::clone(&signatures[sig_index]));
sig_index.index() as _
}
};
// We need the address of the current function
// because these calls are relative.
// because some of these calls are relative.
let func_addr = self.resolver.lookup(index).unwrap().as_ptr();
// Determine relocation type and apply relocation.
@ -200,17 +286,14 @@ impl FuncResolverBuilder {
};
LittleEndian::write_u64(ptr_slice, ptr_to_write);
}
Reloc::X86PCRel4 => unsafe {
let reloc_address = func_addr.offset(reloc.offset as isize) as isize;
let reloc_addend = reloc.addend as isize;
// TODO: Handle overflow.
let reloc_delta_i32 =
(target_func_address - reloc_address + reloc_addend) as i32;
write_unaligned(reloc_address as *mut i32, reloc_delta_i32);
Reloc::X86PCRel4 | Reloc::X86CallPCRel4 => unsafe {
let reloc_address = (func_addr as usize) + reloc.offset as usize;
let reloc_delta = target_func_address
.wrapping_sub(reloc_address as isize)
.wrapping_add(reloc.addend as isize);
write_unaligned(reloc_address as *mut u32, reloc_delta as u32);
},
_ => Err(CompileError::InternalError {
msg: format!("unsupported reloc kind: {}", reloc.reloc),
})?,
}
}
}

View File

@ -1,3 +1,5 @@
#[cfg(feature = "cache")]
use crate::cache::TrampolineCache;
use cranelift_codegen::{
binemit::{NullTrapSink, Reloc, RelocSink},
cursor::{Cursor, FuncCursor},
@ -8,7 +10,7 @@ use hashbrown::HashMap;
use std::{iter, mem};
use wasmer_runtime_core::{
backend::sys::{Memory, Protect},
module::{ExportIndex, ModuleInner},
module::{ExportIndex, ModuleInfo},
types::{FuncSig, SigIndex, Type},
vm,
};
@ -27,7 +29,45 @@ pub struct Trampolines {
}
impl Trampolines {
pub fn new(isa: &isa::TargetIsa, module: &ModuleInner) -> Self {
#[cfg(feature = "cache")]
pub fn from_trampoline_cache(cache: TrampolineCache) -> Self {
// pub struct TrampolineCache {
// #[serde(with = "serde_bytes")]
// code: Vec<u8>,
// offsets: HashMap<SigIndex, usize>,
// }
let mut memory = Memory::with_size(cache.code.len()).unwrap();
unsafe {
memory.protect(.., Protect::ReadWrite).unwrap();
// Copy over the compiled code.
memory.as_slice_mut()[..cache.code.len()].copy_from_slice(cache.code.as_slice());
memory.protect(.., Protect::ReadExec).unwrap();
}
Self {
memory,
offsets: cache.offsets,
}
}
#[cfg(feature = "cache")]
pub fn to_trampoline_cache(self) -> TrampolineCache {
let mut code = vec![0; self.memory.size()];
unsafe {
code.copy_from_slice(self.memory.as_slice());
}
TrampolineCache {
code,
offsets: self.offsets,
}
}
pub fn new(isa: &isa::TargetIsa, module: &ModuleInfo) -> Self {
let func_index_iter = module
.exports
.values()
@ -43,7 +83,7 @@ impl Trampolines {
for exported_func_index in func_index_iter {
let sig_index = module.func_assoc[*exported_func_index];
let func_sig = module.sig_registry.lookup_signature(sig_index);
let func_sig = &module.signatures[sig_index];
let trampoline_func = generate_func(&func_sig);