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

@ -6,6 +6,12 @@ use crate::{
types::{FuncIndex, LocalFuncIndex, Value},
vm,
};
#[cfg(feature = "cache")]
use crate::{
cache::{Cache, Error as CacheError},
module::ModuleInfo,
sys::Memory,
};
use std::ptr::NonNull;
pub mod sys {
@ -13,6 +19,12 @@ pub mod sys {
}
pub use crate::sig_registry::SigRegistry;
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Backend {
Cranelift,
}
/// This type cannot be constructed from
/// outside the runtime crate.
pub struct Token {
@ -30,6 +42,16 @@ pub trait Compiler {
/// The `CompileToken` parameter ensures that this can only
/// be called from inside the runtime.
fn compile(&self, wasm: &[u8], _: Token) -> CompileResult<ModuleInner>;
#[cfg(feature = "cache")]
unsafe fn from_cache(&self, cache: Cache, _: Token) -> Result<ModuleInner, CacheError>;
#[cfg(feature = "cache")]
fn compile_to_backend_cache_data(
&self,
wasm: &[u8],
_: Token,
) -> CompileResult<(Box<ModuleInfo>, Vec<u8>, Memory)>;
}
/// The functionality exposed by this trait is expected to be used

View File

@ -5,6 +5,7 @@ use crate::{
import::ImportObject,
memory::Memory,
module::{ImportName, ModuleInner},
sig_registry::SigRegistry,
structures::{BoxedMap, Map, SliceMap, TypedIndex},
table::Table,
types::{
@ -13,7 +14,7 @@ use crate::{
},
vm,
};
use std::slice;
use std::{slice, sync::Arc};
#[derive(Debug)]
pub struct LocalBacking {
@ -58,9 +59,8 @@ impl LocalBacking {
}
fn generate_memories(module: &ModuleInner) -> BoxedMap<LocalMemoryIndex, Memory> {
let mut memories = Map::with_capacity(module.memories.len());
for (_, &desc) in &module.memories {
let mut memories = Map::with_capacity(module.info.memories.len());
for (_, &desc) in &module.info.memories {
memories.push(Memory::new(desc).expect("unable to create memory"));
}
@ -74,6 +74,7 @@ impl LocalBacking {
) -> BoxedMap<LocalMemoryIndex, *mut vm::LocalMemory> {
// For each init that has some data...
for init in module
.info
.data_initializers
.iter()
.filter(|init| init.data.len() > 0)
@ -92,7 +93,7 @@ impl LocalBacking {
match init.memory_index.local_or_import(module) {
LocalOrImport::Local(local_memory_index) => {
let memory_desc = module.memories[local_memory_index];
let memory_desc = module.info.memories[local_memory_index];
let data_top = init_base + init.data.len();
assert!(memory_desc.minimum.bytes().0 >= data_top);
@ -128,9 +129,9 @@ impl LocalBacking {
}
fn generate_tables(module: &ModuleInner) -> BoxedMap<LocalTableIndex, Table> {
let mut tables = Map::with_capacity(module.tables.len());
let mut tables = Map::with_capacity(module.info.tables.len());
for (_, &table_desc) in module.tables.iter() {
for (_, &table_desc) in module.info.tables.iter() {
let table = Table::new(table_desc).unwrap();
tables.push(table);
}
@ -145,7 +146,7 @@ impl LocalBacking {
tables: &mut SliceMap<LocalTableIndex, Table>,
vmctx: *mut vm::Ctx,
) -> BoxedMap<LocalTableIndex, *mut vm::LocalTable> {
for init in &module.elem_initializers {
for init in &module.info.elem_initializers {
let init_base = match init.base {
Initializer::Const(Value::I32(offset)) => offset as u32,
Initializer::Const(_) => panic!("a const initializer must be the i32 type"),
@ -170,8 +171,11 @@ impl LocalBacking {
table.anyfunc_direct_access_mut(|elements| {
for (i, &func_index) in init.elements.iter().enumerate() {
let sig_index = module.func_assoc[func_index];
let sig_id = vm::SigId(sig_index.index() as u32);
let sig_index = module.info.func_assoc[func_index];
let signature = &module.info.signatures[sig_index];
let sig_id = vm::SigId(
SigRegistry.lookup_sig_index(Arc::clone(&signature)).index() as u32,
);
let (func, ctx) = match func_index.local_or_import(module) {
LocalOrImport::Local(local_func_index) => (
@ -205,8 +209,11 @@ impl LocalBacking {
table.anyfunc_direct_access_mut(|elements| {
for (i, &func_index) in init.elements.iter().enumerate() {
let sig_index = module.func_assoc[func_index];
let sig_id = vm::SigId(sig_index.index() as u32);
let sig_index = module.info.func_assoc[func_index];
let signature = &module.info.signatures[sig_index];
let sig_id = vm::SigId(
SigRegistry.lookup_sig_index(Arc::clone(&signature)).index() as u32,
);
let (func, ctx) = match func_index.local_or_import(module) {
LocalOrImport::Local(local_func_index) => (
@ -243,9 +250,9 @@ impl LocalBacking {
module: &ModuleInner,
imports: &ImportBacking,
) -> BoxedMap<LocalGlobalIndex, Global> {
let mut globals = Map::with_capacity(module.globals.len());
let mut globals = Map::with_capacity(module.info.globals.len());
for (_, global_init) in module.globals.iter() {
for (_, global_init) in module.info.globals.iter() {
let value = match &global_init.init {
Initializer::Const(value) => value.clone(),
Initializer::GetGlobal(import_global_index) => {
@ -348,10 +355,21 @@ fn import_functions(
vmctx: *mut vm::Ctx,
) -> LinkResult<BoxedMap<ImportedFuncIndex, vm::ImportedFunc>> {
let mut link_errors = vec![];
let mut functions = Map::with_capacity(module.imported_functions.len());
for (index, ImportName { namespace, name }) in &module.imported_functions {
let sig_index = module.func_assoc[index.convert_up(module)];
let expected_sig = module.sig_registry.lookup_signature(sig_index);
let mut functions = Map::with_capacity(module.info.imported_functions.len());
for (
index,
ImportName {
namespace_index,
name_index,
},
) in &module.info.imported_functions
{
let sig_index = module.info.func_assoc[index.convert_up(module)];
let expected_sig = &module.info.signatures[sig_index];
let namespace = module.info.namespace_table.get(*namespace_index);
let name = module.info.name_table.get(*name_index);
let import = imports
.get_namespace(namespace)
.and_then(|namespace| namespace.get_export(name));
@ -361,7 +379,7 @@ fn import_functions(
ctx,
signature,
}) => {
if expected_sig == signature {
if *expected_sig == signature {
functions.push(vm::ImportedFunc {
func: func.inner(),
vmctx: match ctx {
@ -371,8 +389,8 @@ fn import_functions(
});
} else {
link_errors.push(LinkError::IncorrectImportSignature {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: expected_sig.clone(),
found: signature.clone(),
});
@ -387,16 +405,16 @@ fn import_functions(
}
.to_string();
link_errors.push(LinkError::IncorrectImportType {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: "function".to_string(),
found: export_type_name,
});
}
None => {
link_errors.push(LinkError::ImportNotFound {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
});
}
}
@ -417,11 +435,22 @@ fn import_memories(
BoxedMap<ImportedMemoryIndex, *mut vm::LocalMemory>,
)> {
let mut link_errors = vec![];
let mut memories = Map::with_capacity(module.imported_memories.len());
let mut vm_memories = Map::with_capacity(module.imported_memories.len());
for (_index, (ImportName { namespace, name }, expected_memory_desc)) in
&module.imported_memories
let mut memories = Map::with_capacity(module.info.imported_memories.len());
let mut vm_memories = Map::with_capacity(module.info.imported_memories.len());
for (
_index,
(
ImportName {
namespace_index,
name_index,
},
expected_memory_desc,
),
) in &module.info.imported_memories
{
let namespace = module.info.namespace_table.get(*namespace_index);
let name = module.info.name_table.get(*name_index);
let memory_import = imports
.get_namespace(&namespace)
.and_then(|namespace| namespace.get_export(&name));
@ -432,8 +461,8 @@ fn import_memories(
vm_memories.push(memory.vm_local_memory());
} else {
link_errors.push(LinkError::IncorrectMemoryDescriptor {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: *expected_memory_desc,
found: memory.descriptor(),
});
@ -448,16 +477,16 @@ fn import_memories(
}
.to_string();
link_errors.push(LinkError::IncorrectImportType {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: "memory".to_string(),
found: export_type_name,
});
}
None => {
link_errors.push(LinkError::ImportNotFound {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
});
}
}
@ -478,9 +507,22 @@ fn import_tables(
BoxedMap<ImportedTableIndex, *mut vm::LocalTable>,
)> {
let mut link_errors = vec![];
let mut tables = Map::with_capacity(module.imported_tables.len());
let mut vm_tables = Map::with_capacity(module.imported_tables.len());
for (_index, (ImportName { namespace, name }, expected_table_desc)) in &module.imported_tables {
let mut tables = Map::with_capacity(module.info.imported_tables.len());
let mut vm_tables = Map::with_capacity(module.info.imported_tables.len());
for (
_index,
(
ImportName {
namespace_index,
name_index,
},
expected_table_desc,
),
) in &module.info.imported_tables
{
let namespace = module.info.namespace_table.get(*namespace_index);
let name = module.info.name_table.get(*name_index);
let table_import = imports
.get_namespace(&namespace)
.and_then(|namespace| namespace.get_export(&name));
@ -491,8 +533,8 @@ fn import_tables(
tables.push(table);
} else {
link_errors.push(LinkError::IncorrectTableDescriptor {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: *expected_table_desc,
found: table.descriptor(),
});
@ -507,16 +549,16 @@ fn import_tables(
}
.to_string();
link_errors.push(LinkError::IncorrectImportType {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: "table".to_string(),
found: export_type_name,
});
}
None => {
link_errors.push(LinkError::ImportNotFound {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
});
}
}
@ -537,9 +579,21 @@ fn import_globals(
BoxedMap<ImportedGlobalIndex, *mut vm::LocalGlobal>,
)> {
let mut link_errors = vec![];
let mut globals = Map::with_capacity(module.imported_globals.len());
let mut vm_globals = Map::with_capacity(module.imported_globals.len());
for (_, (ImportName { namespace, name }, imported_global_desc)) in &module.imported_globals {
let mut globals = Map::with_capacity(module.info.imported_globals.len());
let mut vm_globals = Map::with_capacity(module.info.imported_globals.len());
for (
_,
(
ImportName {
namespace_index,
name_index,
},
imported_global_desc,
),
) in &module.info.imported_globals
{
let namespace = module.info.namespace_table.get(*namespace_index);
let name = module.info.name_table.get(*name_index);
let import = imports
.get_namespace(namespace)
.and_then(|namespace| namespace.get_export(name));
@ -550,8 +604,8 @@ fn import_globals(
globals.push(global);
} else {
link_errors.push(LinkError::IncorrectGlobalDescriptor {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: *imported_global_desc,
found: global.descriptor(),
});
@ -566,16 +620,16 @@ fn import_globals(
}
.to_string();
link_errors.push(LinkError::IncorrectImportType {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
expected: "global".to_string(),
found: export_type_name,
});
}
None => {
link_errors.push(LinkError::ImportNotFound {
namespace: namespace.clone(),
name: name.clone(),
namespace: namespace.to_string(),
name: name.to_string(),
});
}
}

View File

@ -0,0 +1,181 @@
use crate::{module::ModuleInfo, sys::Memory};
use memmap::Mmap;
use serde_bench::{deserialize, serialize};
use sha2::{Digest, Sha256};
use std::{
fs::File,
io::{self, Seek, SeekFrom, Write},
mem,
path::Path,
slice,
};
#[derive(Debug)]
pub enum InvalidFileType {
InvalidSize,
InvalidMagic,
}
#[derive(Debug)]
pub enum Error {
IoError(io::Error),
DeserializeError(String),
SerializeError(String),
Unknown(String),
InvalidFile(InvalidFileType),
InvalidatedCache,
}
const CURRENT_CACHE_VERSION: u64 = 0;
/// The header of a cache file.
#[repr(C, packed)]
struct CacheHeader {
magic: [u8; 8], // [W, A, S, M, E, R, \0, \0]
version: u64,
data_len: u64,
wasm_hash: [u8; 32], // Sha256 of the wasm in binary format.
}
impl CacheHeader {
pub fn read_from_slice(buffer: &[u8]) -> Result<(&CacheHeader, &[u8]), Error> {
if buffer.len() >= mem::size_of::<CacheHeader>() {
if &buffer[..8] == "WASMER\0\0".as_bytes() {
let (header_slice, body_slice) = buffer.split_at(mem::size_of::<CacheHeader>());
let header = unsafe { &*(header_slice.as_ptr() as *const CacheHeader) };
if header.version == CURRENT_CACHE_VERSION {
Ok((header, body_slice))
} else {
Err(Error::InvalidatedCache)
}
} else {
Err(Error::InvalidFile(InvalidFileType::InvalidMagic))
}
} else {
Err(Error::InvalidFile(InvalidFileType::InvalidSize))
}
}
pub fn as_slice(&self) -> &[u8] {
let ptr = self as *const CacheHeader as *const u8;
unsafe { slice::from_raw_parts(ptr, mem::size_of::<CacheHeader>()) }
}
}
#[derive(Serialize, Deserialize)]
struct CacheInner {
info: Box<ModuleInfo>,
#[serde(with = "serde_bytes")]
backend_metadata: Vec<u8>,
compiled_code: Memory,
}
pub struct Cache {
inner: CacheInner,
wasm_hash: Box<[u8; 32]>,
}
impl Cache {
pub(crate) fn new(
wasm: &[u8],
info: Box<ModuleInfo>,
backend_metadata: Vec<u8>,
compiled_code: Memory,
) -> Self {
let wasm_hash = hash_data(wasm);
Self {
inner: CacheInner {
info,
backend_metadata,
compiled_code,
},
wasm_hash: Box::new(wasm_hash),
}
}
pub fn open<P>(path: P) -> Result<Cache, Error>
where
P: AsRef<Path>,
{
let file = File::open(path).map_err(|e| Error::IoError(e))?;
let mmap = unsafe { Mmap::map(&file).map_err(|e| Error::IoError(e))? };
let (header, body_slice) = CacheHeader::read_from_slice(&mmap[..])?;
let inner =
deserialize(body_slice).map_err(|e| Error::DeserializeError(format!("{:#?}", e)))?;
Ok(Cache {
inner,
wasm_hash: Box::new(header.wasm_hash),
})
}
pub fn info(&self) -> &ModuleInfo {
&self.inner.info
}
pub fn wasm_hash(&self) -> &[u8; 32] {
&self.wasm_hash
}
#[doc(hidden)]
pub fn consume(self) -> (ModuleInfo, Vec<u8>, Memory) {
(
*self.inner.info,
self.inner.backend_metadata,
self.inner.compiled_code,
)
}
pub fn store<P>(&self, path: P) -> Result<(), Error>
where
P: AsRef<Path>,
{
let mut file = File::create(path).map_err(|e| Error::IoError(e))?;
let mut buffer = Vec::new();
serialize(&mut buffer, &self.inner).map_err(|e| Error::SerializeError(e.to_string()))?;
let data_len = buffer.len() as u64;
file.seek(SeekFrom::Start(mem::size_of::<CacheHeader>() as u64))
.map_err(|e| Error::IoError(e))?;
file.write(buffer.as_slice())
.map_err(|e| Error::IoError(e))?;
file.seek(SeekFrom::Start(0))
.map_err(|e| Error::Unknown(e.to_string()))?;
let wasm_hash = {
let mut array = [0u8; 32];
array.copy_from_slice(&*self.wasm_hash);
array
};
let cache_header = CacheHeader {
magic: [
'W' as u8, 'A' as u8, 'S' as u8, 'M' as u8, 'E' as u8, 'R' as u8, 0, 0,
],
version: CURRENT_CACHE_VERSION,
data_len,
wasm_hash,
};
file.write(cache_header.as_slice())
.map_err(|e| Error::IoError(e))?;
Ok(())
}
}
pub fn hash_data(data: &[u8]) -> [u8; 32] {
let mut array = [0u8; 32];
array.copy_from_slice(Sha256::digest(data).as_slice());
array
}

View File

@ -49,7 +49,7 @@ impl<'a> ExportIter<'a> {
pub(crate) fn new(module: &'a ModuleInner, inner: &'a mut InstanceInner) -> Self {
Self {
inner,
iter: module.exports.iter(),
iter: module.info.exports.iter(),
module,
}
}

View File

@ -7,6 +7,7 @@ use crate::{
import::{ImportObject, LikeNamespace},
memory::Memory,
module::{ExportIndex, Module, ModuleInner},
sig_registry::SigRegistry,
table::Table,
typed_func::{Func, Safe, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},
@ -65,7 +66,7 @@ impl Instance {
let instance = Instance { module, inner };
if let Some(start_index) = instance.module.start_func {
if let Some(start_index) = instance.module.info.start_func {
instance.call_with_index(start_index, &[])?;
}
@ -98,6 +99,7 @@ impl Instance {
{
let export_index =
self.module
.info
.exports
.get(name)
.ok_or_else(|| ResolveError::ExportNotFound {
@ -107,10 +109,11 @@ impl Instance {
if let ExportIndex::Func(func_index) = export_index {
let sig_index = *self
.module
.info
.func_assoc
.get(*func_index)
.expect("broken invariant, incorrect func index");
let signature = self.module.sig_registry.lookup_signature(sig_index);
let signature = SigRegistry.lookup_signature(sig_index);
if signature.params() != Args::types() || signature.returns() != Rets::types() {
Err(ResolveError::Signature {
@ -167,6 +170,7 @@ impl Instance {
pub fn dyn_func(&self, name: &str) -> ResolveResult<DynFunc> {
let export_index =
self.module
.info
.exports
.get(name)
.ok_or_else(|| ResolveError::ExportNotFound {
@ -176,10 +180,11 @@ impl Instance {
if let ExportIndex::Func(func_index) = export_index {
let sig_index = *self
.module
.info
.func_assoc
.get(*func_index)
.expect("broken invariant, incorrect func index");
let signature = self.module.sig_registry.lookup_signature(sig_index);
let signature = Arc::clone(&self.module.info.signatures[sig_index]);
Ok(DynFunc {
signature,
@ -220,6 +225,7 @@ impl Instance {
pub fn call(&self, name: &str, args: &[Value]) -> CallResult<Vec<Value>> {
let export_index =
self.module
.info
.exports
.get(name)
.ok_or_else(|| ResolveError::ExportNotFound {
@ -270,10 +276,11 @@ impl Instance {
fn call_with_index(&self, func_index: FuncIndex, args: &[Value]) -> CallResult<Vec<Value>> {
let sig_index = *self
.module
.info
.func_assoc
.get(func_index)
.expect("broken invariant, incorrect func index");
let signature = self.module.sig_registry.lookup_signature(sig_index);
let signature = &self.module.info.signatures[sig_index];
if !signature.check_param_value_types(args) {
Err(ResolveError::Signature {
@ -344,6 +351,7 @@ impl InstanceInner {
func_index: FuncIndex,
) -> (FuncPointer, Context, Arc<FuncSig>) {
let sig_index = *module
.info
.func_assoc
.get(func_index)
.expect("broken invariant, incorrect func index");
@ -367,9 +375,13 @@ impl InstanceInner {
}
};
let signature = module.sig_registry.lookup_signature(sig_index);
let signature = &module.info.signatures[sig_index];
(unsafe { FuncPointer::new(func_ptr) }, ctx, signature)
(
unsafe { FuncPointer::new(func_ptr) },
ctx,
Arc::clone(signature),
)
}
fn get_memory_from_index(&self, module: &ModuleInner, mem_index: MemoryIndex) -> Memory {
@ -406,7 +418,7 @@ impl InstanceInner {
impl LikeNamespace for Instance {
fn get_export(&self, name: &str) -> Option<Export> {
let export_index = self.module.exports.get(name)?;
let export_index = self.module.info.exports.get(name)?;
Some(self.inner.get_export_from_index(&self.module, export_index))
}

View File

@ -2,11 +2,17 @@
#[macro_use]
extern crate field_offset;
#[cfg(feature = "cache")]
#[macro_use]
extern crate serde_derive;
#[macro_use]
mod macros;
#[doc(hidden)]
pub mod backend;
mod backing;
#[cfg(feature = "cache")]
pub mod cache;
pub mod error;
pub mod export;
pub mod global;
@ -36,6 +42,9 @@ pub use self::module::Module;
pub use self::typed_func::Func;
use std::sync::Arc;
#[cfg(feature = "cache")]
use self::cache::{Cache, Error as CacheError};
pub mod prelude {
pub use crate::import::{ImportObject, Namespace};
pub use crate::types::{
@ -79,5 +88,28 @@ pub fn validate(wasm: &[u8]) -> bool {
}
}
#[cfg(feature = "cache")]
pub fn compile_to_cache_with(
wasm: &[u8],
compiler: &dyn backend::Compiler,
) -> CompileResult<Cache> {
let token = backend::Token::generate();
let (info, backend_metadata, compiled_code) =
compiler.compile_to_backend_cache_data(wasm, token)?;
Ok(Cache::new(wasm, info, backend_metadata, compiled_code))
}
#[cfg(feature = "cache")]
pub unsafe fn load_cache_with(
cache: Cache,
compiler: &dyn backend::Compiler,
) -> std::result::Result<module::Module, CacheError> {
let token = backend::Token::generate();
compiler
.from_cache(cache, token)
.map(|inner| module::Module::new(Arc::new(inner)))
}
/// The current version of this crate
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@ -1,11 +1,10 @@
use crate::{
backend::{FuncResolver, ProtectedCaller},
backend::{Backend, FuncResolver, ProtectedCaller},
error::Result,
import::ImportObject,
sig_registry::SigRegistry,
structures::Map,
structures::{Map, TypedIndex},
types::{
FuncIndex, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex,
FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit, ImportedFuncIndex,
ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, Initializer,
LocalGlobalIndex, LocalMemoryIndex, LocalTableIndex, MemoryDescriptor, MemoryIndex,
SigIndex, TableDescriptor, TableIndex,
@ -13,6 +12,7 @@ use crate::{
Instance,
};
use hashbrown::HashMap;
use indexmap::IndexMap;
use std::sync::Arc;
/// This is used to instantiate a new WebAssembly module.
@ -21,6 +21,11 @@ pub struct ModuleInner {
pub func_resolver: Box<dyn FuncResolver>,
pub protected_caller: Box<dyn ProtectedCaller>,
pub info: ModuleInfo,
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
pub struct ModuleInfo {
// This are strictly local and the typsystem ensures that.
pub memories: Map<LocalMemoryIndex, MemoryDescriptor>,
pub globals: Map<LocalGlobalIndex, GlobalInit>,
@ -40,7 +45,11 @@ pub struct ModuleInner {
pub start_func: Option<FuncIndex>,
pub func_assoc: Map<FuncIndex, SigIndex>,
pub sig_registry: SigRegistry,
pub signatures: Map<SigIndex, Arc<FuncSig>>,
pub backend: Backend,
pub namespace_table: StringTable<NamespaceIndex>,
pub name_table: StringTable<NameIndex>,
}
/// A compiled WebAssembly module.
@ -87,21 +96,14 @@ impl Module {
impl ModuleInner {}
#[doc(hidden)]
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct ImportName {
pub namespace: String,
pub name: String,
}
impl From<(String, String)> for ImportName {
fn from(n: (String, String)) -> Self {
ImportName {
namespace: n.0,
name: n.1,
}
}
pub namespace_index: NamespaceIndex,
pub name_index: NameIndex,
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExportIndex {
Func(FuncIndex),
@ -111,6 +113,7 @@ pub enum ExportIndex {
}
/// A data initializer for linear memory.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DataInitializer {
/// The index of the memory to initialize.
@ -118,10 +121,12 @@ pub struct DataInitializer {
/// Either a constant offset or a `get_global`
pub base: Initializer,
/// The initialization data.
#[cfg_attr(feature = "cache", serde(with = "serde_bytes"))]
pub data: Vec<u8>,
}
/// A WebAssembly table initializer.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct TableInitializer {
/// The index of a table to initialize.
@ -131,3 +136,110 @@ pub struct TableInitializer {
/// The values to write into the table elements.
pub elements: Vec<FuncIndex>,
}
pub struct StringTableBuilder<K: TypedIndex> {
map: IndexMap<String, (K, u32, u32)>,
buffer: String,
count: u32,
}
impl<K: TypedIndex> StringTableBuilder<K> {
pub fn new() -> Self {
Self {
map: IndexMap::new(),
buffer: String::new(),
count: 0,
}
}
pub fn register<S>(&mut self, s: S) -> K
where
S: Into<String> + AsRef<str>,
{
let s_str = s.as_ref();
if self.map.contains_key(s_str) {
self.map[s_str].0
} else {
let offset = self.buffer.len();
let length = s_str.len();
let index = TypedIndex::new(self.count as _);
self.buffer.push_str(s_str);
self.map
.insert(s.into(), (index, offset as u32, length as u32));
self.count += 1;
index
}
}
pub fn finish(self) -> StringTable<K> {
let table = self
.map
.values()
.map(|(_, offset, length)| (*offset, *length))
.collect();
StringTable {
table,
buffer: self.buffer,
}
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct StringTable<K: TypedIndex> {
table: Map<K, (u32, u32)>,
buffer: String,
}
impl<K: TypedIndex> StringTable<K> {
pub fn new() -> Self {
Self {
table: Map::new(),
buffer: String::new(),
}
}
pub fn get(&self, index: K) -> &str {
let (offset, length) = self.table[index];
let offset = offset as usize;
let length = length as usize;
&self.buffer[offset..offset + length]
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct NamespaceIndex(u32);
impl TypedIndex for NamespaceIndex {
#[doc(hidden)]
fn new(index: usize) -> Self {
NamespaceIndex(index as _)
}
#[doc(hidden)]
fn index(&self) -> usize {
self.0 as usize
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct NameIndex(u32);
impl TypedIndex for NameIndex {
#[doc(hidden)]
fn new(index: usize) -> Self {
NameIndex(index as _)
}
#[doc(hidden)]
fn index(&self) -> usize {
self.0 as usize
}
}

View File

@ -8,6 +8,7 @@ use std::{
};
/// Dense item map
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct Map<K, V>
where

View File

@ -6,7 +6,7 @@ pub use self::boxed::BoxedMap;
pub use self::map::{Iter, IterMut, Map};
pub use self::slice::SliceMap;
pub trait TypedIndex {
pub trait TypedIndex: Copy + Clone {
#[doc(hidden)]
fn new(index: usize) -> Self;
#[doc(hidden)]

View File

@ -9,3 +9,77 @@ pub use self::unix::*;
#[cfg(windows)]
pub use self::windows::*;
#[cfg(feature = "cache")]
use serde::{
de::{self, SeqAccess, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
#[cfg(feature = "cache")]
use serde_bytes::Bytes;
#[cfg(feature = "cache")]
use std::fmt;
#[cfg(feature = "cache")]
impl Serialize for Memory {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
assert!(self.protection().is_readable());
let mut state = serializer.serialize_struct("Memory", 2)?;
state.serialize_field("protection", &self.protection())?;
state.serialize_field("data", &Bytes::new(unsafe { self.as_slice() }))?;
state.end()
}
}
#[cfg(feature = "cache")]
impl<'de> Deserialize<'de> for Memory {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MemoryVisitor;
impl<'de> Visitor<'de> for MemoryVisitor {
type Value = Memory;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "struct Memory")
}
fn visit_seq<V>(self, mut seq: V) -> Result<Memory, V::Error>
where
V: SeqAccess<'de>,
{
let original_protection = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let bytes: Bytes = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
let mut memory = Memory::with_size_protect(bytes.len(), Protect::ReadWrite)
.expect("Could not create a memory");
unsafe {
memory.as_slice_mut().copy_from_slice(&*bytes);
if memory.protection() != original_protection {
memory
.protect(.., original_protection)
.expect("Could not protect memory as its original protection");
}
}
Ok(memory)
}
}
deserializer.deserialize_struct("Memory", &["protection", "data"], MemoryVisitor)
}
}

View File

@ -2,7 +2,7 @@ use errno;
use nix::libc;
use page_size;
use std::ops::{Bound, RangeBounds};
use std::{ptr, slice};
use std::{fs::File, os::unix::io::IntoRawFd, path::Path, ptr, rc::Rc, slice};
unsafe impl Send for Memory {}
unsafe impl Sync for Memory {}
@ -11,14 +11,86 @@ unsafe impl Sync for Memory {}
pub struct Memory {
ptr: *mut u8,
size: usize,
protection: Protect,
fd: Option<Rc<RawFd>>,
}
impl Memory {
pub fn from_file_path<P>(path: P, protection: Protect) -> Result<Self, String>
where
P: AsRef<Path>,
{
let file = File::open(path).map_err(|e| e.to_string())?;
let file_len = file.metadata().map_err(|e| e.to_string())?.len();
let raw_fd = RawFd::from_file(file);
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
file_len as usize,
protection.to_protect_const() as i32,
libc::MAP_PRIVATE,
raw_fd.0,
0,
)
};
if ptr == -1 as _ {
Err(errno::errno().to_string())
} else {
Ok(Self {
ptr: ptr as *mut u8,
size: file_len as usize,
protection,
fd: Some(Rc::new(raw_fd)),
})
}
}
pub fn with_size_protect(size: usize, protection: Protect) -> Result<Self, String> {
if size == 0 {
return Ok(Self {
ptr: ptr::null_mut(),
size: 0,
protection,
fd: None,
});
}
let size = round_up_to_page_size(size, page_size::get());
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
size,
protection.to_protect_const() as i32,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
)
};
if ptr == -1 as _ {
Err(errno::errno().to_string())
} else {
Ok(Self {
ptr: ptr as *mut u8,
size,
protection,
fd: None,
})
}
}
pub fn with_size(size: usize) -> Result<Self, String> {
if size == 0 {
return Ok(Self {
ptr: ptr::null_mut(),
size: 0,
protection: Protect::None,
fd: None,
});
}
@ -41,6 +113,8 @@ impl Memory {
Ok(Self {
ptr: ptr as *mut u8,
size,
protection: Protect::None,
fd: None,
})
}
}
@ -48,9 +122,9 @@ impl Memory {
pub unsafe fn protect(
&mut self,
range: impl RangeBounds<usize>,
protect: Protect,
protection: Protect,
) -> Result<(), String> {
let protect = protect.to_protect_const();
let protect = protection.to_protect_const();
let range_start = match range.start_bound() {
Bound::Included(start) => *start,
@ -75,10 +149,32 @@ impl Memory {
if success == -1 {
Err(errno::errno().to_string())
} else {
self.protection = protection;
Ok(())
}
}
pub fn split_at(mut self, offset: usize) -> (Memory, Memory) {
let page_size = page_size::get();
if offset % page_size == 0 {
let second_ptr = unsafe { self.ptr.add(offset) };
let second_size = self.size - offset;
self.size = offset;
let second = Memory {
ptr: second_ptr,
size: second_size,
protection: self.protection,
fd: self.fd.clone(),
};
(self, second)
} else {
panic!("offset must be multiple of page size: {}", offset)
}
}
pub fn size(&self) -> usize {
self.size
}
@ -91,6 +187,10 @@ impl Memory {
slice::from_raw_parts_mut(self.ptr, self.size)
}
pub fn protection(&self) -> Protect {
self.protection
}
pub fn as_ptr(&self) -> *mut u8 {
self.ptr
}
@ -105,6 +205,7 @@ impl Drop for Memory {
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub enum Protect {
@ -123,6 +224,41 @@ impl Protect {
Protect::ReadExec => 1 | 4,
}
}
pub fn is_readable(self) -> bool {
match self {
Protect::Read | Protect::ReadWrite | Protect::ReadExec => true,
_ => false,
}
}
pub fn is_writable(self) -> bool {
match self {
Protect::ReadWrite => true,
_ => false,
}
}
}
#[derive(Debug)]
struct RawFd(i32);
impl RawFd {
fn from_file(f: File) -> Self {
RawFd(f.into_raw_fd())
}
}
impl Drop for RawFd {
fn drop(&mut self) {
let success = unsafe { libc::close(self.0) };
assert_eq!(
success,
0,
"failed to close mmapped file descriptor: {}",
errno::errno()
);
}
}
/// Round `size` up to the nearest multiple of `page_size`.

View File

@ -14,6 +14,7 @@ unsafe impl Sync for Memory {}
pub struct Memory {
ptr: *mut u8,
size: usize,
protection: Protect,
}
impl Memory {
@ -22,6 +23,7 @@ impl Memory {
return Ok(Self {
ptr: ptr::null_mut(),
size: 0,
protection: Protect::None,
});
}
@ -35,6 +37,7 @@ impl Memory {
Ok(Self {
ptr: ptr as *mut u8,
size,
protection: Protect::None,
})
}
}
@ -71,10 +74,31 @@ impl Memory {
if ptr.is_null() {
Err("unable to protect memory".to_string())
} else {
self.protection = protection;
Ok(())
}
}
pub fn split_at(mut self, offset: usize) -> (Memory, Memory) {
let page_size = page_size::get();
if offset % page_size == 0 {
let second_ptr = unsafe { self.ptr.add(offset) };
let second_size = self.size - offset;
self.size = offset;
let second = Memory {
ptr: second_ptr,
size: second_size,
protection: self.protection,
};
(self, second)
} else {
panic!("offset must be multiple of page size: {}", offset)
}
}
pub fn size(&self) -> usize {
self.size
}
@ -87,6 +111,10 @@ impl Memory {
slice::from_raw_parts_mut(self.ptr, self.size)
}
pub fn protection(&self) -> Protect {
self.protection
}
pub fn as_ptr(&self) -> *mut u8 {
self.ptr
}
@ -101,6 +129,7 @@ impl Drop for Memory {
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub enum Protect {
@ -119,6 +148,20 @@ impl Protect {
Protect::ReadExec => PAGE_EXECUTE_READ,
}
}
pub fn is_readable(self) -> bool {
match self {
Protect::Read | Protect::ReadWrite | Protect::ReadExec => true,
_ => false,
}
}
pub fn is_writable(self) -> bool {
match self {
Protect::ReadWrite => true,
_ => false,
}
}
}
/// Round `size` up to the nearest multiple of `page_size`.

View File

@ -2,6 +2,7 @@ use crate::{memory::MemoryType, module::ModuleInner, structures::TypedIndex, uni
use std::{borrow::Cow, mem};
/// Represents a WebAssembly type.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Type {
/// The `i32` type.
@ -18,6 +19,7 @@ pub enum Type {
///
/// As the number of types in WebAssembly expand,
/// this structure will expand as well.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
/// The `i32` type.
@ -145,12 +147,32 @@ macro_rules! convert_value_impl {
convert_value_impl!(u8, i8, u16, i16, u32, i32, u64, i64);
impl ValueType for f32 {
fn into_le(self, buffer: &mut [u8]) {
self.to_bits().into_le(buffer);
}
fn from_le(buffer: &[u8]) -> Result<Self, ValueError> {
Ok(f32::from_bits(<u32 as ValueType>::from_le(buffer)?))
}
}
impl ValueType for f64 {
fn into_le(self, buffer: &mut [u8]) {
self.to_bits().into_le(buffer);
}
fn from_le(buffer: &[u8]) -> Result<Self, ValueError> {
Ok(f64::from_bits(<u64 as ValueType>::from_le(buffer)?))
}
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementType {
/// Any wasm function.
Anyfunc,
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy)]
pub struct TableDescriptor {
/// Type of data stored in this table.
@ -175,6 +197,7 @@ impl TableDescriptor {
/// A const value initializer.
/// Over time, this will be able to represent more and more
/// complex expressions.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum Initializer {
/// Corresponds to a `const.*` instruction.
@ -183,6 +206,7 @@ pub enum Initializer {
GetGlobal(ImportedGlobalIndex),
}
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlobalDescriptor {
pub mutable: bool,
@ -190,6 +214,7 @@ pub struct GlobalDescriptor {
}
/// A wasm global.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct GlobalInit {
pub desc: GlobalDescriptor,
@ -197,6 +222,7 @@ pub struct GlobalInit {
}
/// A wasm memory.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemoryDescriptor {
/// The minimum number of allowed pages.
@ -229,6 +255,7 @@ impl MemoryDescriptor {
/// The signature of a function that is either implemented
/// in a wasm module or exposed to wasm by the host.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FuncSig {
params: Cow<'static, [Type]>,
@ -273,6 +300,7 @@ pub trait LocalImport {
#[rustfmt::skip]
macro_rules! define_map_index {
($ty:ident) => {
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct $ty (u32);
impl TypedIndex for $ty {
@ -313,17 +341,17 @@ macro_rules! define_local_or_import {
($ty:ident, $local_ty:ident, $imported_ty:ident, $imports:ident) => {
impl $ty {
pub fn local_or_import(self, module: &ModuleInner) -> LocalOrImport<$ty> {
if self.index() < module.$imports.len() {
if self.index() < module.info.$imports.len() {
LocalOrImport::Import(<Self as LocalImport>::Import::new(self.index()))
} else {
LocalOrImport::Local(<Self as LocalImport>::Local::new(self.index() - module.$imports.len()))
LocalOrImport::Local(<Self as LocalImport>::Local::new(self.index() - module.info.$imports.len()))
}
}
}
impl $local_ty {
pub fn convert_up(self, module: &ModuleInner) -> $ty {
$ty ((self.index() + module.$imports.len()) as u32)
$ty ((self.index() + module.info.$imports.len()) as u32)
}
}
@ -348,6 +376,7 @@ define_local_or_import![
(GlobalIndex | (LocalGlobalIndex, ImportedGlobalIndex): imported_globals),
];
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct SigIndex(u32);
impl TypedIndex for SigIndex {

View File

@ -7,6 +7,7 @@ const WASM_PAGE_SIZE: usize = 65_536;
const WASM_MAX_PAGES: usize = 65_536;
/// Units of WebAssembly pages (as specified to be 65,536 bytes).
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Pages(pub u32);
@ -32,6 +33,7 @@ impl fmt::Debug for Pages {
}
/// Units of WebAssembly memory in terms of 8-bit bytes.
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Bytes(pub usize);

View File

@ -423,7 +423,7 @@ mod vm_offset_tests {
#[cfg(test)]
mod vm_ctx_tests {
use super::{Ctx, ImportBacking, LocalBacking};
use crate::module::ModuleInner;
use crate::module::{ModuleInfo, ModuleInner, StringTable};
use crate::structures::Map;
use std::ffi::c_void;
@ -493,7 +493,7 @@ mod vm_ctx_tests {
fn generate_module() -> ModuleInner {
use super::Func;
use crate::backend::{FuncResolver, ProtectedCaller, SigRegistry, Token};
use crate::backend::{Backend, FuncResolver, ProtectedCaller, Token};
use crate::error::RuntimeResult;
use crate::types::{FuncIndex, LocalFuncIndex, Value};
use hashbrown::HashMap;
@ -525,25 +525,31 @@ mod vm_ctx_tests {
ModuleInner {
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(),
// These are strictly imported and the typesystem ensures that.
imported_functions: Map::new(),
imported_memories: Map::new(),
imported_tables: Map::new(),
imported_globals: Map::new(),
// These are strictly imported and the typesystem ensures that.
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(),
},
}
}
}