Merge branch 'master' into feature/dynasm-backend

This commit is contained in:
Brandon Fish
2019-03-12 20:58:22 -05:00
358 changed files with 13992 additions and 1805 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "wasmer-runtime"
version = "0.1.4"
version = "0.2.1"
description = "Wasmer runtime library"
license = "MIT"
authors = ["The Wasmer Engineering Team <engineering@wasmer.io>"]
@ -9,13 +9,33 @@ edition = "2018"
readme = "README.md"
[dependencies]
wasmer-runtime-core = { path = "../runtime-core", version = "0.1.2" }
wasmer-clif-backend = { path = "../clif-backend", version = "0.1.2", optional = true }
wasmer-dynasm-backend = { path = "../dynasm-backend", optional = true }
lazy_static = "1.2.0"
memmap = "0.7.0"
[dependencies.wasmer-runtime-core]
path = "../runtime-core"
version = "0.2.1"
[dependencies.wasmer-clif-backend]
path = "../clif-backend"
version = "0.2.0"
optional = true
[dev-dependencies]
tempfile = "3.0.7"
criterion = "0.2"
wabt = "0.7.4"
[target.'cfg(not(windows))'.dependencies.wasmer-llvm-backend]
path = "../llvm-backend"
[features]
default = ["default-compiler"]
default-compiler = ["wasmer-clif-backend", "wasmer-dynasm-backend"]
cache = ["default-compiler"]
debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"]
[[bench]]
name = "nginx"
harness = false

View File

@ -51,7 +51,7 @@ fn main() -> error::Result<()> {
// We're not importing anything, so make an empty import object.
let import_object = imports! {};
let mut instance = instantiate(WASM, import_object)?;
let mut instance = instantiate(WASM, &import_object)?;
let values = instance
.func("add_one")?

View File

@ -0,0 +1,53 @@
#[macro_use]
extern crate criterion;
use criterion::Criterion;
use tempfile::tempdir;
use wasmer_runtime::{
cache::{Cache, FileSystemCache, WasmHash},
compile, validate,
};
static NGINX_WASM: &'static [u8] = include_bytes!("../../../examples/nginx/nginx.wasm");
fn compile_module() {
compile(NGINX_WASM).unwrap();
}
fn load_module(hash: WasmHash, cache: &impl Cache) {
cache.load(hash).expect("could not load module");
}
fn hashing_benchmark(c: &mut Criterion) {
c.bench_function("nginx HASH", |b| b.iter(|| WasmHash::generate(NGINX_WASM)));
}
fn validate_benchmark(c: &mut Criterion) {
c.bench_function("nginx validate", |b| b.iter(|| validate(NGINX_WASM)));
}
fn compile_benchmark(c: &mut Criterion) {
c.bench_function("nginx compile", |b| b.iter(|| compile_module()));
}
fn load_benchmark(c: &mut Criterion) {
c.bench_function("nginx load", |b| {
let tempdir = tempdir().unwrap();
let mut cache = unsafe {
FileSystemCache::new(tempdir.path()).expect("unable to create file system cache")
};
let module = compile(NGINX_WASM).unwrap();
let wasm_hash = WasmHash::generate(NGINX_WASM);
cache
.store(wasm_hash, module)
.expect("unable to store into cache");
b.iter(|| load_module(wasm_hash, &cache))
});
}
criterion_group! {
name = benches;
config = Criterion::default().sample_size(10);
targets = validate_benchmark, hashing_benchmark, compile_benchmark, load_benchmark
}
criterion_main!(benches);

View File

@ -0,0 +1,58 @@
use wasmer_runtime::{compile, error, imports, Ctx, Func, Value};
use wabt::wat2wasm;
static WAT: &'static str = r#"
(module
(type (;0;) (func (result i32)))
(func $dbz (result i32)
i32.const 42
i32.const 0
i32.div_u
)
(export "dbz" (func $dbz))
)
"#;
// static WAT2: &'static str = r#"
// (module
// (type $t0 (func (param i32)))
// (type $t1 (func))
// (func $print_i32 (export "print_i32") (type $t0) (param $lhs i32))
// (func $print (export "print") (type $t1))
// (table $table (export "table") 10 20 anyfunc)
// (memory $memory (export "memory") 1 2)
// (global $global_i32 (export "global_i32") i32 (i32.const 666)))
// "#;
fn get_wasm() -> Vec<u8> {
wat2wasm(WAT).unwrap()
}
fn foobar(ctx: &mut Ctx) -> i32 {
42
}
fn main() -> Result<(), error::Error> {
let wasm = get_wasm();
let module = compile(&wasm)?;
// let import_module = compile(&wat2wasm(WAT2).unwrap())?;
// let import_instance = import_module.instantiate(&imports! {})?;
// let imports = imports! {
// "spectest" => import_instance,
// };
println!("instantiating");
let instance = module.instantiate(&imports! {})?;
let foo = instance.dyn_func("dbz")?;
let result = foo.call(&[]);
println!("result: {:?}", result);
Ok(())
}

View File

@ -1,128 +1,108 @@
use crate::Module;
use std::path::Path;
use wasmer_runtime_core::cache::{hash_data, Cache as CoreCache};
use memmap::Mmap;
use std::{
fs::{create_dir_all, File},
io::{self, Write},
path::PathBuf,
};
pub use wasmer_runtime_core::cache::Error;
use wasmer_runtime_core::cache::Error as CacheError;
pub use wasmer_runtime_core::cache::{Artifact, Cache, WasmHash};
/// On-disk storage of compiled WebAssembly.
/// Representation of a directory that contains compiled wasm artifacts.
///
/// A `Cache` can be used to quickly reload already
/// compiled WebAssembly from a previous execution
/// during which the wasm was explicitly compiled
/// as a `Cache`.
/// The `FileSystemCache` type implements the [`Cache`] trait, which allows it to be used
/// generically when some sort of cache is required.
///
/// [`Cache`]: trait.Cache.html
///
/// # Usage:
///
/// ```rust
/// use wasmer_runtime::cache::{Cache, FileSystemCache, WasmHash};
///
/// # use wasmer_runtime::{Module, error::CacheError};
/// fn store_module(module: Module) -> Result<Module, CacheError> {
/// // Create a new file system cache.
/// // This is unsafe because we can't ensure that the artifact wasn't
/// // corrupted or tampered with.
/// let mut fs_cache = unsafe { FileSystemCache::new("some/directory/goes/here")? };
/// // Compute a key for a given WebAssembly binary
/// let key = WasmHash::generate(&[]);
/// // Store a module into the cache given a key
/// fs_cache.store(key, module.clone())?;
/// Ok(module)
/// }
/// ```
/// use wasmer_runtime::{compile_cache, Cache};
///
/// # use wasmer_runtime::error::{CompileResult, CacheError};
/// # fn make_cache(wasm: &[u8]) -> CompileResult<()> {
/// // Make a cache.
/// let cache = compile_cache(wasm)?;
///
/// # Ok(())
/// # }
/// # fn usage_cache(cache: Cache) -> Result<(), CacheError> {
/// // Store the cache in a file.
/// cache.store("some_cache_file")?;
///
/// // Load the cache.
/// let cache = Cache::load("some_cache_file")?;
/// let module = unsafe { cache.into_module()? };
/// # Ok(())
/// # }
/// ```
///
/// # Performance Characteristics:
///
/// Loading caches from files has been optimized for latency.
/// There is still more work to do that will reduce
/// loading time, especially for very large modules,
/// but it will require signifigant internal work.
///
/// # Drawbacks:
///
/// Due to internal shortcomings, you cannot convert
/// a module into a `Cache`. This means that compiling
/// into a `Cache` and then converting into a module
/// has more overhead than directly compiling
/// into a [`Module`].
///
/// [`Module`]: struct.Module.html
pub struct Cache(pub(crate) CoreCache);
pub struct FileSystemCache {
path: PathBuf,
}
impl Cache {
/// Load a `Cache` from the file specified by `path`.
impl FileSystemCache {
/// Construct a new `FileSystemCache` around the specified directory.
///
/// # Usage:
///
/// ```
/// use wasmer_runtime::Cache;
/// # use wasmer_runtime::error::CacheError;
///
/// # fn load_cache() -> Result<(), CacheError> {
/// let cache = Cache::load("some_file.cache")?;
/// # Ok(())
/// # }
/// ```
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
CoreCache::open(path).map(|core_cache| Cache(core_cache))
}
/// # Note:
/// This method is unsafe because there's no way to ensure the artifacts
/// stored in this cache haven't been corrupted or tampered with.
pub unsafe fn new<P: Into<PathBuf>>(path: P) -> io::Result<Self> {
let path: PathBuf = path.into();
/// Convert a `Cache` into a [`Module`].
///
/// [`Module`]: struct.Module.html
///
/// # Usage:
///
/// ```
/// use wasmer_runtime::Cache;
///
/// # use wasmer_runtime::error::CacheError;
/// # fn cache2module(cache: Cache) -> Result<(), CacheError> {
/// let module = unsafe { cache.into_module()? };
/// # Ok(())
/// # }
/// ```
///
/// # Notes:
///
/// This method is unsafe because the runtime cannot confirm
/// that this cache was not tampered with or corrupted.
pub unsafe fn into_module(self) -> Result<Module, Error> {
let default_compiler = super::default_compiler();
wasmer_runtime_core::load_cache_with(self.0, default_compiler)
}
/// Compare the Sha256 hash of the wasm this cache was build
/// from with some other WebAssembly.
///
/// The main use-case for this is invalidating old caches.
pub fn compare_wasm(&self, wasm: &[u8]) -> bool {
let param_wasm_hash = hash_data(wasm);
self.0.wasm_hash() as &[u8] == &param_wasm_hash as &[u8]
}
/// Store this cache in a file.
///
/// # Notes:
///
/// If a file exists at the specified path, it will be overwritten.
///
/// # Usage:
///
/// ```
/// use wasmer_runtime::Cache;
///
/// # use wasmer_runtime::error::CacheError;
/// # fn store_cache(cache: Cache) -> Result<(), CacheError> {
/// cache.store("some_file.cache")?;
/// # Ok(())
/// # }
/// ```
pub fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
self.0.store(path)
if path.exists() {
let metadata = path.metadata()?;
if metadata.is_dir() {
if !metadata.permissions().readonly() {
Ok(Self { path })
} else {
// This directory is readonly.
Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("the supplied path is readonly: {}", path.display()),
))
}
} else {
// This path points to a file.
Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"the supplied path already points to a file: {}",
path.display()
),
))
}
} else {
// Create the directory and any parent directories if they don't yet exist.
create_dir_all(&path)?;
Ok(Self { path })
}
}
}
impl Cache for FileSystemCache {
type LoadError = CacheError;
type StoreError = CacheError;
fn load(&self, key: WasmHash) -> Result<Module, CacheError> {
let filename = key.encode();
let mut new_path_buf = self.path.clone();
new_path_buf.push(filename);
let file = File::open(new_path_buf)?;
let mmap = unsafe { Mmap::map(&file)? };
let serialized_cache = Artifact::deserialize(&mmap[..])?;
unsafe { wasmer_runtime_core::load_cache_with(serialized_cache, super::default_compiler()) }
}
fn store(&mut self, key: WasmHash, module: Module) -> Result<(), CacheError> {
let filename = key.encode();
let mut new_path_buf = self.path.clone();
new_path_buf.push(filename);
let serialized_cache = module.cache()?;
let buffer = serialized_cache.serialize()?;
let mut file = File::create(new_path_buf)?;
file.write_all(&buffer)?;
Ok(())
}
}

View File

@ -99,8 +99,7 @@ pub mod wasm {
}
pub mod error {
#[cfg(feature = "cache")]
pub use super::cache::Error as CacheError;
pub use wasmer_runtime_core::cache::Error as CacheError;
pub use wasmer_runtime_core::error::*;
}
@ -109,15 +108,10 @@ pub mod units {
pub use wasmer_runtime_core::units::{Bytes, Pages};
}
#[cfg(feature = "cache")]
mod cache;
pub mod cache;
#[cfg(feature = "default-compiler")]
use wasmer_runtime_core::backend::Compiler;
#[cfg(feature = "cache")]
pub use self::cache::Cache;
/// Compile WebAssembly binary code into a [`Module`].
/// This function is useful if it is necessary to
/// compile a module before it can be instantiated
@ -131,7 +125,6 @@ pub use self::cache::Cache;
/// binary code of the wasm module you want to compile.
/// # Errors:
/// If the operation fails, the function returns `Err(error::CompileError::...)`.
#[cfg(feature = "default-compiler")]
pub fn compile(wasm: &[u8]) -> error::CompileResult<Module> {
wasmer_runtime_core::compile_with(&wasm[..], default_compiler())
}
@ -154,44 +147,23 @@ pub fn compile(wasm: &[u8]) -> error::CompileResult<Module> {
/// `error::CompileError`, `error::LinkError`, or
/// `error::RuntimeError` (all combined into an `error::Error`),
/// depending on the cause of the failure.
#[cfg(feature = "default-compiler")]
pub fn instantiate(wasm: &[u8], import_object: &ImportObject) -> error::Result<Instance> {
let module = compile(wasm)?;
module.instantiate(import_object)
}
/// Compile wasm into a [`Cache`] that can be stored to a file or
/// converted into [`Module`].
///
/// [`Cache`]: struct.Cache.html
/// [`Module`]: struct.Module.html
///
/// # Usage:
///
/// ```
/// # use wasmer_runtime::error::CompileResult;
/// use wasmer_runtime::compile_cache;
///
/// # fn make_cache(wasm: &[u8]) -> CompileResult<()> {
/// let cache = compile_cache(wasm)?;
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "cache")]
pub fn compile_cache(wasm: &[u8]) -> error::CompileResult<Cache> {
let default_compiler = default_compiler();
wasmer_runtime_core::compile_to_cache_with(wasm, default_compiler)
.map(|core_cache| Cache(core_cache))
}
#[cfg(feature = "default-compiler")]
fn default_compiler() -> &'static dyn Compiler {
use lazy_static::lazy_static;
use wasmer_dynasm_backend::SinglePassCompiler;
#[cfg(feature = "llvm")]
use wasmer_llvm_backend::LLVMCompiler as DefaultCompiler;
#[cfg(not(feature = "llvm"))]
use wasmer_dynasm_backend::SinglePassCompiler as DefaultCompiler;
// use wasmer_clif_backend::CraneliftCompiler as DefaultCompiler; // TODO Fix default
lazy_static! {
static ref DEFAULT_COMPILER: SinglePassCompiler = { SinglePassCompiler {} };
static ref DEFAULT_COMPILER: DefaultCompiler = { DefaultCompiler::new() };
}
&*DEFAULT_COMPILER as &dyn Compiler