Merge remote-tracking branch 'origin/main' into pch/wasi_common_cap_std

This commit is contained in:
Pat Hickey
2021-01-29 12:38:13 -08:00
188 changed files with 12164 additions and 3298 deletions

View File

@@ -16,10 +16,13 @@ crate-type = ["rlib", "cdylib"]
[dependencies]
anyhow = "1.0"
shuffling-allocator = { version = "1.1.1", optional = true }
wasmtime = { path = "../wasmtime", default-features = false }
wasmtime-wasi = { path = "../wasi" }
wasi-common = { path = "../wasi-common" }
[dev-dependencies]
wat = "1.0"
[features]
default = ["shuffling-allocator"]

View File

@@ -21,10 +21,16 @@
//! # Example
//!
//! ```
//! use std::ptr;
//! use wasmtime_bench_api::*;
//!
//! let engine = unsafe { wasm_bench_create() };
//! assert!(!engine.is_null());
//! let working_dir = std::env::current_dir().unwrap().display().to_string();
//! let mut bench_api = ptr::null_mut();
//! unsafe {
//! let code = wasm_bench_create(working_dir.as_ptr(), working_dir.len(), &mut bench_api);
//! assert_eq!(code, OK);
//! assert!(!bench_api.is_null());
//! };
//!
//! let wasm = wat::parse_bytes(br#"
//! (module
@@ -42,7 +48,7 @@
//! "#).unwrap();
//!
//! // Start your compilation timer here.
//! let code = unsafe { wasm_bench_compile(engine, wasm.as_ptr(), wasm.len()) };
//! let code = unsafe { wasm_bench_compile(bench_api, wasm.as_ptr(), wasm.len()) };
//! // End your compilation timer here.
//! assert_eq!(code, OK);
//!
@@ -57,23 +63,25 @@
//! }
//!
//! // Start your instantiation timer here.
//! let code = unsafe { wasm_bench_instantiate(engine, bench_start, bench_stop) };
//! let code = unsafe { wasm_bench_instantiate(bench_api, bench_start, bench_stop) };
//! // End your instantiation timer here.
//! assert_eq!(code, OK);
//!
//! // No need to start timers for the execution since, by convention, the timer
//! // functions we passed during instantiation will be called by the benchmark
//! // at the appropriate time (before and after the benchmarked section).
//! let code = unsafe { wasm_bench_execute(engine) };
//! let code = unsafe { wasm_bench_execute(bench_api) };
//! assert_eq!(code, OK);
//!
//! unsafe {
//! wasm_bench_free(engine);
//! wasm_bench_free(bench_api);
//! }
//! ```
use anyhow::{anyhow, Context, Result};
use std::env;
use std::os::raw::{c_int, c_void};
use std::path::Path;
use std::slice;
use wasi_common::WasiCtxBuilder;
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};
@@ -83,15 +91,43 @@ pub type ExitCode = c_int;
pub const OK: ExitCode = 0;
pub const ERR: ExitCode = -1;
// Randomize the location of heap objects to avoid accidental locality being an
// uncontrolled variable that obscures performance evaluation in our
// experiments.
#[cfg(feature = "shuffling-allocator")]
#[global_allocator]
static ALLOC: shuffling_allocator::ShufflingAllocator<std::alloc::System> =
shuffling_allocator::wrap!(&std::alloc::System);
/// Exposes a C-compatible way of creating the engine from the bytes of a single
/// Wasm module.
///
/// This function returns a pointer to a structure that contains the engine's
/// initialized state.
/// On success, the `out_bench_ptr` is initialized to a pointer to a structure
/// that contains the engine's initialized state, and `0` is returned. On
/// failure, a non-zero status code is returned and `out_bench_ptr` is left
/// untouched.
#[no_mangle]
pub extern "C" fn wasm_bench_create() -> *mut c_void {
let state = Box::new(BenchState::new());
Box::into_raw(state) as _
pub extern "C" fn wasm_bench_create(
working_dir_ptr: *const u8,
working_dir_len: usize,
out_bench_ptr: *mut *mut c_void,
) -> ExitCode {
let result = (|| -> Result<_> {
let working_dir = unsafe { std::slice::from_raw_parts(working_dir_ptr, working_dir_len) };
let working_dir = std::str::from_utf8(working_dir)
.context("given working directory is not valid UTF-8")?;
let state = Box::new(BenchState::new(working_dir)?);
Ok(Box::into_raw(state) as _)
})();
if let Ok(bench_ptr) = result {
unsafe {
assert!(!out_bench_ptr.is_null());
*out_bench_ptr = bench_ptr;
}
}
to_exit_code(result.map(|_| ()))
}
/// Free the engine state allocated by this library.
@@ -155,27 +191,49 @@ fn to_exit_code<T>(result: impl Into<Result<T>>) -> ExitCode {
/// to manage the Wasmtime engine between calls.
struct BenchState {
engine: Engine,
store: Store,
linker: Linker,
module: Option<Module>,
instance: Option<Instance>,
did_execute: bool,
}
impl BenchState {
fn new() -> Self {
fn new(working_dir: impl AsRef<Path>) -> Result<Self> {
let mut config = Config::new();
config.wasm_simd(true);
// NB: do not configure a code cache.
let engine = Engine::new(&config);
let store = Store::new(&engine);
Self {
let mut linker = Linker::new(&store);
// Create a WASI environment.
let mut cx = WasiCtxBuilder::new();
cx.inherit_stdio();
// Allow access to the working directory so that the benchmark can read
// its input workload(s).
let working_dir = wasi_common::preopen_dir(working_dir)
.context("failed to preopen the working directory")?;
cx.preopened_dir(working_dir, ".");
// Pass this env var along so that the benchmark program can use smaller
// input workload(s) if it has them and that has been requested.
if let Ok(val) = env::var("WASM_BENCH_USE_SMALL_WORKLOAD") {
cx.env("WASM_BENCH_USE_SMALL_WORKLOAD", &val);
}
let cx = cx.build()?;
let wasi = Wasi::new(&store, cx);
wasi.add_to_linker(&mut linker)?;
Ok(Self {
engine,
store,
linker,
module: None,
instance: None,
did_execute: false,
}
})
}
fn compile(&mut self, bytes: &[u8]) -> Result<()> {
@@ -201,20 +259,11 @@ impl BenchState {
.as_mut()
.expect("compile the module before instantiating it");
let mut linker = Linker::new(&self.store);
// Import a very restricted WASI environment.
let mut cx = WasiCtxBuilder::new();
cx.inherit_stdio();
let cx = cx.build()?;
let wasi = Wasi::new(linker.store(), cx);
wasi.add_to_linker(&mut linker)?;
// Import the specialized benchmarking functions.
linker.func("bench", "start", move || bench_start())?;
linker.func("bench", "end", move || bench_end())?;
self.linker.func("bench", "start", move || bench_start())?;
self.linker.func("bench", "end", move || bench_end())?;
self.instance = Some(linker.instantiate(&module)?);
self.instance = Some(self.linker.instantiate(&module)?);
Ok(())
}

View File

@@ -316,6 +316,12 @@
*
* \fn wasm_name_new_new_uninitialized
* \brief Convenience alias
*
* \fn wasm_name_new_from_string
* \brief Create a new name from a C string.
*
* \fn wasm_name_new_from_string_nt
* \brief Create a new name from a C string with null terminator.
*
* \fn wasm_name_copy
* \brief Convenience alias
@@ -407,7 +413,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_valtype_t* wasm_valtype_copy(wasm_valtype_t *)
* \fn own wasm_valtype_t* wasm_valtype_copy(const wasm_valtype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -477,7 +483,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_functype_t* wasm_functype_copy(wasm_functype_t *)
* \fn own wasm_functype_t* wasm_functype_copy(const wasm_functype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -548,7 +554,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_globaltype_t* wasm_globaltype_copy(wasm_globaltype_t *)
* \fn own wasm_globaltype_t* wasm_globaltype_copy(const wasm_globaltype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -625,7 +631,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_tabletype_t* wasm_tabletype_copy(wasm_tabletype_t *)
* \fn own wasm_tabletype_t* wasm_tabletype_copy(const wasm_tabletype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -711,7 +717,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_memorytype_t* wasm_memorytype_copy(wasm_memorytype_t *)
* \fn own wasm_memorytype_t* wasm_memorytype_copy(const wasm_memorytype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -780,7 +786,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_externtype_t* wasm_externtype_copy(wasm_externtype_t *)
* \fn own wasm_externtype_t* wasm_externtype_copy(const wasm_externtype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -957,7 +963,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_importtype_t* wasm_importtype_copy(wasm_importtype_t *)
* \fn own wasm_importtype_t* wasm_importtype_copy(const wasm_importtype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -1038,7 +1044,7 @@
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_exporttype_t* wasm_exporttype_copy(wasm_exporttype_t *)
* \fn own wasm_exporttype_t* wasm_exporttype_copy(const wasm_exporttype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -1520,8 +1526,6 @@
* and types of results as the original type signature. It is undefined behavior
* to return other types or different numbers of values.
*
* This function takes ownership of all of the parameters given. It's expected
* that the caller will invoke `wasm_val_delete` for each one provided.
* Ownership of the results and the trap returned, if any, is passed to the
* caller of this function.
*
@@ -1608,7 +1612,7 @@
* \fn size_t wasm_func_result_arity(const wasm_func_t *);
* \brief Returns the number of results returned by this function.
*
* \fn own wasm_trap_t *wasm_func_call(const wasm_func_t *, const wasm_val_t args[], const wasm_val_t results[]);
* \fn own wasm_trap_t *wasm_func_call(const wasm_func_t *, const wasm_val_vec_t *args, wasm_val_vec_t *results);
* \brief Calls the provided function with the arguments given.
*
* This function is used to call WebAssembly from the host. The parameter array
@@ -2164,7 +2168,7 @@
* \fn wasm_ref_as_instance_const(const wasm_ref_t *);
* \brief Unimplemented in Wasmtime, aborts the process if called.
*
* \fn own wasm_instance_t *wasm_instance_new(wasm_store_t *, const wasm_module_t *, const wasm_extern_t *const[], wasm_trap_t **);
* \fn own wasm_instance_t *wasm_instance_new(wasm_store_t *, const wasm_module_t *, const wasm_extern_vec_t *, wasm_trap_t **);
* \brief Instantiates a module with the provided imports.
*
* This function will instantiate the provided #wasm_module_t into the provided
@@ -2194,3 +2198,29 @@
* the same length as #wasm_module_exports called on the original module. Each
* element is 1:1 matched with the elements in the list of #wasm_module_exports.
*/
/**
* \def WASM_EMPTY_VEC
* \brief Used to initialize an empty vector type.
*
* \def WASM_ARRAY_VEC
* \brief Used to initialize a vector type from a C array.
*
* \def WASM_I32_VAL
* \brief Used to initialize a 32-bit integer wasm_val_t value.
*
* \def WASM_I64_VAL
* \brief Used to initialize a 64-bit integer wasm_val_t value.
*
* \def WASM_F32_VAL
* \brief Used to initialize a 32-bit floating point wasm_val_t value.
*
* \def WASM_F64_VAL
* \brief Used to initialize a 64-bit floating point wasm_val_t value.
*
* \def WASM_REF_VAL
* \brief Used to initialize an externref wasm_val_t value.
*
* \def WASM_INIT_VAL
* \brief Used to initialize a null externref wasm_val_t value.
*/

View File

@@ -274,6 +274,14 @@ WASMTIME_CONFIG_PROP(void, static_memory_guard_size, uint64_t)
*/
WASMTIME_CONFIG_PROP(void, dynamic_memory_guard_size, uint64_t)
/**
* \brief Configures the maximum number of instances that can be created.
*
* For more information see the Rust documentation at
* https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.max_instances.
*/
WASMTIME_CONFIG_PROP(void, max_instances, size_t)
/**
* \brief Enables Wasmtime's cache and loads configuration from the specified
* path.
@@ -527,7 +535,7 @@ typedef struct wasmtime_caller_t wasmtime_caller_t;
* argument is a #wasmtime_caller_t which allows learning information about the
* caller.
*/
typedef own wasm_trap_t* (*wasmtime_func_callback_t)(const wasmtime_caller_t* caller, const wasm_val_t args[], wasm_val_t results[]);
typedef own wasm_trap_t* (*wasmtime_func_callback_t)(const wasmtime_caller_t* caller, const wasm_val_vec_t *args, wasm_val_vec_t *results);
/**
* \brief Callback signature for #wasmtime_func_new_with_env.
@@ -536,7 +544,7 @@ typedef own wasm_trap_t* (*wasmtime_func_callback_t)(const wasmtime_caller_t* ca
* first argument is a #wasmtime_caller_t which allows learning information
* about the caller.
*/
typedef own wasm_trap_t* (*wasmtime_func_callback_with_env_t)(const wasmtime_caller_t* caller, void* env, const wasm_val_t args[], wasm_val_t results[]);
typedef own wasm_trap_t* (*wasmtime_func_callback_with_env_t)(const wasmtime_caller_t* caller, void* env, const wasm_val_vec_t *args, wasm_val_vec_t *results);
/**
* \brief Creates a new host-defined function.
@@ -671,7 +679,6 @@ WASM_API_EXTERN const wasm_name_t *wasmtime_frame_module_name(const wasm_frame_t
*
* This function is similar to #wasm_func_call, but with a few tweaks:
*
* * `args` and `results` have a size parameter saying how big the arrays are
* * An error *and* a trap can be returned
* * Errors are returned if `args` have the wrong types, if the args/results
* arrays have the wrong lengths, or if values come from the wrong store.
@@ -697,10 +704,8 @@ WASM_API_EXTERN const wasm_name_t *wasmtime_frame_module_name(const wasm_frame_t
*/
WASM_API_EXTERN own wasmtime_error_t *wasmtime_func_call(
wasm_func_t *func,
const wasm_val_t *args,
size_t num_args,
wasm_val_t *results,
size_t num_results,
const wasm_val_vec_t *args,
wasm_val_vec_t *results,
own wasm_trap_t **trap
);
@@ -741,7 +746,6 @@ WASM_API_EXTERN own wasmtime_error_t *wasmtime_global_set(
* This function is similar to #wasm_instance_new, but with a few tweaks:
*
* * An error message can be returned from this function.
* * The number of imports specified is passed as an argument
* * The `trap` pointer is required to not be NULL.
*
* The states of return values from this function are similar to
@@ -759,8 +763,7 @@ WASM_API_EXTERN own wasmtime_error_t *wasmtime_global_set(
WASM_API_EXTERN own wasmtime_error_t *wasmtime_instance_new(
wasm_store_t *store,
const wasm_module_t *module,
const wasm_extern_t* const imports[],
size_t num_imports,
const wasm_extern_vec_t* imports,
own wasm_instance_t **instance,
own wasm_trap_t **trap
);
@@ -1016,7 +1019,7 @@ WASM_API_EXTERN own wasmtime_error_t *wasmtime_module_deserialize(
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_instancetype_t* wasm_instancetype_copy(wasm_instancetype_t *)
* \fn own wasm_instancetype_t* wasm_instancetype_copy(const wasm_instancetype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
@@ -1113,7 +1116,7 @@ WASM_API_EXTERN const wasm_instancetype_t* wasm_externtype_as_instancetype_const
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_moduletype_t* wasm_moduletype_copy(wasm_moduletype_t *)
* \fn own wasm_moduletype_t* wasm_moduletype_copy(const wasm_moduletype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.

View File

@@ -171,3 +171,8 @@ pub extern "C" fn wasmtime_config_static_memory_guard_size_set(c: &mut wasm_conf
pub extern "C" fn wasmtime_config_dynamic_memory_guard_size_set(c: &mut wasm_config_t, size: u64) {
c.config.dynamic_memory_guard_size(size);
}
#[no_mangle]
pub extern "C" fn wasmtime_config_max_instances_set(c: &mut wasm_config_t, limit: usize) {
c.config.max_instances(limit);
}

View File

@@ -1,4 +1,4 @@
use crate::{wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t};
use crate::{wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t};
use crate::{wasm_name_t, wasm_trap_t, wasmtime_error_t};
use anyhow::anyhow;
use std::ffi::c_void;
@@ -21,26 +21,28 @@ pub struct wasmtime_caller_t<'a> {
caller: Caller<'a>,
}
pub type wasm_func_callback_t =
extern "C" fn(args: *const wasm_val_t, results: *mut wasm_val_t) -> Option<Box<wasm_trap_t>>;
pub type wasm_func_callback_t = extern "C" fn(
args: *const wasm_val_vec_t,
results: *mut wasm_val_vec_t,
) -> Option<Box<wasm_trap_t>>;
pub type wasm_func_callback_with_env_t = extern "C" fn(
env: *mut std::ffi::c_void,
args: *const wasm_val_t,
results: *mut wasm_val_t,
args: *const wasm_val_vec_t,
results: *mut wasm_val_vec_t,
) -> Option<Box<wasm_trap_t>>;
pub type wasmtime_func_callback_t = extern "C" fn(
caller: *const wasmtime_caller_t,
args: *const wasm_val_t,
results: *mut wasm_val_t,
args: *const wasm_val_vec_t,
results: *mut wasm_val_vec_t,
) -> Option<Box<wasm_trap_t>>;
pub type wasmtime_func_callback_with_env_t = extern "C" fn(
caller: *const wasmtime_caller_t,
env: *mut std::ffi::c_void,
args: *const wasm_val_t,
results: *mut wasm_val_t,
args: *const wasm_val_vec_t,
results: *mut wasm_val_vec_t,
) -> Option<Box<wasm_trap_t>>;
struct Finalizer {
@@ -83,21 +85,25 @@ impl From<Func> for wasm_func_t {
fn create_function(
store: &wasm_store_t,
ty: &wasm_functype_t,
func: impl Fn(Caller<'_>, *const wasm_val_t, *mut wasm_val_t) -> Option<Box<wasm_trap_t>> + 'static,
func: impl Fn(Caller<'_>, *const wasm_val_vec_t, *mut wasm_val_vec_t) -> Option<Box<wasm_trap_t>>
+ 'static,
) -> Box<wasm_func_t> {
let store = &store.store;
let ty = ty.ty().ty.clone();
let func = Func::new(store, ty, move |caller, params, results| {
let params = params
let params: wasm_val_vec_t = params
.iter()
.cloned()
.map(|p| wasm_val_t::from_val(p))
.collect::<Vec<_>>();
let mut out_results = vec![wasm_val_t::default(); results.len()];
let out = func(caller, params.as_ptr(), out_results.as_mut_ptr());
.collect::<Vec<_>>()
.into();
let mut out_results: wasm_val_vec_t = vec![wasm_val_t::default(); results.len()].into();
let out = func(caller, &params, &mut out_results);
if let Some(trap) = out {
return Err(trap.trap.clone());
}
let out_results = out_results.as_slice();
for i in 0..results.len() {
results[i] = out_results[i].val();
}
@@ -164,17 +170,14 @@ pub extern "C" fn wasmtime_func_new_with_env(
#[no_mangle]
pub unsafe extern "C" fn wasm_func_call(
wasm_func: &wasm_func_t,
args: *const wasm_val_t,
results: *mut MaybeUninit<wasm_val_t>,
args: *const wasm_val_vec_t,
results: *mut wasm_val_vec_t,
) -> *mut wasm_trap_t {
let func = wasm_func.func();
let mut trap = ptr::null_mut();
let error = wasmtime_func_call(
let error = _wasmtime_func_call(
wasm_func,
args,
func.param_arity(),
results,
func.result_arity(),
(*args).as_slice(),
(*results).as_uninit_slice(),
&mut trap,
);
match error {
@@ -186,16 +189,14 @@ pub unsafe extern "C" fn wasm_func_call(
#[no_mangle]
pub unsafe extern "C" fn wasmtime_func_call(
func: &wasm_func_t,
args: *const wasm_val_t,
num_args: usize,
results: *mut MaybeUninit<wasm_val_t>,
num_results: usize,
args: *const wasm_val_vec_t,
results: *mut wasm_val_vec_t,
trap_ptr: &mut *mut wasm_trap_t,
) -> Option<Box<wasmtime_error_t>> {
_wasmtime_func_call(
func,
std::slice::from_raw_parts(args, num_args),
std::slice::from_raw_parts_mut(results, num_results),
(*args).as_slice(),
(*results).as_uninit_slice(),
trap_ptr,
)
}

View File

@@ -40,16 +40,15 @@ impl wasm_instance_t {
pub unsafe extern "C" fn wasm_instance_new(
store: &wasm_store_t,
wasm_module: &wasm_module_t,
imports: *const Box<wasm_extern_t>,
imports: *const wasm_extern_vec_t,
result: Option<&mut *mut wasm_trap_t>,
) -> Option<Box<wasm_instance_t>> {
let mut instance = ptr::null_mut();
let mut trap = ptr::null_mut();
let err = wasmtime_instance_new(
let err = _wasmtime_instance_new(
store,
wasm_module,
imports,
wasm_module.module().imports().len(),
(*imports).as_slice(),
&mut instance,
&mut trap,
);
@@ -83,31 +82,27 @@ pub unsafe extern "C" fn wasm_instance_new(
pub unsafe extern "C" fn wasmtime_instance_new(
store: &wasm_store_t,
module: &wasm_module_t,
imports: *const Box<wasm_extern_t>,
num_imports: usize,
imports: *const wasm_extern_vec_t,
instance_ptr: &mut *mut wasm_instance_t,
trap_ptr: &mut *mut wasm_trap_t,
) -> Option<Box<wasmtime_error_t>> {
_wasmtime_instance_new(
store,
module,
std::slice::from_raw_parts(imports, num_imports),
instance_ptr,
trap_ptr,
)
_wasmtime_instance_new(store, module, (*imports).as_slice(), instance_ptr, trap_ptr)
}
fn _wasmtime_instance_new(
store: &wasm_store_t,
module: &wasm_module_t,
imports: &[Box<wasm_extern_t>],
imports: &[Option<Box<wasm_extern_t>>],
instance_ptr: &mut *mut wasm_instance_t,
trap_ptr: &mut *mut wasm_trap_t,
) -> Option<Box<wasmtime_error_t>> {
let store = &store.store;
let imports = imports
.iter()
.map(|import| import.which.clone())
.filter_map(|import| match import {
Some(i) => Some(i.which.clone()),
None => None,
})
.collect::<Vec<_>>();
handle_instantiate(
Instance::new(store, module.module(), &imports),

View File

@@ -4,6 +4,7 @@ use crate::{
wasm_moduletype_t, wasm_tabletype_t, wasm_val_t, wasm_valtype_t,
};
use std::mem;
use std::mem::MaybeUninit;
use std::ptr;
use std::slice;
@@ -54,6 +55,18 @@ macro_rules! declare_vecs {
}
}
pub fn as_uninit_slice(&mut self) -> &mut [MaybeUninit<$elem_ty>] {
// Note that we're careful to not create a slice with a null
// pointer as the data pointer, since that isn't defined
// behavior in Rust.
if self.size == 0 {
&mut []
} else {
assert!(!self.data.is_null());
unsafe { slice::from_raw_parts_mut(self.data as _, self.size) }
}
}
pub fn take(&mut self) -> Vec<$elem_ty> {
if self.data.is_null() {
return Vec::new();

View File

@@ -18,7 +18,7 @@ log = { version = "0.4.8", default-features = false }
serde = { version = "1.0.94", features = ["derive"] }
sha2 = "0.9.0"
toml = "0.5.5"
zstd = "0.5"
zstd = { version = "0.6", default-features = false }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = "0.3.7"

View File

@@ -17,3 +17,4 @@ cranelift-wasm = { path = "../../cranelift/wasm", version = "0.69.0" }
cranelift-codegen = { path = "../../cranelift/codegen", version = "0.69.0" }
cranelift-frontend = { path = "../../cranelift/frontend", version = "0.69.0" }
cranelift-entity = { path = "../../cranelift/entity", version = "0.69.0" }
wasmparser = "0.73.0"

View File

@@ -7,11 +7,14 @@ use cranelift_codegen::ir::{AbiParam, ArgumentPurpose, Function, InstBuilder, Si
use cranelift_codegen::isa::{self, TargetFrontendConfig};
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_frontend::FunctionBuilder;
use cranelift_frontend::Variable;
use cranelift_wasm::{
self, FuncIndex, GlobalIndex, GlobalVariable, MemoryIndex, SignatureIndex, TableIndex,
TargetEnvironment, TypeIndex, WasmError, WasmResult, WasmType,
self, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, MemoryIndex,
SignatureIndex, TableIndex, TargetEnvironment, TypeIndex, WasmError, WasmResult, WasmType,
};
use std::convert::TryFrom;
use std::mem;
use wasmparser::Operator;
use wasmtime_environ::{
BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, TableStyle, Tunables, VMOffsets,
INTERRUPTED, WASM_PAGE_SIZE,
@@ -125,6 +128,20 @@ pub struct FuncEnvironment<'module_environment> {
pub(crate) offsets: VMOffsets,
tunables: &'module_environment Tunables,
/// A function-local variable which stores the cached value of the amount of
/// fuel remaining to execute. If used this is modified frequently so it's
/// stored locally as a variable instead of always referenced from the field
/// in `*const VMInterrupts`
fuel_var: cranelift_frontend::Variable,
/// A function-local variable which caches the value of `*const
/// VMInterrupts` for this function's vmctx argument. This pointer is stored
/// in the vmctx itself, but never changes for the lifetime of the function,
/// so if we load it up front we can continue to use it throughout.
vminterrupts_ptr: cranelift_frontend::Variable,
fuel_consumed: i64,
}
impl<'module_environment> FuncEnvironment<'module_environment> {
@@ -151,6 +168,12 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
builtin_function_signatures,
offsets: VMOffsets::new(target_config.pointer_bytes(), module),
tunables,
fuel_var: Variable::new(0),
vminterrupts_ptr: Variable::new(0),
// Start with at least one fuel being consumed because even empty
// functions should consume at least some fuel.
fuel_consumed: 1,
}
}
@@ -418,6 +441,241 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
(global, 0)
}
}
fn declare_vminterrupts_ptr(&mut self, builder: &mut FunctionBuilder<'_>) {
// We load the `*const VMInterrupts` value stored within vmctx at the
// head of the function and reuse the same value across the entire
// function. This is possible since we know that the pointer never
// changes for the lifetime of the function.
let pointer_type = self.pointer_type();
builder.declare_var(self.vminterrupts_ptr, pointer_type);
let vmctx = self.vmctx(builder.func);
let base = builder.ins().global_value(pointer_type, vmctx);
let offset = i32::try_from(self.offsets.vmctx_interrupts()).unwrap();
let interrupt_ptr = builder
.ins()
.load(pointer_type, ir::MemFlags::trusted(), base, offset);
builder.def_var(self.vminterrupts_ptr, interrupt_ptr);
}
fn fuel_function_entry(&mut self, builder: &mut FunctionBuilder<'_>) {
// On function entry we load the amount of fuel into a function-local
// `self.fuel_var` to make fuel modifications fast locally. This cache
// is then periodically flushed to the Store-defined location in
// `VMInterrupts` later.
builder.declare_var(self.fuel_var, ir::types::I64);
self.fuel_load_into_var(builder);
self.fuel_check(builder);
}
fn fuel_function_exit(&mut self, builder: &mut FunctionBuilder<'_>) {
// On exiting the function we need to be sure to save the fuel we have
// cached locally in `self.fuel_var` back into the Store-defined
// location.
self.fuel_save_from_var(builder);
}
fn fuel_before_op(
&mut self,
op: &Operator<'_>,
builder: &mut FunctionBuilder<'_>,
reachable: bool,
) {
if !reachable {
// In unreachable code we shouldn't have any leftover fuel we
// haven't accounted for since the reason for us to become
// unreachable should have already added it to `self.fuel_var`.
debug_assert_eq!(self.fuel_consumed, 0);
return;
}
self.fuel_consumed += match op {
// Nop and drop generate no code, so don't consume fuel for them.
Operator::Nop | Operator::Drop => 0,
// Control flow may create branches, but is generally cheap and
// free, so don't consume fuel. Note the lack of `if` since some
// cost is incurred with the conditional check.
Operator::Block { .. }
| Operator::Loop { .. }
| Operator::Unreachable
| Operator::Return
| Operator::Else
| Operator::End => 0,
// everything else, just call it one operation.
_ => 1,
};
match op {
// Exiting a function (via a return or unreachable) or otherwise
// entering a different function (via a call) means that we need to
// update the fuel consumption in `VMInterrupts` because we're
// about to move control out of this function itself and the fuel
// may need to be read.
//
// Before this we need to update the fuel counter from our own cost
// leading up to this function call, and then we can store
// `self.fuel_var` into `VMInterrupts`.
Operator::Unreachable
| Operator::Return
| Operator::CallIndirect { .. }
| Operator::Call { .. }
| Operator::ReturnCall { .. }
| Operator::ReturnCallIndirect { .. } => {
self.fuel_increment_var(builder);
self.fuel_save_from_var(builder);
}
// To ensure all code preceding a loop is only counted once we
// update the fuel variable on entry.
Operator::Loop { .. }
// Entering into an `if` block means that the edge we take isn't
// known until runtime, so we need to update our fuel consumption
// before we take the branch.
| Operator::If { .. }
// Control-flow instructions mean that we're moving to the end/exit
// of a block somewhere else. That means we need to update the fuel
// counter since we're effectively terminating our basic block.
| Operator::Br { .. }
| Operator::BrIf { .. }
| Operator::BrTable { .. }
// Exiting a scope means that we need to update the fuel
// consumption because there are multiple ways to exit a scope and
// this is the only time we have to account for instructions
// executed so far.
| Operator::End
// This is similar to `end`, except that it's only the terminator
// for an `if` block. The same reasoning applies though in that we
// are terminating a basic block and need to update the fuel
// variable.
| Operator::Else => self.fuel_increment_var(builder),
// This is a normal instruction where the fuel is buffered to later
// get added to `self.fuel_var`.
//
// Note that we generally ignore instructions which may trap and
// therefore result in exiting a block early. Current usage of fuel
// means that it's not too important to account for a precise amount
// of fuel consumed but rather "close to the actual amount" is good
// enough. For 100% precise counting, however, we'd probably need to
// not only increment but also save the fuel amount more often
// around trapping instructions. (see the `unreachable` instruction
// case above)
//
// Note that `Block` is specifically omitted from incrementing the
// fuel variable. Control flow entering a `block` is unconditional
// which means it's effectively executing straight-line code. We'll
// update the counter when exiting a block, but we shouldn't need to
// do so upon entering a block.
_ => {}
}
}
fn fuel_after_op(&mut self, op: &Operator<'_>, builder: &mut FunctionBuilder<'_>) {
// After a function call we need to reload our fuel value since the
// function may have changed it.
match op {
Operator::Call { .. } | Operator::CallIndirect { .. } => {
self.fuel_load_into_var(builder);
}
_ => {}
}
}
/// Adds `self.fuel_consumed` to the `fuel_var`, zero-ing out the amount of
/// fuel consumed at that point.
fn fuel_increment_var(&mut self, builder: &mut FunctionBuilder<'_>) {
let consumption = mem::replace(&mut self.fuel_consumed, 0);
if consumption == 0 {
return;
}
let fuel = builder.use_var(self.fuel_var);
let fuel = builder.ins().iadd_imm(fuel, consumption);
builder.def_var(self.fuel_var, fuel);
}
/// Loads the fuel consumption value from `VMInterrupts` into `self.fuel_var`
fn fuel_load_into_var(&mut self, builder: &mut FunctionBuilder<'_>) {
let (addr, offset) = self.fuel_addr_offset(builder);
let fuel = builder
.ins()
.load(ir::types::I64, ir::MemFlags::trusted(), addr, offset);
builder.def_var(self.fuel_var, fuel);
}
/// Stores the fuel consumption value from `self.fuel_var` into
/// `VMInterrupts`.
fn fuel_save_from_var(&mut self, builder: &mut FunctionBuilder<'_>) {
let (addr, offset) = self.fuel_addr_offset(builder);
let fuel_consumed = builder.use_var(self.fuel_var);
builder
.ins()
.store(ir::MemFlags::trusted(), fuel_consumed, addr, offset);
}
/// Returns the `(address, offset)` of the fuel consumption within
/// `VMInterrupts`, used to perform loads/stores later.
fn fuel_addr_offset(
&mut self,
builder: &mut FunctionBuilder<'_>,
) -> (ir::Value, ir::immediates::Offset32) {
(
builder.use_var(self.vminterrupts_ptr),
i32::from(self.offsets.vminterrupts_fuel_consumed()).into(),
)
}
/// Checks the amount of remaining, and if we've run out of fuel we call
/// the out-of-fuel function.
fn fuel_check(&mut self, builder: &mut FunctionBuilder) {
self.fuel_increment_var(builder);
let out_of_gas_block = builder.create_block();
let continuation_block = builder.create_block();
// Note that our fuel is encoded as adding positive values to a
// negative number. Whenever the negative number goes positive that
// means we ran out of fuel.
//
// Compare to see if our fuel is positive, and if so we ran out of gas.
// Otherwise we can continue on like usual.
let zero = builder.ins().iconst(ir::types::I64, 0);
let fuel = builder.use_var(self.fuel_var);
let cmp = builder.ins().ifcmp(fuel, zero);
builder
.ins()
.brif(IntCC::SignedGreaterThanOrEqual, cmp, out_of_gas_block, &[]);
builder.ins().jump(continuation_block, &[]);
builder.seal_block(out_of_gas_block);
// If we ran out of gas then we call our out-of-gas intrinsic and it
// figures out what to do. Note that this may raise a trap, or do
// something like yield to an async runtime. In either case we don't
// assume what happens and handle the case the intrinsic returns.
//
// Note that we save/reload fuel around this since the out-of-gas
// intrinsic may alter how much fuel is in the system.
builder.switch_to_block(out_of_gas_block);
self.fuel_save_from_var(builder);
let out_of_gas_sig = self.builtin_function_signatures.out_of_gas(builder.func);
let (vmctx, out_of_gas) = self.translate_load_builtin_function_address(
&mut builder.cursor(),
BuiltinFunctionIndex::out_of_gas(),
);
builder
.ins()
.call_indirect(out_of_gas_sig, out_of_gas, &[vmctx]);
self.fuel_load_into_var(builder);
builder.ins().jump(continuation_block, &[]);
builder.seal_block(continuation_block);
builder.switch_to_block(continuation_block);
}
}
impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environment> {
@@ -437,6 +695,11 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
index >= 2
}
fn after_locals(&mut self, num_locals: usize) {
self.vminterrupts_ptr = Variable::new(num_locals);
self.fuel_var = Variable::new(num_locals + 1);
}
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> WasmResult<ir::Table> {
let pointer_type = self.pointer_type();
@@ -1482,36 +1745,90 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
Ok(*pos.func.dfg.inst_results(call_inst).first().unwrap())
}
fn translate_loop_header(&mut self, mut pos: FuncCursor) -> WasmResult<()> {
if !self.tunables.interruptable {
return Ok(());
}
// Start out each loop with a check to the interupt flag to allow
// interruption of long or infinite loops.
fn translate_loop_header(&mut self, builder: &mut FunctionBuilder) -> WasmResult<()> {
// If enabled check the interrupt flag to prevent long or infinite
// loops.
//
// For more information about this see comments in
// `crates/environ/src/cranelift.rs`
let vmctx = self.vmctx(&mut pos.func);
let pointer_type = self.pointer_type();
let base = pos.ins().global_value(pointer_type, vmctx);
let offset = i32::try_from(self.offsets.vmctx_interrupts()).unwrap();
let interrupt_ptr = pos
.ins()
.load(pointer_type, ir::MemFlags::trusted(), base, offset);
let interrupt = pos.ins().load(
pointer_type,
ir::MemFlags::trusted(),
interrupt_ptr,
i32::from(self.offsets.vminterrupts_stack_limit()),
);
// Note that the cast to `isize` happens first to allow sign-extension,
// if necessary, to `i64`.
let interrupted_sentinel = pos.ins().iconst(pointer_type, INTERRUPTED as isize as i64);
let cmp = pos
.ins()
.icmp(IntCC::Equal, interrupt, interrupted_sentinel);
pos.ins().trapnz(cmp, ir::TrapCode::Interrupt);
if self.tunables.interruptable {
let pointer_type = self.pointer_type();
let interrupt_ptr = builder.use_var(self.vminterrupts_ptr);
let interrupt = builder.ins().load(
pointer_type,
ir::MemFlags::trusted(),
interrupt_ptr,
i32::from(self.offsets.vminterrupts_stack_limit()),
);
// Note that the cast to `isize` happens first to allow sign-extension,
// if necessary, to `i64`.
let interrupted_sentinel = builder
.ins()
.iconst(pointer_type, INTERRUPTED as isize as i64);
let cmp = builder
.ins()
.icmp(IntCC::Equal, interrupt, interrupted_sentinel);
builder.ins().trapnz(cmp, ir::TrapCode::Interrupt);
}
// Additionally if enabled check how much fuel we have remaining to see
// if we've run out by this point.
if self.tunables.consume_fuel {
self.fuel_check(builder);
}
Ok(())
}
fn before_translate_operator(
&mut self,
op: &Operator,
builder: &mut FunctionBuilder,
state: &FuncTranslationState,
) -> WasmResult<()> {
if self.tunables.consume_fuel {
self.fuel_before_op(op, builder, state.reachable());
}
Ok(())
}
fn after_translate_operator(
&mut self,
op: &Operator,
builder: &mut FunctionBuilder,
state: &FuncTranslationState,
) -> WasmResult<()> {
if self.tunables.consume_fuel && state.reachable() {
self.fuel_after_op(op, builder);
}
Ok(())
}
fn before_translate_function(
&mut self,
builder: &mut FunctionBuilder,
_state: &FuncTranslationState,
) -> WasmResult<()> {
// If the `vminterrupts_ptr` variable will get used then we initialize
// it here.
if self.tunables.consume_fuel || self.tunables.interruptable {
self.declare_vminterrupts_ptr(builder);
}
// Additionally we initialize `fuel_var` if it will get used.
if self.tunables.consume_fuel {
self.fuel_function_entry(builder);
}
Ok(())
}
fn after_translate_function(
&mut self,
builder: &mut FunctionBuilder,
state: &FuncTranslationState,
) -> WasmResult<()> {
if self.tunables.consume_fuel && state.reachable() {
self.fuel_function_exit(builder);
}
Ok(())
}
}

View File

@@ -13,8 +13,8 @@ edition = "2018"
[dependencies]
gimli = "0.23.0"
wasmparser = "0.71"
object = { version = "0.22.0", default-features = false, features = ["read_core", "elf", "write"] }
wasmparser = "0.73"
object = { version = "0.23.0", default-features = false, features = ["read_core", "elf", "write"] }
wasmtime-environ = { path = "../environ", version = "0.22.0" }
target-lexicon = { version = "0.11.0", default-features = false }
anyhow = "1.0"

View File

@@ -7,7 +7,7 @@ use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::ir::{StackSlots, ValueLabel, ValueLabelsRanges, ValueLoc};
use wasmtime_environ::ir::{LabelValueLoc, StackSlots, ValueLabel, ValueLabelsRanges, ValueLoc};
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::{get_vmctx_value_label, DefinedFuncIndex};
use wasmtime_environ::ModuleMemoryOffset;
@@ -131,27 +131,24 @@ impl CompiledExpression {
const X86_64_STACK_OFFSET: i64 = 16;
fn translate_loc(
loc: ValueLoc,
loc: LabelValueLoc,
frame_info: Option<&FunctionFrameInfo>,
isa: &dyn TargetIsa,
add_stack_value: bool,
) -> Result<Option<Vec<u8>>> {
Ok(match loc {
ValueLoc::Reg(reg) if add_stack_value => {
LabelValueLoc::ValueLoc(ValueLoc::Reg(reg)) => {
let machine_reg = isa.map_dwarf_register(reg)?;
let mut writer = ExpressionWriter::new();
writer.write_op_reg(machine_reg)?;
if add_stack_value {
writer.write_op_reg(machine_reg)?;
} else {
writer.write_op_breg(machine_reg)?;
writer.write_sleb128(0)?;
}
Some(writer.into_vec())
}
ValueLoc::Reg(reg) => {
assert!(!add_stack_value);
let machine_reg = isa.map_dwarf_register(reg)?;
let mut writer = ExpressionWriter::new();
writer.write_op_breg(machine_reg)?;
writer.write_sleb128(0)?;
Some(writer.into_vec())
}
ValueLoc::Stack(ss) => {
LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => {
if let Some(frame_info) = frame_info {
if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
let mut writer = ExpressionWriter::new();
@@ -165,6 +162,27 @@ fn translate_loc(
}
None
}
LabelValueLoc::Reg(r) => {
let machine_reg = isa.map_regalloc_reg_to_dwarf(r)?;
let mut writer = ExpressionWriter::new();
if add_stack_value {
writer.write_op_reg(machine_reg)?;
} else {
writer.write_op_breg(machine_reg)?;
writer.write_sleb128(0)?;
}
Some(writer.into_vec())
}
LabelValueLoc::SPOffset(off) => {
let mut writer = ExpressionWriter::new();
writer.write_op_breg(X86_64::RSP.0)?;
writer.write_sleb128(off)?;
if !add_stack_value {
writer.write_op(gimli::constants::DW_OP_deref)?;
}
return Ok(Some(writer.into_vec()));
}
_ => None,
})
}
@@ -172,13 +190,13 @@ fn translate_loc(
fn append_memory_deref(
buf: &mut Vec<u8>,
frame_info: &FunctionFrameInfo,
vmctx_loc: ValueLoc,
vmctx_loc: LabelValueLoc,
isa: &dyn TargetIsa,
) -> Result<bool> {
let mut writer = ExpressionWriter::new();
// FIXME for imported memory
match vmctx_loc {
ValueLoc::Reg(vmctx_reg) => {
LabelValueLoc::ValueLoc(ValueLoc::Reg(vmctx_reg)) => {
let reg = isa.map_dwarf_register(vmctx_reg)? as u8;
writer.write_u8(gimli::constants::DW_OP_breg0.0 + reg)?;
let memory_offset = match frame_info.vmctx_memory_offset() {
@@ -189,7 +207,7 @@ fn append_memory_deref(
};
writer.write_sleb128(memory_offset)?;
}
ValueLoc::Stack(ss) => {
LabelValueLoc::ValueLoc(ValueLoc::Stack(ss)) => {
if let Some(ss_offset) = frame_info.stack_slots[ss].offset {
writer.write_op_breg(X86_64::RBP.0)?;
writer.write_sleb128(ss_offset as i64 + X86_64_STACK_OFFSET)?;
@@ -207,6 +225,31 @@ fn append_memory_deref(
return Ok(false);
}
}
LabelValueLoc::Reg(r) => {
let reg = isa.map_regalloc_reg_to_dwarf(r)?;
writer.write_op_breg(reg)?;
let memory_offset = match frame_info.vmctx_memory_offset() {
Some(offset) => offset,
None => {
return Ok(false);
}
};
writer.write_sleb128(memory_offset)?;
}
LabelValueLoc::SPOffset(off) => {
writer.write_op_breg(X86_64::RSP.0)?;
writer.write_sleb128(off)?;
writer.write_op(gimli::constants::DW_OP_deref)?;
writer.write_op(gimli::constants::DW_OP_consts)?;
let memory_offset = match frame_info.vmctx_memory_offset() {
Some(offset) => offset,
None => {
return Ok(false);
}
};
writer.write_sleb128(memory_offset)?;
writer.write_op(gimli::constants::DW_OP_plus)?;
}
_ => {
return Ok(false);
}
@@ -468,7 +511,7 @@ where
let _ = code_chunk; // suppresses warning for final flush
}
};
};
}
// Find all landing pads by scanning bytes, do not care about
// false location at this moment.
// Looks hacky but it is fast; does not need to be really exact.
@@ -653,7 +696,7 @@ struct CachedValueLabelRange {
func_index: DefinedFuncIndex,
start: usize,
end: usize,
label_location: HashMap<ValueLabel, ValueLoc>,
label_location: HashMap<ValueLabel, LabelValueLoc>,
}
struct ValueLabelRangesBuilder<'a, 'b> {
@@ -1179,7 +1222,7 @@ mod tests {
fn create_mock_value_ranges() -> (ValueLabelsRanges, (ValueLabel, ValueLabel, ValueLabel)) {
use std::collections::HashMap;
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::ir::{ValueLoc, ValueLocRange};
use wasmtime_environ::ir::{LabelValueLoc, ValueLoc, ValueLocRange};
let mut value_ranges = HashMap::new();
let value_0 = ValueLabel::new(0);
let value_1 = ValueLabel::new(1);
@@ -1187,7 +1230,7 @@ mod tests {
value_ranges.insert(
value_0,
vec![ValueLocRange {
loc: ValueLoc::Unassigned,
loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 0,
end: 25,
}],
@@ -1195,7 +1238,7 @@ mod tests {
value_ranges.insert(
value_1,
vec![ValueLocRange {
loc: ValueLoc::Unassigned,
loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 5,
end: 30,
}],
@@ -1204,12 +1247,12 @@ mod tests {
value_2,
vec![
ValueLocRange {
loc: ValueLoc::Unassigned,
loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 0,
end: 10,
},
ValueLocRange {
loc: ValueLoc::Unassigned,
loc: LabelValueLoc::ValueLoc(ValueLoc::Unassigned),
start: 20,
end: 30,
},

View File

@@ -16,7 +16,7 @@ anyhow = "1.0"
cranelift-codegen = { path = "../../cranelift/codegen", version = "0.69.0", features = ["enable-serde"] }
cranelift-entity = { path = "../../cranelift/entity", version = "0.69.0", features = ["enable-serde"] }
cranelift-wasm = { path = "../../cranelift/wasm", version = "0.69.0", features = ["enable-serde"] }
wasmparser = "0.71"
wasmparser = "0.73"
indexmap = { version = "1.0.2", features = ["serde-1"] }
thiserror = "1.0.4"
serde = { version = "1.0.94", features = ["derive"] }

View File

@@ -57,6 +57,8 @@ macro_rules! foreach_builtin_function {
memory_atomic_wait64(vmctx, i32, i32, i64, i64) -> (i32);
/// Returns an index for wasm's `memory.atomic.wait64` for imported memories.
imported_memory_atomic_wait64(vmctx, i32, i32, i64, i64) -> (i32);
/// Invoked when fuel has run out while executing a function.
out_of_gas(vmctx) -> ();
}
};
}

View File

@@ -3,8 +3,8 @@
pub mod ir {
pub use cranelift_codegen::binemit::{Reloc, StackMap};
pub use cranelift_codegen::ir::{
types, AbiParam, ArgumentPurpose, JumpTableOffsets, LibCall, Signature, SourceLoc,
StackSlots, TrapCode, Type, ValueLabel, ValueLoc,
types, AbiParam, ArgumentPurpose, JumpTableOffsets, LabelValueLoc, LibCall, Signature,
SourceLoc, StackSlots, TrapCode, Type, ValueLabel, ValueLoc,
};
pub use cranelift_codegen::{ValueLabelsRanges, ValueLocRange};
}

View File

@@ -142,12 +142,6 @@ impl ModuleType {
/// memory initializers.
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Module {
/// The parent index of this module, used for the module linking proposal.
///
/// This index is into the list of modules returned from compilation of a
/// single wasm file with nested modules.
pub parent: Option<usize>,
/// The name of this wasm module, often found in the wasm file.
pub name: Option<String>,
@@ -213,25 +207,26 @@ pub struct Module {
pub enum Initializer {
/// An imported item is required to be provided.
Import {
/// Module name of this import
module: String,
/// Optional field name of this import
/// Name of this import
name: String,
/// The field name projection of this import. When module-linking is
/// enabled this is always `None`. Otherwise this is always `Some`.
field: Option<String>,
/// Where this import will be placed, which also has type information
/// about the import.
index: EntityIndex,
},
/// A module from the parent's declared modules is inserted into our own
/// An export from a previously defined instance is being inserted into our
/// index space.
AliasParentModule(ModuleIndex),
/// A module from the parent's declared modules is inserted into our own
/// index space.
#[allow(missing_docs)]
///
/// Note that when the module linking proposal is enabled two-level imports
/// will implicitly desugar to this initializer.
AliasInstanceExport {
/// The instance that we're referencing.
instance: InstanceIndex,
export: usize,
/// Which export is being inserted into our index space.
export: String,
},
/// A module is being instantiated with previously configured intializers
@@ -239,15 +234,36 @@ pub enum Initializer {
Instantiate {
/// The module that this instance is instantiating.
module: ModuleIndex,
/// The arguments provided to instantiation.
args: Vec<EntityIndex>,
/// The arguments provided to instantiation, along with their name in
/// the instance being instantiated.
args: IndexMap<String, EntityIndex>,
},
/// A module is defined into the module index space, and which module is
/// being defined is specified by the index payload.
/// A module is being created from a set of compiled artifacts.
CreateModule {
/// The index of the artifact that's being convereted into a module.
artifact_index: usize,
/// The list of artifacts that this module value will be inheriting.
artifacts: Vec<usize>,
/// The list of modules that this module value will inherit.
modules: Vec<ModuleUpvar>,
},
/// A module is created from a closed-over-module value, defined when this
/// module was created.
DefineModule(usize),
}
/// Where module values can come from when creating a new module from a compiled
/// artifact.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ModuleUpvar {
/// A module value is inherited from the module creating the new module.
Inherit(usize),
/// A module value comes from the instance-to-be-created module index space.
Local(ModuleIndex),
}
impl Module {
/// Allocates the module data structures.
pub fn new() -> Self {
@@ -351,11 +367,9 @@ impl Module {
/// module name, field name, and type that's being imported.
pub fn imports(&self) -> impl Iterator<Item = (&str, Option<&str>, EntityType)> {
self.initializers.iter().filter_map(move |i| match i {
Initializer::Import {
module,
field,
index,
} => Some((module.as_str(), field.as_deref(), self.type_of(*index))),
Initializer::Import { name, field, index } => {
Some((name.as_str(), field.as_deref(), self.type_of(*index)))
}
_ => None,
})
}
@@ -389,16 +403,16 @@ pub struct TypeTables {
/// The type signature of known modules.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleSignature {
/// All imports in this module, listed in order with their module/name and
/// All imports in this module, listed in order with their name and
/// what type they're importing.
pub imports: Vec<(String, Option<String>, EntityType)>,
pub imports: IndexMap<String, EntityType>,
/// Exports are what an instance type conveys, so we go through an
/// indirection over there.
pub exports: InstanceTypeIndex,
}
/// The type signature of known instances.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct InstanceSignature {
/// The name of what's being exported as well as its type signature.
pub exports: IndexMap<String, EntityType>,

View File

@@ -1,6 +1,6 @@
use crate::module::{
Initializer, InstanceSignature, MemoryPlan, Module, ModuleSignature, ModuleType, TableElements,
TablePlan, TypeTables,
Initializer, InstanceSignature, MemoryPlan, Module, ModuleSignature, ModuleType, ModuleUpvar,
TableElements, TablePlan, TypeTables,
};
use crate::tunables::Tunables;
use cranelift_codegen::ir;
@@ -14,7 +14,7 @@ use cranelift_wasm::{
WasmError, WasmFuncType, WasmResult,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::{hash_map::Entry, HashMap};
use std::convert::TryFrom;
use std::mem;
use std::path::PathBuf;
@@ -31,6 +31,10 @@ pub struct ModuleEnvironment<'data> {
/// the module linking proposal.
results: Vec<ModuleTranslation<'data>>,
/// Modules which are in-progress being translated, or otherwise also known
/// as the outer modules of the current module being processed.
in_progress: Vec<ModuleTranslation<'data>>,
/// How many modules that have not yet made their way into `results` which
/// are coming at some point.
modules_to_be: usize,
@@ -38,13 +42,11 @@ pub struct ModuleEnvironment<'data> {
/// Intern'd types for this entire translation, shared by all modules.
types: TypeTables,
/// Where our module will get pushed into `results` after it's finished.
cur: usize,
// Various bits and pieces of configuration
features: WasmFeatures,
target_config: TargetFrontendConfig,
tunables: Tunables,
first_module: bool,
}
/// The result of translating via `ModuleEnvironment`. Function bodies are not
@@ -72,15 +74,15 @@ pub struct ModuleTranslation<'data> {
/// which function is currently being defined.
code_index: u32,
/// When local modules are declared an entry is pushed onto this list which
/// indicates that the initializer at the specified position needs to be
/// rewritten with the module's final index in the global list of compiled
/// modules.
module_initializer_indexes: Vec<usize>,
implicit_instances: HashMap<&'data str, InstanceIndex>,
/// Used as a pointer into the above list as the module code section is
/// parsed.
num_modules_defined: usize,
/// The artifacts which are needed from the parent module when this module
/// is created. This is used to insert into `Initializer::CreateModule` when
/// this module is defined in the parent.
creation_artifacts: Vec<usize>,
/// Same as `creation_artifacts`, but for modules instead of artifacts.
creation_modules: Vec<ModuleUpvar>,
}
/// Contains function data: byte code and its offset in the module.
@@ -142,12 +144,13 @@ impl<'data> ModuleEnvironment<'data> {
Self {
result: ModuleTranslation::default(),
results: Vec::with_capacity(1),
in_progress: Vec::new(),
modules_to_be: 1,
cur: 0,
types: Default::default(),
target_config,
tunables: tunables.clone(),
features: *features,
first_module: true,
}
}
@@ -163,7 +166,8 @@ impl<'data> ModuleEnvironment<'data> {
/// Note that for MVP modules this will always be a list with one element,
/// but with the module linking proposal this may have many elements.
///
/// For the module linking proposal the top-level module is at index 0.
/// For the module linking proposal the top-level module is returned as the
/// first return value.
///
/// The `TypeTables` structure returned contains intern'd versions of types
/// referenced from each module translation. This primarily serves as the
@@ -173,10 +177,10 @@ impl<'data> ModuleEnvironment<'data> {
pub fn translate(
mut self,
data: &'data [u8],
) -> WasmResult<(Vec<ModuleTranslation<'data>>, TypeTables)> {
) -> WasmResult<(usize, Vec<ModuleTranslation<'data>>, TypeTables)> {
translate_module(data, &mut self)?;
assert!(self.results.len() > 0);
Ok((self.results, self.types))
Ok((self.results.len() - 1, self.results, self.types))
}
fn declare_export(&mut self, export: EntityIndex, name: &str) -> WasmResult<()> {
@@ -224,6 +228,136 @@ impl<'data> ModuleEnvironment<'data> {
dwarf.ranges = gimli::RangeLists::new(info.debug_ranges, info.debug_rnglists);
dwarf.locations = gimli::LocationLists::new(info.debug_loc, info.debug_loclists);
}
/// Declares a new import with the `module` and `field` names, importing the
/// `ty` specified.
///
/// Note that this method is somewhat tricky due to the implementation of
/// the module linking proposal. In the module linking proposal two-level
/// imports are recast as single-level imports of instances. That recasting
/// happens here by recording an import of an instance for the first time
/// we see a two-level import.
///
/// When the module linking proposal is disabled, however, disregard this
/// logic and instead work directly with two-level imports since no
/// instances are defined.
fn declare_import(&mut self, module: &'data str, field: Option<&'data str>, ty: EntityType) {
if !self.features.module_linking {
assert!(field.is_some());
let index = self.push_type(ty);
self.result.module.initializers.push(Initializer::Import {
name: module.to_owned(),
field: field.map(|s| s.to_string()),
index,
});
return;
}
match field {
Some(field) => {
// If this is a two-level import then this is actually an
// implicit import of an instance, where each two-level import
// is an alias directive from the original instance. The first
// thing we do here is lookup our implicit instance, creating a
// blank one if it wasn't already created.
let instance = match self.result.implicit_instances.entry(module) {
Entry::Occupied(e) => *e.get(),
Entry::Vacant(v) => {
let ty = self
.types
.instance_signatures
.push(InstanceSignature::default());
let idx = self.result.module.instances.push(ty);
self.result.module.initializers.push(Initializer::Import {
name: module.to_owned(),
field: None,
index: EntityIndex::Instance(idx),
});
*v.insert(idx)
}
};
// Update the implicit instance's type signature with this new
// field and its type.
self.types.instance_signatures[self.result.module.instances[instance]]
.exports
.insert(field.to_string(), ty.clone());
// Record our implicit alias annotation which corresponds to
// this import that we're processing.
self.result
.module
.initializers
.push(Initializer::AliasInstanceExport {
instance,
export: field.to_string(),
});
// And then record the type information for the item that we're
// processing.
self.push_type(ty);
}
None => {
// Without a field then this is a single-level import (a feature
// of module linking) which means we're simply importing that
// name with the specified type. Record the type information and
// then the name that we're importing.
let index = self.push_type(ty);
self.result.module.initializers.push(Initializer::Import {
name: module.to_owned(),
field: None,
index,
});
}
}
}
fn push_type(&mut self, ty: EntityType) -> EntityIndex {
match ty {
EntityType::Function(ty) => {
EntityIndex::Function(self.result.module.functions.push(ty))
}
EntityType::Table(ty) => {
let plan = TablePlan::for_table(ty, &self.tunables);
EntityIndex::Table(self.result.module.table_plans.push(plan))
}
EntityType::Memory(ty) => {
let plan = MemoryPlan::for_memory(ty, &self.tunables);
EntityIndex::Memory(self.result.module.memory_plans.push(plan))
}
EntityType::Global(ty) => EntityIndex::Global(self.result.module.globals.push(ty)),
EntityType::Instance(ty) => {
EntityIndex::Instance(self.result.module.instances.push(ty))
}
EntityType::Module(ty) => EntityIndex::Module(self.result.module.modules.push(ty)),
EntityType::Event(_) => unimplemented!(),
}
}
fn gen_type_of_module(&mut self, module: &Module) -> ModuleTypeIndex {
let imports = module
.imports()
.map(|(s, field, ty)| {
assert!(field.is_none());
(s.to_string(), ty)
})
.collect();
let exports = module
.exports
.iter()
.map(|(name, idx)| (name.clone(), module.type_of(*idx)))
.collect();
// FIXME(#2469): this instance/module signature insertion should likely
// be deduplicated.
let exports = self
.types
.instance_signatures
.push(InstanceSignature { exports });
self.types
.module_signatures
.push(ModuleSignature { imports, exports })
}
}
impl<'data> TargetEnvironment for ModuleEnvironment<'data> {
@@ -267,13 +401,29 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
fn declare_type_module(
&mut self,
imports: &[(&'data str, Option<&'data str>, EntityType)],
declared_imports: &[(&'data str, Option<&'data str>, EntityType)],
exports: &[(&'data str, EntityType)],
) -> WasmResult<()> {
let imports = imports
.iter()
.map(|i| (i.0.to_string(), i.1.map(|s| s.to_string()), i.2.clone()))
.collect();
let mut imports = indexmap::IndexMap::new();
let mut instance_types = HashMap::new();
for (module, field, ty) in declared_imports {
match field {
Some(field) => {
let idx = *instance_types
.entry(module)
.or_insert_with(|| self.types.instance_signatures.push(Default::default()));
self.types.instance_signatures[idx]
.exports
.insert(field.to_string(), ty.clone());
if !imports.contains_key(*module) {
imports.insert(module.to_string(), EntityType::Instance(idx));
}
}
None => {
imports.insert(module.to_string(), ty.clone());
}
}
}
let exports = exports
.iter()
.map(|e| (e.0.to_string(), e.1.clone()))
@@ -344,8 +494,8 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
fn declare_func_import(
&mut self,
index: TypeIndex,
module: &str,
field: Option<&str>,
module: &'data str,
field: Option<&'data str>,
) -> WasmResult<()> {
debug_assert_eq!(
self.result.module.functions.len(),
@@ -353,12 +503,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
"Imported functions must be declared first"
);
let sig_index = self.result.module.types[index].unwrap_function();
let func_index = self.result.module.functions.push(sig_index);
self.result.module.initializers.push(Initializer::Import {
module: module.to_owned(),
field: field.map(|s| s.to_owned()),
index: EntityIndex::Function(func_index),
});
self.declare_import(module, field, EntityType::Function(sig_index));
self.result.module.num_imported_funcs += 1;
self.result.debuginfo.wasm_file.imported_func_count += 1;
Ok(())
@@ -367,21 +512,15 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
fn declare_table_import(
&mut self,
table: Table,
module: &str,
field: Option<&str>,
module: &'data str,
field: Option<&'data str>,
) -> WasmResult<()> {
debug_assert_eq!(
self.result.module.table_plans.len(),
self.result.module.num_imported_tables,
"Imported tables must be declared first"
);
let plan = TablePlan::for_table(table, &self.tunables);
let table_index = self.result.module.table_plans.push(plan);
self.result.module.initializers.push(Initializer::Import {
module: module.to_owned(),
field: field.map(|s| s.to_owned()),
index: EntityIndex::Table(table_index),
});
self.declare_import(module, field, EntityType::Table(table));
self.result.module.num_imported_tables += 1;
Ok(())
}
@@ -389,8 +528,8 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
fn declare_memory_import(
&mut self,
memory: Memory,
module: &str,
field: Option<&str>,
module: &'data str,
field: Option<&'data str>,
) -> WasmResult<()> {
debug_assert_eq!(
self.result.module.memory_plans.len(),
@@ -400,13 +539,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
if memory.shared {
return Err(WasmError::Unsupported("shared memories".to_owned()));
}
let plan = MemoryPlan::for_memory(memory, &self.tunables);
let memory_index = self.result.module.memory_plans.push(plan);
self.result.module.initializers.push(Initializer::Import {
module: module.to_owned(),
field: field.map(|s| s.to_owned()),
index: EntityIndex::Memory(memory_index),
});
self.declare_import(module, field, EntityType::Memory(memory));
self.result.module.num_imported_memories += 1;
Ok(())
}
@@ -414,20 +547,15 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
fn declare_global_import(
&mut self,
global: Global,
module: &str,
field: Option<&str>,
module: &'data str,
field: Option<&'data str>,
) -> WasmResult<()> {
debug_assert_eq!(
self.result.module.globals.len(),
self.result.module.num_imported_globals,
"Imported globals must be declared first"
);
let global_index = self.result.module.globals.push(global);
self.result.module.initializers.push(Initializer::Import {
module: module.to_owned(),
field: field.map(|s| s.to_owned()),
index: EntityIndex::Global(global_index),
});
self.declare_import(module, field, EntityType::Global(global));
self.result.module.num_imported_globals += 1;
Ok(())
}
@@ -439,12 +567,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
field: Option<&'data str>,
) -> WasmResult<()> {
let signature = self.type_to_module_type(ty_index)?;
let module_index = self.result.module.modules.push(signature);
self.result.module.initializers.push(Initializer::Import {
module: module.to_owned(),
field: field.map(|s| s.to_owned()),
index: EntityIndex::Module(module_index),
});
self.declare_import(module, field, EntityType::Module(signature));
Ok(())
}
@@ -455,12 +578,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
field: Option<&'data str>,
) -> WasmResult<()> {
let signature = self.type_to_instance_type(ty_index)?;
let instance_index = self.result.module.instances.push(signature);
self.result.module.initializers.push(Initializer::Import {
module: module.to_owned(),
field: field.map(|s| s.to_owned()),
index: EntityIndex::Instance(instance_index),
});
self.declare_import(module, field, EntityType::Instance(signature));
Ok(())
}
@@ -755,62 +873,57 @@ and for re-adding support for interface types you can see this issue:
self.result.module.initializers.reserve(amount as usize);
}
fn declare_module(&mut self, ty: TypeIndex) -> WasmResult<()> {
// Record the type signature of this module ...
let signature = self.type_to_module_type(ty)?;
self.result.module.modules.push(signature);
// ... and then record that in the initialization steps of this module
// we're inserting this module into the module index space. At this
// point we don't know the final index of the module we're defining, so
// we leave a placeholder to get rewritten later.
let loc = self.result.module.initializers.len();
self.result
.module
.initializers
.push(Initializer::DefineModule(usize::max_value()));
self.result.module_initializer_indexes.push(loc);
Ok(())
}
fn module_start(&mut self, index: usize) {
// Reset the contents of `self.result` for a new module that's getting
// translataed.
let mut prev = mem::replace(&mut self.result, ModuleTranslation::default());
// If this is a nested submodule then we record the final destination of
// the child in parent (we store `index` into `prev`) in the appropriate
// initialization slot as dicated by `num_modules_defined` (our index of
// iteration through the code section).
// Record that the `num_modules_defined`-th module is defined at index
// by updating the initializer entry.
if index > 0 {
let initializer_idx = prev.module_initializer_indexes[prev.num_modules_defined];
prev.num_modules_defined += 1;
debug_assert!(match &prev.module.initializers[initializer_idx] {
Initializer::DefineModule(usize::MAX) => true,
_ => false,
});
prev.module.initializers[initializer_idx] = Initializer::DefineModule(index);
self.result.module.parent = Some(self.cur);
fn module_start(&mut self) {
// If this is the first time this method is called, nothing to do.
if self.first_module {
self.first_module = false;
return;
}
// Update our current index counter and save our parent's translation
// where this current translation will end up, which we'll swap back as
// part of `module_end`.
self.cur = index;
assert_eq!(index, self.results.len());
self.results.push(prev);
// Reset our internal state for a new module by saving the current
// module in `results`.
let in_progress = mem::replace(&mut self.result, ModuleTranslation::default());
self.in_progress.push(in_progress);
self.modules_to_be -= 1;
}
fn module_end(&mut self, index: usize) {
assert!(self.result.num_modules_defined == self.result.module_initializer_indexes.len());
fn module_end(&mut self) {
self.result.creation_artifacts.shrink_to_fit();
self.result.creation_modules.shrink_to_fit();
// Move our finished module into its final location, swapping it with
// what was this module's parent.
self.cur = self.result.module.parent.unwrap_or(0);
mem::swap(&mut self.result, &mut self.results[index]);
let (record_initializer, mut done) = match self.in_progress.pop() {
Some(m) => (true, mem::replace(&mut self.result, m)),
None => (false, mem::take(&mut self.result)),
};
if record_initializer {
// Record the type of the module we just finished in our own
// module's list of modules.
let sig = self.gen_type_of_module(&done.module);
self.result.module.modules.push(sig);
// The root module will store the artifacts for this finished
// module at `artifact_index`. This then needs to be inherited by
// all later modules coming down to our now-current `self.result`...
let mut artifact_index = self.results.len();
for result in self.in_progress.iter_mut().chain(Some(&mut self.result)) {
result.creation_artifacts.push(artifact_index);
artifact_index = result.creation_artifacts.len() - 1;
}
// ... and then `self.result` needs to create a new module with
// whatever was record to save off as its own artifacts/modules.
self.result
.module
.initializers
.push(Initializer::CreateModule {
artifact_index,
artifacts: mem::take(&mut done.creation_artifacts),
modules: mem::take(&mut done.creation_modules),
});
}
// And the final step is to insert the module into the list of finished
// modules to get returned at the end.
self.results.push(done);
}
fn reserve_instances(&mut self, amt: u32) {
@@ -818,7 +931,12 @@ and for re-adding support for interface types you can see this issue:
self.result.module.initializers.reserve(amt as usize);
}
fn declare_instance(&mut self, module: ModuleIndex, args: Vec<EntityIndex>) -> WasmResult<()> {
fn declare_instance(
&mut self,
module: ModuleIndex,
args: Vec<(&'data str, EntityIndex)>,
) -> WasmResult<()> {
let args = args.into_iter().map(|(s, i)| (s.to_string(), i)).collect();
// Record the type of this instance with the type signature of the
// module we're instantiating and then also add an initializer which
// records that we'll be adding to the instance index space here.
@@ -839,29 +957,60 @@ and for re-adding support for interface types you can see this issue:
//
// Note that we don't add an initializer for this alias because
// we statically know where all types point to.
Alias::ParentType(parent_idx) => {
let ty = self.results[self.cur].module.types[parent_idx];
Alias::OuterType {
relative_depth,
index,
} => {
let module_idx = self.in_progress.len() - 1 - (relative_depth as usize);
let ty = self.in_progress[module_idx].module.types[index];
self.result.module.types.push(ty);
}
// This is similar to types in that it's easy for us to record the
// type of the module that's being aliased, but we also need to add
// an initializer so during instantiation we can prepare the index
// space appropriately.
Alias::ParentModule(parent_idx) => {
let module_idx = self.results[self.cur].module.modules[parent_idx];
self.result.module.modules.push(module_idx);
// Modules are a bit trickier since we need to record how to track
// the state from the original module down to our own.
Alias::OuterModule {
relative_depth,
index,
} => {
// First we can copy the type from the parent module into our
// own module to record what type our module definition will
// have.
let module_idx = self.in_progress.len() - 1 - (relative_depth as usize);
let module_ty = self.in_progress[module_idx].module.modules[index];
self.result.module.modules.push(module_ty);
// Next we'll be injecting a module value that is closed over,
// and that will be used to define the module into the index
// space. Record an initializer about where our module is
// sourced from (which will be stored within each module value
// itself).
let module_index = self.result.creation_modules.len();
self.result
.module
.initializers
.push(Initializer::AliasParentModule(parent_idx));
.push(Initializer::DefineModule(module_index));
// And finally we need to record a breadcrumb trail of how to
// get the module value into `module_index`. The module just
// after our destination module will use a `ModuleIndex` to
// fetch the module value, and everything else inbetween will
// inherit that module's closed-over value.
let mut upvar = ModuleUpvar::Local(index);
for outer in self.in_progress[module_idx + 1..].iter_mut() {
let upvar = mem::replace(
&mut upvar,
ModuleUpvar::Inherit(outer.creation_modules.len()),
);
outer.creation_modules.push(upvar);
}
self.result.creation_modules.push(upvar);
}
// This case is slightly more involved, we'll be recording all the
// type information for each kind of entity, and then we also need
// to record an initialization step to get the export from the
// instance.
Alias::Child { instance, export } => {
Alias::InstanceExport { instance, export } => {
let ty = self.result.module.instances[instance];
match &self.types.instance_signatures[ty].exports[export] {
EntityType::Global(g) => {
@@ -894,7 +1043,10 @@ and for re-adding support for interface types you can see this issue:
self.result
.module
.initializers
.push(Initializer::AliasInstanceExport { instance, export })
.push(Initializer::AliasInstanceExport {
instance,
export: export.to_string(),
})
}
}

View File

@@ -23,6 +23,10 @@ pub struct Tunables {
/// calls and interrupts are implemented through the `VMInterrupts`
/// structure, or `InterruptHandle` in the `wasmtime` crate.
pub interruptable: bool,
/// Whether or not fuel is enabled for generated code, meaning that fuel
/// will be consumed every time a wasm instruction is executed.
pub consume_fuel: bool,
}
impl Default for Tunables {
@@ -57,6 +61,7 @@ impl Default for Tunables {
generate_native_debuginfo: false,
parse_wasm_debuginfo: true,
interruptable: false,
consume_fuel: false,
}
}
}

View File

@@ -258,6 +258,11 @@ impl VMOffsets {
pub fn vminterrupts_stack_limit(&self) -> u8 {
0
}
/// Return the offset of the `fuel_consumed` field of `VMInterrupts`
pub fn vminterrupts_fuel_consumed(&self) -> u8 {
self.pointer_size
}
}
/// Offsets for `VMCallerCheckedAnyfunc`.

View File

@@ -13,12 +13,12 @@ arbitrary = { version = "0.4.1", features = ["derive"] }
env_logger = "0.8.1"
log = "0.4.8"
rayon = "1.2.1"
wasmparser = "0.71"
wasmprinter = "0.2.17"
wasmparser = "0.73"
wasmprinter = "0.2.20"
wasmtime = { path = "../wasmtime" }
wasmtime-wast = { path = "../wast" }
wasm-encoder = "0.2"
wasm-smith = "0.3.0"
wasm-encoder = "0.4"
wasm-smith = "0.3.1"
wasmi = "0.7.0"
[dev-dependencies]

View File

@@ -64,6 +64,8 @@ pub struct Config {
debug_info: bool,
canonicalize_nans: bool,
interruptable: bool,
#[allow(missing_docs)]
pub consume_fuel: bool,
// Note that we use 32-bit values here to avoid blowing the 64-bit address
// space by requesting ungodly-large sizes/guards.
@@ -75,14 +77,15 @@ pub struct Config {
impl Config {
/// Converts this to a `wasmtime::Config` object
pub fn to_wasmtime(&self) -> wasmtime::Config {
let mut cfg = wasmtime::Config::new();
let mut cfg = crate::fuzz_default_config(wasmtime::Strategy::Auto).unwrap();
cfg.debug_info(self.debug_info)
.static_memory_maximum_size(self.static_memory_maximum_size.unwrap_or(0).into())
.static_memory_guard_size(self.static_memory_guard_size.unwrap_or(0).into())
.dynamic_memory_guard_size(self.dynamic_memory_guard_size.unwrap_or(0).into())
.cranelift_nan_canonicalization(self.canonicalize_nans)
.cranelift_opt_level(self.opt_level.to_wasmtime())
.interruptable(self.interruptable);
.interruptable(self.interruptable)
.consume_fuel(self.consume_fuel);
return cfg;
}
}

View File

@@ -39,6 +39,9 @@ pub fn fuzz_default_config(strategy: wasmtime::Strategy) -> anyhow::Result<wasmt
.wasm_bulk_memory(true)
.wasm_reference_types(true)
.wasm_module_linking(true)
.max_instances(100)
.max_tables(100)
.max_memories(100)
.strategy(strategy)?;
Ok(config)
}

View File

@@ -40,6 +40,20 @@ fn log_wasm(wasm: &[u8]) {
}
}
/// Methods of timing out execution of a WebAssembly module
#[derive(Debug)]
pub enum Timeout {
/// No timeout is used, it should be guaranteed via some other means that
/// the input does not infinite loop.
None,
/// A time-based timeout is used with a sleeping thread sending a signal
/// after the specified duration.
Time(Duration),
/// Fuel-based timeouts are used where the specified fuel is all that the
/// provided wasm module is allowed to consume.
Fuel(u64),
}
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
/// panic or segfault or anything else that can be detected "passively".
///
@@ -47,12 +61,11 @@ fn log_wasm(wasm: &[u8]) {
///
/// You can control which compiler is used via passing a `Strategy`.
pub fn instantiate(wasm: &[u8], known_valid: bool, strategy: Strategy) {
instantiate_with_config(
wasm,
known_valid,
crate::fuzz_default_config(strategy).unwrap(),
None,
);
// Explicitly disable module linking for now since it's a breaking change to
// pre-module-linking modules due to imports
let mut cfg = crate::fuzz_default_config(strategy).unwrap();
cfg.wasm_module_linking(false);
instantiate_with_config(wasm, known_valid, cfg, Timeout::None);
}
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
@@ -65,28 +78,38 @@ pub fn instantiate_with_config(
wasm: &[u8],
known_valid: bool,
mut config: Config,
timeout: Option<Duration>,
timeout: Timeout,
) {
crate::init_fuzzing();
config.interruptable(timeout.is_some());
config.interruptable(match &timeout {
Timeout::Time(_) => true,
_ => false,
});
config.consume_fuel(match &timeout {
Timeout::Fuel(_) => true,
_ => false,
});
let engine = Engine::new(&config);
let store = Store::new(&engine);
// If a timeout is requested then we spawn a helper thread to wait for the
// requested time and then send us a signal to get interrupted. We also
// arrange for the thread's sleep to get interrupted if we return early (or
// the wasm returns within the time limit), which allows the thread to get
// torn down.
//
// This prevents us from creating a huge number of sleeping threads if this
// function is executed in a loop, like it does on nightly fuzzing
// infrastructure.
let mut timeout_state = SignalOnDrop::default();
if let Some(timeout) = timeout {
let handle = store.interrupt_handle().unwrap();
timeout_state.spawn_timeout(timeout, move || handle.interrupt());
match timeout {
Timeout::Fuel(fuel) => store.add_fuel(fuel),
// If a timeout is requested then we spawn a helper thread to wait for
// the requested time and then send us a signal to get interrupted. We
// also arrange for the thread's sleep to get interrupted if we return
// early (or the wasm returns within the time limit), which allows the
// thread to get torn down.
//
// This prevents us from creating a huge number of sleeping threads if
// this function is executed in a loop, like it does on nightly fuzzing
// infrastructure.
Timeout::Time(timeout) => {
let handle = store.interrupt_handle().unwrap();
timeout_state.spawn_timeout(timeout, move || handle.interrupt());
}
Timeout::None => {}
}
log_wasm(wasm);
@@ -99,8 +122,14 @@ pub fn instantiate_with_config(
match Instance::new(&store, &module, &imports) {
Ok(_) => {}
// Allow traps which can happen normally with `unreachable`
// Allow traps which can happen normally with `unreachable` or a timeout
Err(e) if e.downcast_ref::<Trap>().is_some() => {}
// Allow resource exhaustion since this is something that our wasm-smith
// generator doesn't guarantee is forbidden.
Err(e) if e.to_string().contains("resource limit exceeded") => {}
// Also allow errors related to fuel consumption
Err(e) if e.to_string().contains("all fuel consumed") => {}
// Everything else should be a bug in the fuzzer
Err(e) => panic!("failed to instantiate {}", e),
}
}
@@ -381,13 +410,16 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
/// Executes the wast `test` spectest with the `config` specified.
///
/// Ensures that spec tests pass regardless of the `Config`.
pub fn spectest(config: crate::generators::Config, test: crate::generators::SpecTest) {
pub fn spectest(fuzz_config: crate::generators::Config, test: crate::generators::SpecTest) {
crate::init_fuzzing();
log::debug!("running {:?} with {:?}", test.file, config);
let mut config = config.to_wasmtime();
log::debug!("running {:?} with {:?}", test.file, fuzz_config);
let mut config = fuzz_config.to_wasmtime();
config.wasm_reference_types(false);
config.wasm_bulk_memory(false);
let store = Store::new(&Engine::new(&config));
if fuzz_config.consume_fuel {
store.add_fuel(u64::max_value());
}
let mut wast_context = WastContext::new(store);
wast_context.register_spectest().unwrap();
wast_context
@@ -396,16 +428,22 @@ pub fn spectest(config: crate::generators::Config, test: crate::generators::Spec
}
/// Execute a series of `table.get` and `table.set` operations.
pub fn table_ops(config: crate::generators::Config, ops: crate::generators::table_ops::TableOps) {
pub fn table_ops(
fuzz_config: crate::generators::Config,
ops: crate::generators::table_ops::TableOps,
) {
let _ = env_logger::try_init();
let num_dropped = Rc::new(Cell::new(0));
{
let mut config = config.to_wasmtime();
let mut config = fuzz_config.to_wasmtime();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let store = Store::new(&engine);
if fuzz_config.consume_fuel {
store.add_fuel(u64::max_value());
}
let wasm = ops.to_wasm_binary();
log_wasm(&wasm);
@@ -518,6 +556,9 @@ pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Con
wasmtime_config.cranelift_nan_canonicalization(true);
let wasmtime_engine = Engine::new(&wasmtime_config);
let wasmtime_store = Store::new(&wasmtime_engine);
if config.consume_fuel {
wasmtime_store.add_fuel(u64::max_value());
}
let wasmtime_module =
Module::new(&wasmtime_engine, &wasm).expect("Wasmtime can compile module");
let wasmtime_instance = Instance::new(&wasmtime_store, &wasmtime_module, &[])

View File

@@ -349,3 +349,232 @@ fn wat_ty(ty: &ValType) -> &'static str {
ValType::FuncRef => "funcref",
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
fn store() -> Store {
let mut config = Config::default();
config.wasm_module_linking(true);
config.wasm_multi_memory(true);
let engine = wasmtime::Engine::new(&config);
Store::new(&engine)
}
#[test]
fn dummy_table_import() {
let store = store();
let table = dummy_table(
&store,
TableType::new(ValType::ExternRef, Limits::at_least(10)),
);
assert_eq!(table.size(), 10);
for i in 0..10 {
assert!(table.get(i).unwrap().unwrap_externref().is_none());
}
}
#[test]
fn dummy_global_import() {
let store = store();
let global = dummy_global(&store, GlobalType::new(ValType::I32, Mutability::Const));
assert_eq!(global.val_type(), ValType::I32);
assert_eq!(global.mutability(), Mutability::Const);
}
#[test]
fn dummy_memory_import() {
let store = store();
let memory = dummy_memory(&store, MemoryType::new(Limits::at_least(1)));
assert_eq!(memory.size(), 1);
}
#[test]
fn dummy_function_import() {
let store = store();
let func_ty = FuncType::new(vec![ValType::I32], vec![ValType::I64]);
let func = dummy_func(&store, func_ty.clone());
assert_eq!(func.ty(), func_ty);
}
#[test]
fn dummy_instance_import() {
let store = store();
let mut instance_ty = InstanceType::new();
// Functions.
instance_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
instance_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
// Globals.
instance_ty.add_named_export(
"global0",
GlobalType::new(ValType::I32, Mutability::Const).into(),
);
instance_ty.add_named_export(
"global1",
GlobalType::new(ValType::I64, Mutability::Var).into(),
);
// Tables.
instance_ty.add_named_export(
"table0",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
instance_ty.add_named_export(
"table1",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
// Memories.
instance_ty.add_named_export("memory0", MemoryType::new(Limits::at_least(1)).into());
instance_ty.add_named_export("memory1", MemoryType::new(Limits::at_least(1)).into());
// Modules.
instance_ty.add_named_export("module0", ModuleType::new().into());
instance_ty.add_named_export("module1", ModuleType::new().into());
// Instances.
instance_ty.add_named_export("instance0", InstanceType::new().into());
instance_ty.add_named_export("instance1", InstanceType::new().into());
let instance = dummy_instance(&store, instance_ty.clone());
let mut expected_exports = vec![
"func0",
"func1",
"global0",
"global1",
"table0",
"table1",
"memory0",
"memory1",
"module0",
"module1",
"instance0",
"instance1",
]
.into_iter()
.collect::<HashSet<_>>();
for exp in instance.ty().exports() {
let was_expected = expected_exports.remove(exp.name());
assert!(was_expected);
}
assert!(expected_exports.is_empty());
}
#[test]
fn dummy_module_import() {
let store = store();
let mut module_ty = ModuleType::new();
// Multiple exported and imported functions.
module_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
module_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
module_ty.add_named_import(
"func2",
None,
FuncType::new(vec![ValType::I64], vec![]).into(),
);
module_ty.add_named_import(
"func3",
None,
FuncType::new(vec![], vec![ValType::I32]).into(),
);
// Multiple exported and imported globals.
module_ty.add_named_export(
"global0",
GlobalType::new(ValType::I32, Mutability::Const).into(),
);
module_ty.add_named_export(
"global1",
GlobalType::new(ValType::I64, Mutability::Var).into(),
);
module_ty.add_named_import(
"global2",
None,
GlobalType::new(ValType::I32, Mutability::Var).into(),
);
module_ty.add_named_import(
"global3",
None,
GlobalType::new(ValType::I64, Mutability::Const).into(),
);
// Multiple exported and imported tables.
module_ty.add_named_export(
"table0",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
module_ty.add_named_export(
"table1",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
module_ty.add_named_import(
"table2",
None,
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
module_ty.add_named_import(
"table3",
None,
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
// Multiple exported and imported memories.
module_ty.add_named_export("memory0", MemoryType::new(Limits::at_least(1)).into());
module_ty.add_named_export("memory1", MemoryType::new(Limits::at_least(1)).into());
module_ty.add_named_import("memory2", None, MemoryType::new(Limits::at_least(1)).into());
module_ty.add_named_import("memory3", None, MemoryType::new(Limits::at_least(1)).into());
// An exported and an imported module.
module_ty.add_named_export("module0", ModuleType::new().into());
module_ty.add_named_import("module1", None, ModuleType::new().into());
// An exported and an imported instance.
module_ty.add_named_export("instance0", InstanceType::new().into());
module_ty.add_named_import("instance1", None, InstanceType::new().into());
// Create the module.
let module = dummy_module(&store, module_ty);
// Check that we have the expected exports.
assert!(module.get_export("func0").is_some());
assert!(module.get_export("func1").is_some());
assert!(module.get_export("global0").is_some());
assert!(module.get_export("global1").is_some());
assert!(module.get_export("table0").is_some());
assert!(module.get_export("table1").is_some());
assert!(module.get_export("memory0").is_some());
assert!(module.get_export("memory1").is_some());
assert!(module.get_export("instance0").is_some());
assert!(module.get_export("module0").is_some());
// Check that we have the exported imports.
let mut expected_imports = vec![
"func2",
"func3",
"global2",
"global3",
"table2",
"table3",
"memory2",
"memory3",
"instance1",
"module1",
]
.into_iter()
.collect::<HashSet<_>>();
for imp in module.imports() {
assert!(imp.name().is_none());
let was_expected = expected_imports.remove(imp.module());
assert!(was_expected);
}
assert!(expected_imports.is_empty());
}
}

View File

@@ -28,13 +28,13 @@ rayon = { version = "1.0", optional = true }
region = "2.1.0"
thiserror = "1.0.4"
target-lexicon = { version = "0.11.0", default-features = false }
wasmparser = "0.71"
wasmparser = "0.73"
more-asserts = "0.2.1"
anyhow = "1.0"
cfg-if = "1.0"
log = "0.4"
gimli = { version = "0.23.0", default-features = false, features = ["write"] }
object = { version = "0.22.0", default-features = false, features = ["write"] }
object = { version = "0.23.0", default-features = false, features = ["write"] }
serde = { version = "1.0.94", features = ["derive"] }
addr2line = { version = "0.14", default-features = false }

View File

@@ -182,14 +182,10 @@ impl Hash for Compiler {
// misc tunables.
strategy.hash(hasher);
isa.triple().hash(hasher);
features.hash(hasher);
// TODO: if this `to_string()` is too expensive then we should upstream
// a native hashing ability of flags into cranelift itself, but
// compilation and/or cache loading is relatively expensive so seems
// unlikely.
isa.flags().to_string().hash(hasher);
isa.hash_all_flags(hasher);
isa.frontend_config().hash(hasher);
tunables.hash(hasher);
features.hash(hasher);
// Catch accidental bugs of reusing across crate versions.
env!("CARGO_PKG_VERSION").hash(hasher);

View File

@@ -105,8 +105,8 @@ impl CompilationArtifacts {
pub fn build(
compiler: &Compiler,
data: &[u8],
) -> Result<(Vec<CompilationArtifacts>, TypeTables), SetupError> {
let (translations, types) = ModuleEnvironment::new(
) -> Result<(usize, Vec<CompilationArtifacts>, TypeTables), SetupError> {
let (main_module, translations, types) = ModuleEnvironment::new(
compiler.frontend_config(),
compiler.tunables(),
compiler.features(),
@@ -166,6 +166,7 @@ impl CompilationArtifacts {
})
.collect::<Result<Vec<_>, SetupError>>()?;
Ok((
main_module,
list,
TypeTables {
wasm_signatures: types.wasm_signatures,
@@ -220,7 +221,7 @@ impl CompiledModule {
artifacts: Vec<CompilationArtifacts>,
isa: &dyn TargetIsa,
profiler: &dyn ProfilingAgent,
) -> Result<Vec<Self>, SetupError> {
) -> Result<Vec<Arc<Self>>, SetupError> {
maybe_parallel!(artifacts.(into_iter | into_par_iter))
.map(|a| CompiledModule::from_artifacts(a, isa, profiler))
.collect()
@@ -231,7 +232,7 @@ impl CompiledModule {
artifacts: CompilationArtifacts,
isa: &dyn TargetIsa,
profiler: &dyn ProfilingAgent,
) -> Result<Self, SetupError> {
) -> Result<Arc<Self>, SetupError> {
// Allocate all of the compiled functions into executable memory,
// copying over their contents.
let (code_memory, code_range, finished_functions, trampolines) = build_code_memory(
@@ -265,7 +266,7 @@ impl CompiledModule {
let finished_functions = FinishedFunctions(finished_functions);
Ok(Self {
Ok(Arc::new(Self {
module: Arc::new(artifacts.module.clone()),
artifacts,
code: Arc::new(ModuleCode {
@@ -274,7 +275,7 @@ impl CompiledModule {
}),
finished_functions,
trampolines,
})
}))
}
/// Crate an `Instance` from this `CompiledModule`.

View File

@@ -6,6 +6,11 @@ pub fn builder() -> cranelift_codegen::isa::Builder {
cranelift_native::builder().expect("host machine is not a supported target")
}
pub fn builder_without_flags() -> cranelift_codegen::isa::Builder {
cranelift_native::builder_with_options(cranelift_codegen::isa::BackendVariant::Any, false)
.expect("host machine is not a supported target")
}
pub fn call_conv() -> cranelift_codegen::isa::CallConv {
use target_lexicon::HOST;
cranelift_codegen::isa::CallConv::triple_default(&HOST)

View File

@@ -18,18 +18,18 @@ derive_more = "0.99"
dynasm = "1.0.0"
dynasmrt = "1.0.0"
iter-enum = "0.2"
itertools = "0.9.0"
itertools = "0.10.0"
memoffset = "0.6.0"
more-asserts = "0.2.1"
smallvec = "1.6.1"
thiserror = "1.0.9"
typemap = "0.3"
wasmparser = "0.71"
wasmparser = "0.73"
[dev-dependencies]
lazy_static = "1.2"
wat = "1.0.23"
quickcheck = "0.9.0"
quickcheck = "1.0.0"
anyhow = "1.0"
[badges]

View File

@@ -13,6 +13,6 @@ edition = "2018"
[dependencies]
lightbeam = { path = "..", version = "0.22.0" }
wasmparser = "0.71"
wasmparser = "0.73"
cranelift-codegen = { path = "../../../cranelift/codegen", version = "0.69.0" }
wasmtime-environ = { path = "../../environ", version = "0.22.0" }

View File

@@ -83,7 +83,8 @@ fn main() -> anyhow::Result<()> {
.arg("userenv.lib")
.arg("ntdll.lib")
.arg("shell32.lib")
.arg("ole32.lib");
.arg("ole32.lib")
.arg("bcrypt.lib");
if is_dir {
"main.exe".to_string()
} else {

View File

@@ -13,7 +13,7 @@ edition = "2018"
[dependencies]
anyhow = "1.0"
wasmtime-environ = { path = "../environ", version = "0.22.0" }
object = { version = "0.22.0", default-features = false, features = ["write"] }
object = { version = "0.23.0", default-features = false, features = ["write"] }
more-asserts = "0.2.1"
target-lexicon = { version = "0.11.0", default-features = false }
wasmtime-debug = { path = "../debug", version = "0.22.0" }

View File

@@ -24,7 +24,7 @@ wasmtime-runtime = { path = "../runtime", version = "0.22.0" }
ittapi-rs = { version = "0.1.5", optional = true }
[dependencies.object]
version = "0.22.0"
version = "0.23.0"
optional = true
default-features = false
features = ['read_core', 'elf', 'std']

View File

@@ -1,6 +1,6 @@
//! Support for jitdump files which can be used by perf for profiling jitted code.
//! Spec definitions for the output format is as described here:
//! https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt
//! <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt>
//!
//! Usage Example:
//! Record

View File

@@ -14,7 +14,7 @@ edition = "2018"
[dependencies]
wasmtime-environ = { path = "../environ", version = "0.22.0" }
region = "2.1.0"
libc = { version = "0.2.70", default-features = false }
libc = { version = "0.2.82", default-features = false }
log = "0.4.8"
memoffset = "0.6.0"
indexmap = "1.0.2"

View File

@@ -1,14 +1,14 @@
use crate::vmcontext::{
VMCallerCheckedAnyfunc, VMContext, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
};
use crate::InstanceHandle;
use crate::RuntimeInstance;
use std::any::Any;
use std::ptr::NonNull;
use wasmtime_environ::wasm::Global;
use wasmtime_environ::{MemoryPlan, TablePlan};
/// The value of an export passed from one instance to another.
pub enum Export<'a> {
pub enum Export {
/// A function export value.
Function(ExportFunction),
@@ -22,10 +22,10 @@ pub enum Export<'a> {
Global(ExportGlobal),
/// An instance
Instance(&'a InstanceHandle),
Instance(RuntimeInstance),
/// A module
Module(&'a dyn Any),
Module(Box<dyn Any>),
}
/// A function export value.
@@ -38,8 +38,8 @@ pub struct ExportFunction {
pub anyfunc: NonNull<VMCallerCheckedAnyfunc>,
}
impl<'a> From<ExportFunction> for Export<'a> {
fn from(func: ExportFunction) -> Export<'a> {
impl From<ExportFunction> for Export {
fn from(func: ExportFunction) -> Export {
Export::Function(func)
}
}
@@ -55,8 +55,8 @@ pub struct ExportTable {
pub table: TablePlan,
}
impl<'a> From<ExportTable> for Export<'a> {
fn from(func: ExportTable) -> Export<'a> {
impl From<ExportTable> for Export {
fn from(func: ExportTable) -> Export {
Export::Table(func)
}
}
@@ -72,8 +72,8 @@ pub struct ExportMemory {
pub memory: MemoryPlan,
}
impl<'a> From<ExportMemory> for Export<'a> {
fn from(func: ExportMemory) -> Export<'a> {
impl From<ExportMemory> for Export {
fn from(func: ExportMemory) -> Export {
Export::Memory(func)
}
}
@@ -89,8 +89,8 @@ pub struct ExportGlobal {
pub global: Global,
}
impl<'a> From<ExportGlobal> for Export<'a> {
fn from(func: ExportGlobal) -> Export<'a> {
impl From<ExportGlobal> for Export {
fn from(func: ExportGlobal) -> Export {
Export::Global(func)
}
}

View File

@@ -97,7 +97,7 @@
//!
//! For more general information on deferred reference counting, see *An
//! Examination of Deferred Reference Counting and Cycle Detection* by Quinane:
//! https://openresearch-repository.anu.edu.au/bitstream/1885/42030/2/hon-thesis.pdf
//! <https://openresearch-repository.anu.edu.au/bitstream/1885/42030/2/hon-thesis.pdf>
use std::alloc::Layout;
use std::any::Any;

View File

@@ -1,8 +1,4 @@
use crate::vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
use crate::InstanceHandle;
use std::any::Any;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::{InstanceIndex, ModuleIndex};
/// Resolved import pointers.
///
@@ -28,15 +24,4 @@ pub struct Imports<'a> {
/// Resolved addresses for imported globals.
pub globals: &'a [VMGlobalImport],
/// Resolved imported instances.
pub instances: PrimaryMap<InstanceIndex, InstanceHandle>,
/// Resolved imported modules.
///
/// Note that `Box<Any>` here is chosen to allow the embedder of this crate
/// to pick an appropriate representation of what module type should be. For
/// example for the `wasmtime` crate it's `wasmtime::Module` but that's not
/// defined way down here in this low crate.
pub modules: PrimaryMap<ModuleIndex, Box<dyn Any>>,
}

View File

@@ -14,6 +14,7 @@ use crate::vmcontext::{
VMSharedSignatureIndex, VMTableDefinition, VMTableImport,
};
use crate::{ExportFunction, ExportGlobal, ExportMemory, ExportTable};
use indexmap::IndexMap;
use memoffset::offset_of;
use more_asserts::assert_lt;
use std::alloc::{self, Layout};
@@ -22,17 +23,22 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::Arc;
use std::{mem, ptr, slice};
use thiserror::Error;
use wasmtime_environ::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap};
use wasmtime_environ::wasm::{
DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex,
ElemIndex, EntityIndex, FuncIndex, GlobalIndex, GlobalInit, InstanceIndex, MemoryIndex,
ModuleIndex, SignatureIndex, TableElementType, TableIndex, WasmType,
ElemIndex, EntityIndex, FuncIndex, GlobalIndex, GlobalInit, MemoryIndex, SignatureIndex,
TableElementType, TableIndex, WasmType,
};
use wasmtime_environ::{ir, DataInitializer, Module, ModuleType, TableElements, VMOffsets};
/// Runtime representation of an instance value, which erases all `Instance`
/// information since instances are just a collection of values.
pub type RuntimeInstance = Rc<IndexMap<String, Export>>;
/// A WebAssembly instance.
///
/// This is repr(C) to ensure that the vmctx field is last.
@@ -50,15 +56,6 @@ pub(crate) struct Instance {
/// WebAssembly table data.
tables: BoxedSlice<DefinedTableIndex, Table>,
/// Instances our module defined and their handles.
instances: PrimaryMap<InstanceIndex, InstanceHandle>,
/// Modules that are located in our index space.
///
/// For now these are `Box<Any>` so the caller can define the type of what a
/// module looks like.
modules: PrimaryMap<ModuleIndex, Box<dyn Any>>,
/// Passive elements in this instantiation. As `elem.drop`s happen, these
/// entries get removed. A missing entry is considered equivalent to an
/// empty slice.
@@ -266,18 +263,8 @@ impl Instance {
self.vmctx() as *const VMContext as *mut VMContext
}
/// Lookup an export with the given name.
pub fn lookup(&self, field: &str) -> Option<Export> {
let export = if let Some(export) = self.module.exports.get(field) {
export.clone()
} else {
return None;
};
Some(self.lookup_by_declaration(&export))
}
/// Lookup an export with the given export declaration.
pub fn lookup_by_declaration(&self, export: &EntityIndex) -> Export<'_> {
pub fn lookup_by_declaration(&self, export: &EntityIndex) -> Export {
match export {
EntityIndex::Function(index) => {
let anyfunc = self.get_caller_checked_anyfunc(*index).unwrap();
@@ -326,8 +313,9 @@ impl Instance {
}
.into(),
EntityIndex::Instance(index) => Export::Instance(&self.instances[*index]),
EntityIndex::Module(index) => Export::Module(&*self.modules[*index]),
EntityIndex::Instance(_) | EntityIndex::Module(_) => {
panic!("can't use this api for modules/instances")
}
}
}
@@ -855,8 +843,6 @@ impl InstanceHandle {
passive_elements: Default::default(),
passive_data,
host_state,
instances: imports.instances,
modules: imports.modules,
vmctx: VMContext {},
};
let layout = instance.alloc_layout();
@@ -1015,11 +1001,6 @@ impl InstanceHandle {
self.instance().module()
}
/// Lookup an export with the given name.
pub fn lookup(&self, field: &str) -> Option<Export> {
self.instance().lookup(field)
}
/// Lookup an export with the given export declaration.
pub fn lookup_by_declaration(&self, export: &EntityIndex) -> Export {
self.instance().lookup_by_declaration(export)

View File

@@ -37,7 +37,7 @@ pub mod libcalls;
pub use crate::export::*;
pub use crate::externref::*;
pub use crate::imports::Imports;
pub use crate::instance::{InstanceHandle, InstantiationError, LinkError};
pub use crate::instance::{InstanceHandle, InstantiationError, LinkError, RuntimeInstance};
pub use crate::jit_int::GdbJitImageRegistration;
pub use crate::memory::{RuntimeLinearMemory, RuntimeMemoryCreator};
pub use crate::mmap::Mmap;

View File

@@ -581,3 +581,8 @@ pub unsafe extern "C" fn wasmtime_imported_memory_atomic_wait64(
"wasm atomics (fn wasmtime_imported_memory_atomic_wait64) unsupported",
))));
}
/// Hook for when an instance runs out of fuel.
pub unsafe extern "C" fn wasmtime_out_of_gas(_vmctx: *mut VMContext) {
crate::traphandlers::out_of_gas()
}

View File

@@ -164,9 +164,15 @@ cfg_if::cfg_if! {
} else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::ucontext_t);
cx.uc_mcontext.pc as *const u8
} else if #[cfg(target_os = "macos")] {
} else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(*cx.uc_mcontext).__ss.__rip as *const u8
} else if #[cfg(all(target_os = "macos", target_arch = "x86"))] {
let cx = &*(cx as *const libc::ucontext_t);
(*cx.uc_mcontext).__ss.__eip as *const u8
} else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(*cx.uc_mcontext).__ss.__pc as *const u8
} else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
cx.uc_mcontext.mc_rip as *const u8
@@ -404,6 +410,13 @@ pub fn with_last_info<R>(func: impl FnOnce(Option<&dyn Any>) -> R) -> R {
tls::with(|state| func(state.map(|s| s.trap_info.as_any())))
}
/// Invokes the contextually-defined context's out-of-gas function.
///
/// (basically delegates to `wasmtime::Store::out_of_gas`)
pub fn out_of_gas() {
tls::with(|state| state.unwrap().trap_info.out_of_gas())
}
/// Temporary state stored on the stack which is registered in the `tls` module
/// below for calls into wasm.
pub struct CallThreadState<'a> {
@@ -426,7 +439,7 @@ pub unsafe trait TrapInfo {
/// Returns whether the given program counter lies within wasm code,
/// indicating whether we should handle a trap or not.
fn is_wasm_code(&self, pc: usize) -> bool;
fn is_wasm_trap(&self, pc: usize) -> bool;
/// Uses `call` to call a custom signal handler, if one is specified.
///
@@ -436,6 +449,12 @@ pub unsafe trait TrapInfo {
/// Returns the maximum size, in bytes, the wasm native stack is allowed to
/// grow to.
fn max_wasm_stack(&self) -> usize;
/// Callback invoked whenever WebAssembly has entirely consumed the fuel
/// that it was allotted.
///
/// This function may return, and it may also `raise_lib_trap`.
fn out_of_gas(&self);
}
enum UnwindReason {
@@ -629,7 +648,7 @@ impl<'a> CallThreadState<'a> {
}
// If this fault wasn't in wasm code, then it's not our problem
if !self.trap_info.is_wasm_code(pc as usize) {
if !self.trap_info.is_wasm_trap(pc as usize) {
return ptr::null();
}

View File

@@ -4,6 +4,7 @@
use crate::externref::VMExternRef;
use crate::instance::Instance;
use std::any::Any;
use std::cell::UnsafeCell;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use std::u32;
@@ -612,6 +613,7 @@ impl VMBuiltinFunctionsArray {
wasmtime_memory_atomic_wait64 as usize;
ptrs[BuiltinFunctionIndex::imported_memory_atomic_wait64().index() as usize] =
wasmtime_imported_memory_atomic_wait64 as usize;
ptrs[BuiltinFunctionIndex::out_of_gas().index() as usize] = wasmtime_out_of_gas as usize;
if cfg!(debug_assertions) {
for i in 0..ptrs.len() {
@@ -658,8 +660,7 @@ impl VMInvokeArgument {
}
}
/// Structure used to control interrupting wasm code, currently with only one
/// atomic flag internally used.
/// Structure used to control interrupting wasm code.
#[derive(Debug)]
#[repr(C)]
pub struct VMInterrupts {
@@ -668,6 +669,14 @@ pub struct VMInterrupts {
/// This is used to control both stack overflow as well as interrupting wasm
/// modules. For more information see `crates/environ/src/cranelift.rs`.
pub stack_limit: AtomicUsize,
/// Indicator of how much fuel has been consumed and is remaining to
/// WebAssembly.
///
/// This field is typically negative and increments towards positive. Upon
/// turning positive a wasm trap will be generated. This field is only
/// modified if wasm is configured to consume fuel.
pub fuel_consumed: UnsafeCell<i64>,
}
impl VMInterrupts {
@@ -682,6 +691,7 @@ impl Default for VMInterrupts {
fn default() -> VMInterrupts {
VMInterrupts {
stack_limit: AtomicUsize::new(usize::max_value()),
fuel_consumed: UnsafeCell::new(0),
}
}
}

View File

@@ -21,7 +21,7 @@ tempfile = "3.1.0"
os_pipe = "0.9"
anyhow = "1.0.19"
wat = "1.0.23"
cap-std = "0.11"
cap-std = "0.12"
[features]
test_programs = []

View File

@@ -24,8 +24,8 @@ thiserror = "1.0"
wiggle = { path = "../wiggle", default-features = false, version = "0.22.0" }
tracing = "0.1.19"
system-interface = { version = "0.5.4", features = ["cap_std_impls"] }
cap-std = "0.11"
cap-rand = "0.11"
cap-std = "0.12"
cap-rand = "0.12"
bitflags = "1.2"
[target.'cfg(unix)'.dependencies]

View File

@@ -15,10 +15,10 @@ publish = false
[dependencies]
wasi-common = { path = "../", version = "0.22.0" }
anyhow = "1.0"
cap-std = "0.11"
cap-fs-ext = "0.11"
cap-time-ext = "0.11"
cap-rand = "0.11"
cap-std = "0.12"
cap-fs-ext = "0.12"
cap-time-ext = "0.12"
cap-rand = "0.12"
fs-set-times = "0.2.2"
unsafe-io = "0.3"
system-interface = { version = "0.5.4", features = ["cap_std_impls"] }

View File

@@ -0,0 +1,22 @@
[package]
name = "wasmtime-wasi-crypto"
version = "0.22.0"
authors = ["The Wasmtime Project Developers"]
description = "Wasmtime implementation of the wasi-crypto API"
documentation = "https://docs.rs/wasmtime-wasi-crypto"
license = "Apache-2.0 WITH LLVM-exception"
categories = ["wasm", "cryptography"]
keywords = ["webassembly", "wasm", "crypto"]
repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md"
edition = "2018"
[dependencies]
anyhow = "1.0"
wasi-crypto = { path = "spec/implementations/hostcalls/rust", version = "0.1.4" }
wasmtime = { path = "../wasmtime", version = "0.22.0", default-features = false }
wasmtime-wiggle = { path = "../wiggle/wasmtime", version = "0.22.0" }
wiggle = { path = "../wiggle", version = "0.22.0" }
[badges]
maintenance = { status = "experimental" }

220
crates/wasi-crypto/LICENSE Normal file
View File

@@ -0,0 +1,220 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

View File

@@ -0,0 +1,56 @@
# wasmtime-wasi-crypto
This crate enables support for the [wasi-crypto] APIs in Wasmtime.
The sole purpose of the implementation is to allow bindings and
application developers to test the proposed APIs. This implementation
is not meant to be used in production. Like the specification, it is
currently experimental and its functionality can quickly change.
Since the [wasi-crypto] API is expected to be an optional feature of
WASI, this crate is currently separate from the [wasi-common] crate.
* [documentation]
* [interfaces reference]
* [interfaces reference (compact)]
[wasi-crypto]: https://github.com/WebAssembly/wasi-crypto
[wasi-common]: ../../wasi-common
[documentation]: ../spec/docs/wasi-crypto.md
[interfaces reference]: ../spec/witx/wasi_ephemeral_crypto.md
[interfaces reference (compact)]: ../spec/witx/wasi_ephemeral_crypto.txt
## Wasmtime integration
Use the Wasmtime APIs to instantiate a Wasm module and link the
`wasi-crypto` modules as follows:
```rust
use wasmtime_wasi_crypto::{
WasiCryptoAsymmetricCommon, WasiCryptoCommon, WasiCryptoCtx, WasiCryptoSignatures,
WasiCryptoSymmetric,
};
let cx_crypto = WasiCryptoCtx::new();
WasiCryptoCommon::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?;
WasiCryptoAsymmetricCommon::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?;
WasiCryptoSignatures::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?;
WasiCryptoSymmetric::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?;
let wasi = wasmtime_wasi::old::snapshot_0::Wasi::new(linker.store(), mk_cx()?);
wasi.add_to_linker(linker)?;
```
## Building Wasmtime
Wasmtime must be compiled with the `wasi-crypto` feature flag
(disabled by default) in order to include the crypto APIs.
## Examples
Example [rust bindings] and [assemblyscript bindings] are provided to
demonstrate how these APIs can be used and exposed to applications in
an idiomatic way.
[rust bindings]: ../spec/implementations/bindings/rust
[assemblyscript bindings]: ../spec/implementations/bindings/assemblyscript

View File

@@ -0,0 +1,31 @@
mod wiggle_interfaces;
pub use wiggle_interfaces::WasiCryptoCtx;
wasmtime_wiggle::wasmtime_integration!({
target: wiggle_interfaces::wasi_modules,
witx: ["$CARGO_MANIFEST_DIR/spec/witx/wasi_ephemeral_crypto.witx"],
ctx: WasiCryptoCtx,
modules: {
wasi_ephemeral_crypto_common =>
{
name: WasiCryptoCommon,
docs: "wasi-crypto - Common module."
},
wasi_ephemeral_crypto_asymmetric_common =>
{
name: WasiCryptoAsymmetricCommon,
docs: "wasi-crypto - Common module for asymmetric operations."
},
wasi_ephemeral_crypto_signatures =>
{
name: WasiCryptoSignatures,
docs: "wasi-crypto - Signature module."
},
wasi_ephemeral_crypto_symmetric =>
{
name: WasiCryptoSymmetric,
docs: "wasi-crypto - Symmetric cryptography module."
}
}
});

View File

@@ -0,0 +1,292 @@
use super::{guest_types, WasiCryptoCtx};
use std::convert::TryInto;
use wasi_crypto::{ensure, CryptoError, KeyPairEncoding, PublicKeyEncoding, SecretKeyEncoding};
impl super::wasi_ephemeral_crypto_asymmetric_common::WasiEphemeralCryptoAsymmetricCommon
for WasiCryptoCtx
{
// --- keypair_manager
fn keypair_generate_managed(
&self,
secrets_manager_handle: guest_types::SecretsManager,
alg_type: guest_types::AlgorithmType,
alg_str: &wiggle::GuestPtr<'_, str>,
options_handle: &guest_types::OptOptions,
) -> Result<guest_types::Keypair, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let options_handle = match *options_handle {
guest_types::OptOptions::Some(options_handle) => Some(options_handle),
guest_types::OptOptions::None => None,
};
Ok(self
.ctx
.keypair_generate_managed(
secrets_manager_handle.into(),
alg_type.into(),
alg_str,
options_handle.map(Into::into),
)?
.into())
}
fn keypair_store_managed(
&self,
secrets_manager_handle: guest_types::SecretsManager,
kp_handle: guest_types::Keypair,
kp_id_ptr: &wiggle::GuestPtr<'_, u8>,
kp_id_max_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let key_id_buf = &mut *kp_id_ptr.as_array(kp_id_max_len).as_slice_mut()?;
Ok(self.ctx.keypair_store_managed(
secrets_manager_handle.into(),
kp_handle.into(),
key_id_buf,
)?)
}
fn keypair_replace_managed(
&self,
secrets_manager_handle: guest_types::SecretsManager,
kp_old_handle: guest_types::Keypair,
kp_new_handle: guest_types::Keypair,
) -> Result<guest_types::Version, guest_types::CryptoErrno> {
Ok(self
.ctx
.keypair_replace_managed(
secrets_manager_handle.into(),
kp_old_handle.into(),
kp_new_handle.into(),
)?
.into())
}
fn keypair_from_id(
&self,
secrets_manager_handle: guest_types::SecretsManager,
kp_id_ptr: &wiggle::GuestPtr<'_, u8>,
kp_id_len: guest_types::Size,
kp_version: guest_types::Version,
) -> Result<guest_types::Keypair, guest_types::CryptoErrno> {
let kp_id = &*kp_id_ptr.as_array(kp_id_len).as_slice()?;
Ok(self
.ctx
.keypair_from_id(secrets_manager_handle.into(), kp_id, kp_version.into())?
.into())
}
// --- keypair
fn keypair_generate(
&self,
alg_type: guest_types::AlgorithmType,
alg_str: &wiggle::GuestPtr<'_, str>,
options_handle: &guest_types::OptOptions,
) -> Result<guest_types::Keypair, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let options_handle = match *options_handle {
guest_types::OptOptions::Some(options_handle) => Some(options_handle),
guest_types::OptOptions::None => None,
};
Ok(self
.ctx
.keypair_generate(alg_type.into(), alg_str, options_handle.map(Into::into))?
.into())
}
fn keypair_import(
&self,
alg_type: guest_types::AlgorithmType,
alg_str: &wiggle::GuestPtr<'_, str>,
encoded_ptr: &wiggle::GuestPtr<'_, u8>,
encoded_len: guest_types::Size,
encoding: guest_types::KeypairEncoding,
) -> Result<guest_types::Keypair, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let encoded = &*encoded_ptr.as_array(encoded_len).as_slice()?;
Ok(self
.ctx
.keypair_import(alg_type.into(), alg_str, encoded, encoding.into())?
.into())
}
fn keypair_id(
&self,
kp_handle: guest_types::Keypair,
kp_id_ptr: &wiggle::GuestPtr<'_, u8>,
kp_id_max_len: guest_types::Size,
) -> Result<(guest_types::Size, guest_types::Version), guest_types::CryptoErrno> {
let kp_id_buf = &mut *kp_id_ptr.as_array(kp_id_max_len as _).as_slice_mut()?;
let (kp_id, version) = self.ctx.keypair_id(kp_handle.into())?;
ensure!(kp_id.len() <= kp_id_buf.len(), CryptoError::Overflow.into());
kp_id_buf.copy_from_slice(&kp_id);
Ok((kp_id.len().try_into()?, version.into()))
}
fn keypair_export(
&self,
kp_handle: guest_types::Keypair,
encoding: guest_types::KeypairEncoding,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
Ok(self
.ctx
.keypair_export(kp_handle.into(), encoding.into())?
.into())
}
fn keypair_publickey(
&self,
kp_handle: guest_types::Keypair,
) -> Result<guest_types::Publickey, guest_types::CryptoErrno> {
Ok(self.ctx.keypair_publickey(kp_handle.into())?.into())
}
fn keypair_close(
&self,
kp_handle: guest_types::Keypair,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.keypair_close(kp_handle.into())?)
}
// --- publickey
fn publickey_import(
&self,
alg_type: guest_types::AlgorithmType,
alg_str: &wiggle::GuestPtr<'_, str>,
encoded_ptr: &wiggle::GuestPtr<'_, u8>,
encoded_len: guest_types::Size,
encoding: guest_types::PublickeyEncoding,
) -> Result<guest_types::Publickey, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let encoded = &*encoded_ptr.as_array(encoded_len).as_slice()?;
Ok(self
.ctx
.publickey_import(alg_type.into(), alg_str, encoded, encoding.into())?
.into())
}
fn publickey_export(
&self,
pk_handle: guest_types::Publickey,
encoding: guest_types::PublickeyEncoding,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
Ok(self
.ctx
.publickey_export(pk_handle.into(), encoding.into())?
.into())
}
fn publickey_from_secretkey(
&self,
sk_handle: guest_types::Secretkey,
) -> Result<guest_types::Publickey, guest_types::CryptoErrno> {
Ok(self.ctx.keypair_publickey(sk_handle.into())?.into())
}
fn publickey_verify(
&self,
pk_handle: guest_types::Publickey,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.publickey_verify(pk_handle.into())?)
}
fn publickey_close(
&self,
pk_handle: guest_types::Publickey,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.publickey_close(pk_handle.into())?)
}
// --- secretkey
fn secretkey_import(
&self,
alg_type: guest_types::AlgorithmType,
alg_str: &wiggle::GuestPtr<'_, str>,
encoded_ptr: &wiggle::GuestPtr<'_, u8>,
encoded_len: guest_types::Size,
encoding: guest_types::SecretkeyEncoding,
) -> Result<guest_types::Secretkey, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let encoded = &*encoded_ptr.as_array(encoded_len).as_slice()?;
Ok(self
.ctx
.secretkey_import(alg_type.into(), alg_str, encoded, encoding.into())?
.into())
}
fn secretkey_export(
&self,
sk_handle: guest_types::Secretkey,
encoding: guest_types::SecretkeyEncoding,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
Ok(self
.ctx
.secretkey_export(sk_handle.into(), encoding.into())?
.into())
}
fn secretkey_close(
&self,
sk_handle: guest_types::Secretkey,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.secretkey_close(sk_handle.into())?)
}
fn keypair_from_pk_and_sk(
&self,
pk_handle: guest_types::Publickey,
sk_handle: guest_types::Secretkey,
) -> Result<guest_types::Keypair, guest_types::CryptoErrno> {
Ok(self
.ctx
.keypair_from_pk_and_sk(pk_handle.into(), sk_handle.into())?
.into())
}
fn keypair_secretkey(
&self,
kp_handle: guest_types::Keypair,
) -> Result<guest_types::Secretkey, guest_types::CryptoErrno> {
Ok(self.ctx.keypair_secretkey(kp_handle.into())?.into())
}
}
impl From<guest_types::KeypairEncoding> for KeyPairEncoding {
fn from(encoding: guest_types::KeypairEncoding) -> Self {
match encoding {
guest_types::KeypairEncoding::Raw => KeyPairEncoding::Raw,
guest_types::KeypairEncoding::Pkcs8 => KeyPairEncoding::Pkcs8,
guest_types::KeypairEncoding::Pem => KeyPairEncoding::Pem,
guest_types::KeypairEncoding::Local => KeyPairEncoding::Local,
}
}
}
impl From<guest_types::PublickeyEncoding> for PublicKeyEncoding {
fn from(encoding: guest_types::PublickeyEncoding) -> Self {
match encoding {
guest_types::PublickeyEncoding::Raw => PublicKeyEncoding::Raw,
guest_types::PublickeyEncoding::Pkcs8 => PublicKeyEncoding::Pkcs8,
guest_types::PublickeyEncoding::Pem => PublicKeyEncoding::Pem,
guest_types::PublickeyEncoding::Sec => PublicKeyEncoding::Sec,
guest_types::PublickeyEncoding::CompressedSec => PublicKeyEncoding::CompressedSec,
guest_types::PublickeyEncoding::Local => PublicKeyEncoding::Local,
}
}
}
impl From<guest_types::SecretkeyEncoding> for SecretKeyEncoding {
fn from(encoding: guest_types::SecretkeyEncoding) -> Self {
match encoding {
guest_types::SecretkeyEncoding::Raw => SecretKeyEncoding::Raw,
guest_types::SecretkeyEncoding::Pkcs8 => SecretKeyEncoding::Pkcs8,
guest_types::SecretkeyEncoding::Pem => SecretKeyEncoding::Pem,
guest_types::SecretkeyEncoding::Sec => SecretKeyEncoding::Sec,
guest_types::SecretkeyEncoding::CompressedSec => SecretKeyEncoding::CompressedSec,
guest_types::SecretkeyEncoding::Local => SecretKeyEncoding::Local,
}
}
}

View File

@@ -0,0 +1,150 @@
use super::{guest_types, WasiCryptoCtx};
use std::convert::TryInto;
use wasi_crypto::{AlgorithmType, Version};
impl super::wasi_ephemeral_crypto_common::WasiEphemeralCryptoCommon for WasiCryptoCtx {
// --- options
fn options_open(
&self,
options_type: guest_types::AlgorithmType,
) -> Result<guest_types::Options, guest_types::CryptoErrno> {
Ok(self.ctx.options_open(options_type.into())?.into())
}
fn options_close(
&self,
options_handle: guest_types::Options,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.options_close(options_handle.into())?)
}
fn options_set(
&self,
options_handle: guest_types::Options,
name_str: &wiggle::GuestPtr<'_, str>,
value_ptr: &wiggle::GuestPtr<'_, u8>,
value_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let name_str: &str = &*name_str.as_str()?;
let value: &[u8] = { &*value_ptr.as_array(value_len).as_slice()? };
Ok(self
.ctx
.options_set(options_handle.into(), name_str, value)?)
}
fn options_set_guest_buffer(
&self,
options_handle: guest_types::Options,
name_str: &wiggle::GuestPtr<'_, str>,
buffer_ptr: &wiggle::GuestPtr<'_, u8>,
buffer_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let name_str: &str = &*name_str.as_str()?;
let buffer: &'static mut [u8] =
unsafe { std::mem::transmute(&mut *buffer_ptr.as_array(buffer_len).as_slice_mut()?) };
Ok(self
.ctx
.options_set_guest_buffer(options_handle.into(), name_str, buffer)?)
}
fn options_set_u64(
&self,
options_handle: guest_types::Options,
name_str: &wiggle::GuestPtr<'_, str>,
value: u64,
) -> Result<(), guest_types::CryptoErrno> {
let name_str: &str = &*name_str.as_str()?;
Ok(self
.ctx
.options_set_u64(options_handle.into(), name_str, value)?)
}
// --- array
fn array_output_len(
&self,
array_output_handle: guest_types::ArrayOutput,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
Ok(self
.ctx
.array_output_len(array_output_handle.into())?
.try_into()?)
}
fn array_output_pull(
&self,
array_output_handle: guest_types::ArrayOutput,
buf_ptr: &wiggle::GuestPtr<'_, u8>,
buf_len: guest_types::Size,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
let buf: &mut [u8] = { &mut *buf_ptr.as_array(buf_len).as_slice_mut()? };
Ok(self
.ctx
.array_output_pull(array_output_handle.into(), buf)?
.try_into()?)
}
// --- secrets_manager
fn secrets_manager_open(
&self,
options_handle: &guest_types::OptOptions,
) -> Result<guest_types::SecretsManager, guest_types::CryptoErrno> {
let options_handle = match *options_handle {
guest_types::OptOptions::Some(options_handle) => Some(options_handle),
guest_types::OptOptions::None => None,
};
Ok(self
.ctx
.secrets_manager_open(options_handle.map(Into::into))?
.into())
}
fn secrets_manager_close(
&self,
secrets_manager_handle: guest_types::SecretsManager,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self
.ctx
.secrets_manager_close(secrets_manager_handle.into())?)
}
fn secrets_manager_invalidate(
&self,
secrets_manager_handle: guest_types::SecretsManager,
key_id_ptr: &wiggle::GuestPtr<'_, u8>,
key_id_len: guest_types::Size,
key_version: guest_types::Version,
) -> Result<(), guest_types::CryptoErrno> {
let key_id: &[u8] = { &*key_id_ptr.as_array(key_id_len).as_slice()? };
Ok(self.ctx.secrets_manager_invalidate(
secrets_manager_handle.into(),
key_id,
key_version.into(),
)?)
}
}
impl From<guest_types::AlgorithmType> for AlgorithmType {
fn from(options_type: guest_types::AlgorithmType) -> Self {
match options_type {
guest_types::AlgorithmType::Signatures => AlgorithmType::Signatures,
guest_types::AlgorithmType::Symmetric => AlgorithmType::Symmetric,
guest_types::AlgorithmType::KeyExchange => AlgorithmType::KeyExchange,
}
}
}
impl From<guest_types::Version> for Version {
fn from(version: guest_types::Version) -> Self {
Version(version.into())
}
}
impl From<Version> for guest_types::Version {
fn from(version: Version) -> Self {
version.into()
}
}

View File

@@ -0,0 +1,67 @@
use super::{guest_types, WasiCryptoCtx};
use std::num::TryFromIntError;
use wasi_crypto::CryptoError;
impl From<CryptoError> for guest_types::CryptoErrno {
fn from(e: CryptoError) -> Self {
match e {
CryptoError::Success => guest_types::CryptoErrno::Success,
CryptoError::GuestError(_wiggle_error) => guest_types::CryptoErrno::GuestError,
CryptoError::NotImplemented => guest_types::CryptoErrno::NotImplemented,
CryptoError::UnsupportedFeature => guest_types::CryptoErrno::UnsupportedFeature,
CryptoError::ProhibitedOperation => guest_types::CryptoErrno::ProhibitedOperation,
CryptoError::UnsupportedEncoding => guest_types::CryptoErrno::UnsupportedEncoding,
CryptoError::UnsupportedAlgorithm => guest_types::CryptoErrno::UnsupportedAlgorithm,
CryptoError::UnsupportedOption => guest_types::CryptoErrno::UnsupportedOption,
CryptoError::InvalidKey => guest_types::CryptoErrno::InvalidKey,
CryptoError::InvalidLength => guest_types::CryptoErrno::InvalidLength,
CryptoError::VerificationFailed => guest_types::CryptoErrno::VerificationFailed,
CryptoError::RNGError => guest_types::CryptoErrno::RngError,
CryptoError::AlgorithmFailure => guest_types::CryptoErrno::AlgorithmFailure,
CryptoError::InvalidSignature => guest_types::CryptoErrno::InvalidSignature,
CryptoError::Closed => guest_types::CryptoErrno::Closed,
CryptoError::InvalidHandle => guest_types::CryptoErrno::InvalidHandle,
CryptoError::Overflow => guest_types::CryptoErrno::Overflow,
CryptoError::InternalError => guest_types::CryptoErrno::InternalError,
CryptoError::TooManyHandles => guest_types::CryptoErrno::TooManyHandles,
CryptoError::KeyNotSupported => guest_types::CryptoErrno::KeyNotSupported,
CryptoError::KeyRequired => guest_types::CryptoErrno::KeyRequired,
CryptoError::InvalidTag => guest_types::CryptoErrno::InvalidTag,
CryptoError::InvalidOperation => guest_types::CryptoErrno::InvalidOperation,
CryptoError::NonceRequired => guest_types::CryptoErrno::NonceRequired,
CryptoError::InvalidNonce => guest_types::CryptoErrno::InvalidNonce,
CryptoError::OptionNotSet => guest_types::CryptoErrno::OptionNotSet,
CryptoError::NotFound => guest_types::CryptoErrno::NotFound,
CryptoError::ParametersMissing => guest_types::CryptoErrno::ParametersMissing,
CryptoError::IncompatibleKeys => guest_types::CryptoErrno::IncompatibleKeys,
CryptoError::Expired => guest_types::CryptoErrno::Expired,
}
}
}
impl From<TryFromIntError> for guest_types::CryptoErrno {
fn from(_: TryFromIntError) -> Self {
CryptoError::Overflow.into()
}
}
impl<'a> wiggle::GuestErrorType for guest_types::CryptoErrno {
fn success() -> Self {
guest_types::CryptoErrno::Success
}
}
impl guest_types::GuestErrorConversion for WasiCryptoCtx {
fn into_crypto_errno(&self, e: wiggle::GuestError) -> guest_types::CryptoErrno {
eprintln!("GuestError (witx) {:?}", e);
guest_types::CryptoErrno::GuestError
}
}
impl From<wiggle::GuestError> for guest_types::CryptoErrno {
fn from(e: wiggle::GuestError) -> Self {
eprintln!("GuestError (impl) {:?}", e);
guest_types::CryptoErrno::GuestError
}
}

View File

@@ -0,0 +1,40 @@
use super::{guest_types, WasiCryptoCtx};
impl super::wasi_ephemeral_crypto_kx::WasiEphemeralCryptoKx for WasiCryptoCtx {
// --- key exchange
fn kx_dh(
&self,
pk_handle: guest_types::Publickey,
sk_handle: guest_types::Secretkey,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
Ok(self.ctx.kx_dh(pk_handle.into(), sk_handle.into())?.into())
}
// --- Key encapsulation
fn kx_encapsulate(
&self,
pk_handle: guest_types::Publickey,
) -> Result<(guest_types::ArrayOutput, guest_types::ArrayOutput), guest_types::CryptoErrno>
{
let (secret_handle, encapsulated_secret_handle) =
self.ctx.kx_encapsulate(pk_handle.into())?;
Ok((secret_handle.into(), encapsulated_secret_handle.into()))
}
fn kx_decapsulate(
&self,
sk_handle: guest_types::Secretkey,
encapsulated_secret_ptr: &wiggle::GuestPtr<'_, u8>,
encapsulated_secret_len: guest_types::Size,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
let encapsulated_secret = &*encapsulated_secret_ptr
.as_array(encapsulated_secret_len)
.as_slice()?;
Ok(self
.ctx
.kx_decapsulate(sk_handle.into(), encapsulated_secret)?
.into())
}
}

View File

@@ -0,0 +1,38 @@
use std::rc::Rc;
use wasi_crypto::CryptoCtx;
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/spec/witx/wasi_ephemeral_crypto.witx"],
ctx: WasiCryptoCtx
});
pub mod wasi_modules {
pub use super::{
wasi_ephemeral_crypto_asymmetric_common, wasi_ephemeral_crypto_common,
wasi_ephemeral_crypto_kx, wasi_ephemeral_crypto_signatures,
wasi_ephemeral_crypto_symmetric,
};
}
pub use types as guest_types;
#[derive(Clone)]
pub struct WasiCryptoCtx {
ctx: Rc<CryptoCtx>,
}
impl WasiCryptoCtx {
pub fn new() -> Self {
WasiCryptoCtx {
ctx: Rc::new(CryptoCtx::new()),
}
}
}
mod asymmetric_common;
mod common;
mod error;
mod key_exchange;
mod signatures;
mod symmetric;

View File

@@ -0,0 +1,129 @@
use super::{guest_types, WasiCryptoCtx};
use wasi_crypto::SignatureEncoding;
impl super::wasi_ephemeral_crypto_signatures::WasiEphemeralCryptoSignatures for WasiCryptoCtx {
// --- signature
fn signature_export(
&self,
signature_handle: guest_types::Signature,
encoding: guest_types::SignatureEncoding,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
Ok(self
.ctx
.signature_export(signature_handle.into(), encoding.into())?
.into())
}
fn signature_import(
&self,
alg_str: &wiggle::GuestPtr<'_, str>,
encoded_ptr: &wiggle::GuestPtr<'_, u8>,
encoded_len: guest_types::Size,
encoding: guest_types::SignatureEncoding,
) -> Result<guest_types::Signature, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let encoded = &*encoded_ptr.as_array(encoded_len).as_slice()?;
Ok(self
.ctx
.signature_import(alg_str, encoded, encoding.into())?
.into())
}
fn signature_state_open(
&self,
kp_handle: guest_types::Keypair,
) -> Result<guest_types::SignatureState, guest_types::CryptoErrno> {
Ok(self.ctx.signature_state_open(kp_handle.into())?.into())
}
fn signature_state_update(
&self,
state_handle: guest_types::SignatureState,
input_ptr: &wiggle::GuestPtr<'_, u8>,
input_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let input = &*input_ptr.as_array(input_len).as_slice()?;
Ok(self
.ctx
.signature_state_update(state_handle.into(), input)?)
}
fn signature_state_sign(
&self,
signature_state_handle: guest_types::SignatureState,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
Ok(self
.ctx
.signature_state_sign(signature_state_handle.into())?
.into())
}
fn signature_state_close(
&self,
signature_state_handle: guest_types::SignatureState,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self
.ctx
.signature_state_close(signature_state_handle.into())?)
}
fn signature_verification_state_open(
&self,
pk_handle: guest_types::Publickey,
) -> Result<guest_types::SignatureVerificationState, guest_types::CryptoErrno> {
Ok(self
.ctx
.signature_verification_state_open(pk_handle.into())?
.into())
}
fn signature_verification_state_update(
&self,
verification_state_handle: guest_types::SignatureVerificationState,
input_ptr: &wiggle::GuestPtr<'_, u8>,
input_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let input: &[u8] = &*input_ptr.as_array(input_len).as_slice()?;
Ok(self
.ctx
.signature_verification_state_update(verification_state_handle.into(), input)?)
}
fn signature_verification_state_verify(
&self,
verification_state_handle: guest_types::SignatureVerificationState,
signature_handle: guest_types::Signature,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.signature_verification_state_verify(
verification_state_handle.into(),
signature_handle.into(),
)?)
}
fn signature_verification_state_close(
&self,
verification_state_handle: guest_types::SignatureVerificationState,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self
.ctx
.signature_verification_state_close(verification_state_handle.into())?)
}
fn signature_close(
&self,
signature_handle: guest_types::Signature,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.signature_close(signature_handle.into())?)
}
}
impl From<guest_types::SignatureEncoding> for SignatureEncoding {
fn from(encoding: guest_types::SignatureEncoding) -> Self {
match encoding {
guest_types::SignatureEncoding::Raw => SignatureEncoding::Raw,
guest_types::SignatureEncoding::Der => SignatureEncoding::Der,
}
}
}

View File

@@ -0,0 +1,384 @@
use super::{guest_types, WasiCryptoCtx};
use std::convert::TryInto;
use wasi_crypto::{ensure, CryptoError};
impl super::wasi_ephemeral_crypto_symmetric::WasiEphemeralCryptoSymmetric for WasiCryptoCtx {
// --- secrets_manager
fn symmetric_key_generate_managed(
&self,
secrets_manager_handle: guest_types::SecretsManager,
alg_str: &wiggle::GuestPtr<'_, str>,
options_handle: &guest_types::OptOptions,
) -> Result<guest_types::SymmetricKey, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let options_handle = match *options_handle {
guest_types::OptOptions::Some(options_handle) => Some(options_handle),
guest_types::OptOptions::None => None,
};
Ok(self
.ctx
.symmetric_key_generate_managed(
secrets_manager_handle.into(),
alg_str,
options_handle.map(Into::into),
)?
.into())
}
fn symmetric_key_store_managed(
&self,
secrets_manager_handle: guest_types::SecretsManager,
symmetric_key_handle: guest_types::SymmetricKey,
symmetric_key_id_ptr: &wiggle::GuestPtr<'_, u8>,
symmetric_key_id_max_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let key_id_buf = &mut *symmetric_key_id_ptr
.as_array(symmetric_key_id_max_len)
.as_slice_mut()?;
Ok(self.ctx.symmetric_key_store_managed(
secrets_manager_handle.into(),
symmetric_key_handle.into(),
key_id_buf,
)?)
}
fn symmetric_key_replace_managed(
&self,
secrets_manager_handle: guest_types::SecretsManager,
symmetric_key_old_handle: guest_types::SymmetricKey,
symmetric_key_new_handle: guest_types::SymmetricKey,
) -> Result<guest_types::Version, guest_types::CryptoErrno> {
Ok(self
.ctx
.symmetric_key_replace_managed(
secrets_manager_handle.into(),
symmetric_key_old_handle.into(),
symmetric_key_new_handle.into(),
)?
.into())
}
fn symmetric_key_from_id(
&self,
secrets_manager_handle: guest_types::SecretsManager,
symmetric_key_id_ptr: &wiggle::GuestPtr<'_, u8>,
symmetric_key_id_len: guest_types::Size,
symmetric_key_version: guest_types::Version,
) -> Result<guest_types::SymmetricKey, guest_types::CryptoErrno> {
let symmetric_key_id = &*symmetric_key_id_ptr
.as_array(symmetric_key_id_len)
.as_slice()?;
Ok(self
.ctx
.symmetric_key_from_id(
secrets_manager_handle.into(),
symmetric_key_id,
symmetric_key_version.into(),
)?
.into())
}
// --- key
fn symmetric_key_generate(
&self,
alg_str: &wiggle::GuestPtr<'_, str>,
options_handle: &guest_types::OptOptions,
) -> Result<guest_types::SymmetricKey, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let options_handle = match *options_handle {
guest_types::OptOptions::Some(options_handle) => Some(options_handle),
guest_types::OptOptions::None => None,
};
Ok(self
.ctx
.symmetric_key_generate(alg_str, options_handle.map(Into::into))?
.into())
}
fn symmetric_key_import(
&self,
alg_str: &wiggle::GuestPtr<'_, str>,
raw_ptr: &wiggle::GuestPtr<'_, u8>,
raw_len: guest_types::Size,
) -> Result<guest_types::SymmetricKey, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let raw = &*raw_ptr.as_array(raw_len).as_slice()?;
Ok(self.ctx.symmetric_key_import(alg_str, raw)?.into())
}
fn symmetric_key_export(
&self,
symmetric_key_handle: guest_types::SymmetricKey,
) -> Result<guest_types::ArrayOutput, guest_types::CryptoErrno> {
Ok(self
.ctx
.symmetric_key_export(symmetric_key_handle.into())?
.into())
}
fn symmetric_key_id(
&self,
symmetric_key_handle: guest_types::SymmetricKey,
symmetric_key_id_ptr: &wiggle::GuestPtr<'_, u8>,
symmetric_key_id_max_len: guest_types::Size,
) -> Result<(guest_types::Size, guest_types::Version), guest_types::CryptoErrno> {
let key_id_buf = &mut *symmetric_key_id_ptr
.as_array(symmetric_key_id_max_len)
.as_slice_mut()?;
let (key_id, version) = self.ctx.symmetric_key_id(symmetric_key_handle.into())?;
ensure!(
key_id.len() <= key_id_buf.len(),
CryptoError::Overflow.into()
);
key_id_buf.copy_from_slice(&key_id);
Ok((key_id.len().try_into()?, version.into()))
}
fn symmetric_key_close(
&self,
key_handle: guest_types::SymmetricKey,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.symmetric_key_close(key_handle.into())?)
}
// --- state
fn symmetric_state_open(
&self,
alg_str: &wiggle::GuestPtr<'_, str>,
key_handle: &guest_types::OptSymmetricKey,
options_handle: &guest_types::OptOptions,
) -> Result<guest_types::SymmetricState, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
let key_handle = match *key_handle {
guest_types::OptSymmetricKey::Some(key_handle) => Some(key_handle),
guest_types::OptSymmetricKey::None => None,
};
let options_handle = match *options_handle {
guest_types::OptOptions::Some(options_handle) => Some(options_handle),
guest_types::OptOptions::None => None,
};
Ok(self
.ctx
.symmetric_state_open(
alg_str,
key_handle.map(Into::into),
options_handle.map(Into::into),
)?
.into())
}
fn symmetric_state_options_get(
&self,
symmetric_state_handle: guest_types::SymmetricState,
name_str: &wiggle::GuestPtr<'_, str>,
value_ptr: &wiggle::GuestPtr<'_, u8>,
value_max_len: guest_types::Size,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
let name_str: &str = &*name_str.as_str()?;
let value = &mut *value_ptr.as_array(value_max_len).as_slice_mut()?;
Ok(self
.ctx
.options_get(symmetric_state_handle.into(), name_str, value)?
.try_into()?)
}
fn symmetric_state_options_get_u64(
&self,
symmetric_state_handle: guest_types::SymmetricState,
name_str: &wiggle::GuestPtr<'_, str>,
) -> Result<u64, guest_types::CryptoErrno> {
let name_str: &str = &*name_str.as_str()?;
Ok(self
.ctx
.options_get_u64(symmetric_state_handle.into(), name_str)?)
}
fn symmetric_state_close(
&self,
symmetric_state_handle: guest_types::SymmetricState,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self
.ctx
.symmetric_state_close(symmetric_state_handle.into())?)
}
fn symmetric_state_absorb(
&self,
symmetric_state_handle: guest_types::SymmetricState,
data_ptr: &wiggle::GuestPtr<'_, u8>,
data_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let data = &*data_ptr.as_array(data_len).as_slice()?;
Ok(self
.ctx
.symmetric_state_absorb(symmetric_state_handle.into(), data)?)
}
fn symmetric_state_squeeze(
&self,
symmetric_state_handle: guest_types::SymmetricState,
out_ptr: &wiggle::GuestPtr<'_, u8>,
out_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let out = &mut *out_ptr.as_array(out_len).as_slice_mut()?;
Ok(self
.ctx
.symmetric_state_squeeze(symmetric_state_handle.into(), out)?)
}
fn symmetric_state_squeeze_tag(
&self,
symmetric_state_handle: guest_types::SymmetricState,
) -> Result<guest_types::SymmetricTag, guest_types::CryptoErrno> {
Ok(self
.ctx
.symmetric_state_squeeze_tag(symmetric_state_handle.into())?
.into())
}
fn symmetric_state_squeeze_key(
&self,
symmetric_state_handle: guest_types::SymmetricState,
alg_str: &wiggle::GuestPtr<'_, str>,
) -> Result<guest_types::SymmetricKey, guest_types::CryptoErrno> {
let alg_str = &*alg_str.as_str()?;
Ok(self
.ctx
.symmetric_state_squeeze_key(symmetric_state_handle.into(), alg_str)?
.into())
}
fn symmetric_state_max_tag_len(
&self,
symmetric_state_handle: guest_types::SymmetricState,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
Ok(self
.ctx
.symmetric_state_max_tag_len(symmetric_state_handle.into())?
.try_into()?)
}
fn symmetric_state_encrypt(
&self,
symmetric_state_handle: guest_types::SymmetricState,
out_ptr: &wiggle::GuestPtr<'_, u8>,
out_len: guest_types::Size,
data_ptr: &wiggle::GuestPtr<'_, u8>,
data_len: guest_types::Size,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
let out = &mut *out_ptr.as_array(out_len).as_slice_mut()?;
let data = &*data_ptr.as_array(data_len).as_slice()?;
Ok(self
.ctx
.symmetric_state_encrypt(symmetric_state_handle.into(), out, data)?
.try_into()?)
}
fn symmetric_state_encrypt_detached(
&self,
symmetric_state_handle: guest_types::SymmetricState,
out_ptr: &wiggle::GuestPtr<'_, u8>,
out_len: guest_types::Size,
data_ptr: &wiggle::GuestPtr<'_, u8>,
data_len: guest_types::Size,
) -> Result<guest_types::SymmetricTag, guest_types::CryptoErrno> {
let out = &mut *out_ptr.as_array(out_len).as_slice_mut()?;
let data = &*data_ptr.as_array(data_len).as_slice()?;
Ok(self
.ctx
.symmetric_state_encrypt_detached(symmetric_state_handle.into(), out, data)?
.into())
}
fn symmetric_state_decrypt(
&self,
symmetric_state_handle: guest_types::SymmetricState,
out_ptr: &wiggle::GuestPtr<'_, u8>,
out_len: guest_types::Size,
data_ptr: &wiggle::GuestPtr<'_, u8>,
data_len: guest_types::Size,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
let out = &mut *out_ptr.as_array(out_len).as_slice_mut()?;
let data = &*data_ptr.as_array(data_len).as_slice()?;
Ok(self
.ctx
.symmetric_state_decrypt(symmetric_state_handle.into(), out, data)?
.try_into()?)
}
fn symmetric_state_decrypt_detached(
&self,
symmetric_state_handle: guest_types::SymmetricState,
out_ptr: &wiggle::GuestPtr<'_, u8>,
out_len: guest_types::Size,
data_ptr: &wiggle::GuestPtr<'_, u8>,
data_len: guest_types::Size,
raw_tag_ptr: &wiggle::GuestPtr<'_, u8>,
raw_tag_len: guest_types::Size,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
let out = &mut *out_ptr.as_array(out_len).as_slice_mut()?;
let data = &*data_ptr.as_array(data_len).as_slice()?;
let raw_tag: &[u8] = &*raw_tag_ptr.as_array(raw_tag_len).as_slice()?;
Ok(self
.ctx
.symmetric_state_decrypt_detached(symmetric_state_handle.into(), out, data, raw_tag)?
.try_into()?)
}
fn symmetric_state_ratchet(
&self,
symmetric_state_handle: guest_types::SymmetricState,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self
.ctx
.symmetric_state_ratchet(symmetric_state_handle.into())?)
}
// --- tag
fn symmetric_tag_len(
&self,
symmetric_tag_handle: guest_types::SymmetricTag,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
Ok(self
.ctx
.symmetric_tag_len(symmetric_tag_handle.into())?
.try_into()?)
}
fn symmetric_tag_pull(
&self,
symmetric_tag_handle: guest_types::SymmetricTag,
buf_ptr: &wiggle::GuestPtr<'_, u8>,
buf_len: guest_types::Size,
) -> Result<guest_types::Size, guest_types::CryptoErrno> {
let buf = &mut *buf_ptr.as_array(buf_len).as_slice_mut()?;
Ok(self
.ctx
.symmetric_tag_pull(symmetric_tag_handle.into(), buf)?
.try_into()?)
}
fn symmetric_tag_verify(
&self,
symmetric_tag_handle: guest_types::SymmetricTag,
expected_raw_ptr: &wiggle::GuestPtr<'_, u8>,
expected_raw_len: guest_types::Size,
) -> Result<(), guest_types::CryptoErrno> {
let expected_raw = &*expected_raw_ptr.as_array(expected_raw_len).as_slice()?;
Ok(self
.ctx
.symmetric_tag_verify(symmetric_tag_handle.into(), expected_raw)?)
}
fn symmetric_tag_close(
&self,
symmetric_tag_handle: guest_types::SymmetricTag,
) -> Result<(), guest_types::CryptoErrno> {
Ok(self.ctx.symmetric_tag_close(symmetric_tag_handle.into())?)
}
}

View File

@@ -16,7 +16,7 @@ wasmtime-jit = { path = "../jit", version = "0.22.0" }
wasmtime-cache = { path = "../cache", version = "0.22.0", optional = true }
wasmtime-profiling = { path = "../profiling", version = "0.22.0" }
target-lexicon = { version = "0.11.0", default-features = false }
wasmparser = "0.71"
wasmparser = "0.73"
anyhow = "1.0.19"
region = "2.2.0"
libc = "0.2"

View File

@@ -1,4 +1,4 @@
use crate::externals::MemoryCreator;
use crate::memory::MemoryCreator;
use crate::trampoline::MemoryCreatorProxy;
use anyhow::{bail, Result};
use std::cmp;
@@ -33,6 +33,9 @@ pub struct Config {
pub(crate) max_wasm_stack: usize,
pub(crate) features: WasmFeatures,
pub(crate) wasm_backtrace_details_env_used: bool,
pub(crate) max_instances: usize,
pub(crate) max_tables: usize,
pub(crate) max_memories: usize,
}
impl Config {
@@ -79,6 +82,9 @@ impl Config {
multi_value: true,
..WasmFeatures::default()
},
max_instances: 10_000,
max_tables: 10_000,
max_memories: 10_000,
};
ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
return ret;
@@ -131,6 +137,28 @@ impl Config {
self
}
/// Configures whether execution of WebAssembly will "consume fuel" to
/// either halt or yield execution as desired.
///
/// This option is similar in purpose to [`Config::interruptable`] where
/// you can prevent infinitely-executing WebAssembly code. The difference
/// is that this option allows deterministic execution of WebAssembly code
/// by instrumenting generated code consume fuel as it executes. When fuel
/// runs out the behavior is defined by configuration within a [`Store`],
/// and by default a trap is raised.
///
/// Note that a [`Store`] starts with no fuel, so if you enable this option
/// you'll have to be sure to pour some fuel into [`Store`] before
/// executing some code.
///
/// By default this option is `false`.
///
/// [`Store`]: crate::Store
pub fn consume_fuel(&mut self, enable: bool) -> &mut Self {
self.tunables.consume_fuel = enable;
self
}
/// Configures the maximum amount of native stack space available to
/// executing WebAssembly code.
///
@@ -383,6 +411,20 @@ impl Config {
self
}
/// Clears native CPU flags inferred from the host.
///
/// By default Wasmtime will tune generated code for the host that Wasmtime
/// itself is running on. If you're compiling on one host, however, and
/// shipping artifacts to another host then this behavior may not be
/// desired. This function will clear all inferred native CPU features.
///
/// To enable CPU features afterwards it's recommended to use the
/// [`Config::cranelift_other_flag`] method.
pub fn cranelift_clear_cpu_flags(&mut self) -> &mut Self {
self.isa_flags = native::builder_without_flags();
self
}
/// Allows settings another Cranelift flag defined by a flag name and value. This allows
/// fine-tuning of Cranelift settings.
///
@@ -635,6 +677,39 @@ impl Config {
self
}
/// Configures the maximum number of instances which can be created within
/// this `Store`.
///
/// Instantiation will fail with an error if this limit is exceeded.
///
/// This value defaults to 10,000.
pub fn max_instances(&mut self, instances: usize) -> &mut Self {
self.max_instances = instances;
self
}
/// Configures the maximum number of tables which can be created within
/// this `Store`.
///
/// Instantiation will fail with an error if this limit is exceeded.
///
/// This value defaults to 10,000.
pub fn max_tables(&mut self, tables: usize) -> &mut Self {
self.max_tables = tables;
self
}
/// Configures the maximum number of memories which can be created within
/// this `Store`.
///
/// Instantiation will fail with an error if this limit is exceeded.
///
/// This value defaults to 10,000.
pub fn max_memories(&mut self, memories: usize) -> &mut Self {
self.max_memories = memories;
self
}
pub(crate) fn target_isa(&self) -> Box<dyn TargetIsa> {
self.isa_flags
.clone()

View File

@@ -1,15 +1,13 @@
use crate::trampoline::{
generate_global_export, generate_memory_export, generate_table_export, StoreInstanceHandle,
};
use crate::memory::Memory;
use crate::trampoline::{generate_global_export, generate_table_export, StoreInstanceHandle};
use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val};
use crate::{
ExternRef, ExternType, Func, GlobalType, Instance, MemoryType, Module, Mutability, Store,
TableType, Trap, ValType,
ExternRef, ExternType, Func, GlobalType, Instance, Module, Mutability, Store, TableType, Trap,
ValType,
};
use anyhow::{anyhow, bail, Result};
use std::mem;
use std::ptr;
use std::slice;
use wasmtime_environ::wasm;
use wasmtime_runtime::{self as runtime, InstanceHandle};
@@ -112,26 +110,25 @@ impl Extern {
}
}
pub(crate) fn from_wasmtime_export(
wasmtime_export: wasmtime_runtime::Export,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_export(
wasmtime_export: &wasmtime_runtime::Export,
store: &Store,
) -> Extern {
match wasmtime_export {
wasmtime_runtime::Export::Function(f) => {
Extern::Func(Func::from_wasmtime_function(f, instance))
Extern::Func(Func::from_wasmtime_function(f, store))
}
wasmtime_runtime::Export::Memory(m) => {
Extern::Memory(Memory::from_wasmtime_memory(m, instance))
Extern::Memory(Memory::from_wasmtime_memory(m, store))
}
wasmtime_runtime::Export::Global(g) => {
Extern::Global(Global::from_wasmtime_global(g, instance))
Extern::Global(Global::from_wasmtime_global(g, store))
}
wasmtime_runtime::Export::Table(t) => {
Extern::Table(Table::from_wasmtime_table(t, instance))
Extern::Table(Table::from_wasmtime_table(t, store))
}
wasmtime_runtime::Export::Instance(i) => {
let handle = unsafe { instance.store.existing_instance_handle(i.clone()) };
Extern::Instance(Instance::from_wasmtime(handle))
Extern::Instance(Instance::from_wasmtime(i, store))
}
wasmtime_runtime::Export::Module(m) => {
Extern::Module(m.downcast_ref::<Module>().unwrap().clone())
@@ -335,13 +332,13 @@ impl Global {
Ok(())
}
pub(crate) fn from_wasmtime_global(
wasmtime_export: wasmtime_runtime::ExportGlobal,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_global(
wasmtime_export: &wasmtime_runtime::ExportGlobal,
store: &Store,
) -> Global {
Global {
instance,
wasmtime_export,
instance: store.existing_vmctx(wasmtime_export.vmctx),
wasmtime_export: wasmtime_export.clone(),
}
}
@@ -354,6 +351,10 @@ impl Global {
from: self.wasmtime_export.definition,
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportGlobal {
&self.wasmtime_export
}
}
/// A WebAssembly `table`, or an array of values.
@@ -576,13 +577,13 @@ impl Table {
Ok(())
}
pub(crate) fn from_wasmtime_table(
wasmtime_export: wasmtime_runtime::ExportTable,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_table(
wasmtime_export: &wasmtime_runtime::ExportTable,
store: &Store,
) -> Table {
Table {
instance,
wasmtime_export,
instance: store.existing_vmctx(wasmtime_export.vmctx),
wasmtime_export: wasmtime_export.clone(),
}
}
@@ -596,510 +597,9 @@ impl Table {
vmctx: self.wasmtime_export.vmctx,
}
}
}
/// A WebAssembly linear memory.
///
/// WebAssembly memories represent a contiguous array of bytes that have a size
/// that is always a multiple of the WebAssembly page size, currently 64
/// kilobytes.
///
/// WebAssembly memory is used for global data, statics in C/C++/Rust, shadow
/// stack memory, etc. Accessing wasm memory is generally quite fast!
///
/// # `Memory` and `Clone`
///
/// Memories are internally reference counted so you can `clone` a `Memory`. The
/// cloning process only performs a shallow clone, so two cloned `Memory`
/// instances are equivalent in their functionality.
///
/// # `Memory` and threads
///
/// It is intended that `Memory` is safe to share between threads. At this time
/// this is not implemented in `wasmtime`, however. This is planned to be
/// implemented though!
///
/// # `Memory` and Safety
///
/// Linear memory is a lynchpin of safety for WebAssembly, but it turns out
/// there are very few ways to safely inspect the contents of a memory from the
/// host (Rust). This is because memory safety is quite tricky when working with
/// a `Memory` and we're still working out the best idioms to encapsulate
/// everything safely where it's efficient and ergonomic. This section of
/// documentation, however, is intended to help educate a bit what is and isn't
/// safe when working with `Memory`.
///
/// For safety purposes you can think of a `Memory` as a glorified
/// `Rc<UnsafeCell<Vec<u8>>>`. There are a few consequences of this
/// interpretation:
///
/// * At any time someone else may have access to the memory (hence the `Rc`).
/// This could be a wasm instance, other host code, or a set of wasm instances
/// which all reference a `Memory`. When in doubt assume someone else has a
/// handle to your `Memory`.
///
/// * At any time, memory can be read from or written to (hence the
/// `UnsafeCell`). Anyone with a handle to a wasm memory can read/write to it.
/// Primarily other instances can execute the `load` and `store` family of
/// instructions, as well as any other which modifies or reads memory.
///
/// * At any time memory may grow (hence the `Vec<..>`). Growth may relocate the
/// base memory pointer (similar to how `vec.push(...)` can change the result
/// of `.as_ptr()`)
///
/// So given that we're working roughly with `Rc<UnsafeCell<Vec<u8>>>` that's a
/// lot to keep in mind! It's hopefully though sort of setting the stage as to
/// what you can safely do with memories.
///
/// Let's run through a few safe examples first of how you can use a `Memory`.
///
/// ```rust
/// use wasmtime::Memory;
///
/// fn safe_examples(mem: &Memory) {
/// // Just like wasm, it's safe to read memory almost at any time. The
/// // gotcha here is that we need to be sure to load from the correct base
/// // pointer and perform the bounds check correctly. So long as this is
/// // all self contained here (e.g. not arbitrary code in the middle) we're
/// // good to go.
/// let byte = unsafe { mem.data_unchecked()[0x123] };
///
/// // Short-lived borrows of memory are safe, but they must be scoped and
/// // not have code which modifies/etc `Memory` while the borrow is active.
/// // For example if you want to read a string from memory it is safe to do
/// // so:
/// let string_base = 0xdead;
/// let string_len = 0xbeef;
/// let string = unsafe {
/// let bytes = &mem.data_unchecked()[string_base..][..string_len];
/// match std::str::from_utf8(bytes) {
/// Ok(s) => s.to_string(), // copy out of wasm memory
/// Err(_) => panic!("not valid utf-8"),
/// }
/// };
///
/// // Additionally like wasm you can write to memory at any point in time,
/// // again making sure that after you get the unchecked slice you don't
/// // execute code which could read/write/modify `Memory`:
/// unsafe {
/// mem.data_unchecked_mut()[0x123] = 3;
/// }
///
/// // When working with *borrows* that point directly into wasm memory you
/// // need to be extremely careful. Any functionality that operates on a
/// // borrow into wasm memory needs to be thoroughly audited to effectively
/// // not touch the `Memory` at all
/// let data_base = 0xfeed;
/// let data_len = 0xface;
/// unsafe {
/// let data = &mem.data_unchecked()[data_base..][..data_len];
/// host_function_that_doesnt_touch_memory(data);
///
/// // effectively the same rules apply to mutable borrows
/// let data_mut = &mut mem.data_unchecked_mut()[data_base..][..data_len];
/// host_function_that_doesnt_touch_memory(data);
/// }
/// }
/// # fn host_function_that_doesnt_touch_memory(_: &[u8]){}
/// ```
///
/// It's worth also, however, covering some examples of **incorrect**,
/// **unsafe** usages of `Memory`. Do not do these things!
///
/// ```rust
/// # use anyhow::Result;
/// use wasmtime::Memory;
///
/// // NOTE: All code in this function is not safe to execute and may cause
/// // segfaults/undefined behavior at runtime. Do not copy/paste these examples
/// // into production code!
/// unsafe fn unsafe_examples(mem: &Memory) -> Result<()> {
/// // First and foremost, any borrow can be invalidated at any time via the
/// // `Memory::grow` function. This can relocate memory which causes any
/// // previous pointer to be possibly invalid now.
/// let pointer: &u8 = &mem.data_unchecked()[0x100];
/// mem.grow(1)?; // invalidates `pointer`!
/// // println!("{}", *pointer); // FATAL: use-after-free
///
/// // Note that the use-after-free also applies to slices, whether they're
/// // slices of bytes or strings.
/// let slice: &[u8] = &mem.data_unchecked()[0x100..0x102];
/// mem.grow(1)?; // invalidates `slice`!
/// // println!("{:?}", slice); // FATAL: use-after-free
///
/// // Due to the reference-counted nature of `Memory` note that literal
/// // calls to `Memory::grow` are not sufficient to audit for. You'll need
/// // to be careful that any mutation of `Memory` doesn't happen while
/// // you're holding an active borrow.
/// let slice: &[u8] = &mem.data_unchecked()[0x100..0x102];
/// some_other_function(); // may invalidate `slice` through another `mem` reference
/// // println!("{:?}", slice); // FATAL: maybe a use-after-free
///
/// // An especially subtle aspect of accessing a wasm instance's memory is
/// // that you need to be extremely careful about aliasing. Anyone at any
/// // time can call `data_unchecked()` or `data_unchecked_mut()`, which
/// // means you can easily have aliasing mutable references:
/// let ref1: &u8 = &mem.data_unchecked()[0x100];
/// let ref2: &mut u8 = &mut mem.data_unchecked_mut()[0x100];
/// // *ref2 = *ref1; // FATAL: violates Rust's aliasing rules
///
/// // Note that aliasing applies to strings as well, for example this is
/// // not valid because the slices overlap.
/// let slice1: &mut [u8] = &mut mem.data_unchecked_mut()[0x100..][..3];
/// let slice2: &mut [u8] = &mut mem.data_unchecked_mut()[0x102..][..4];
/// // println!("{:?} {:?}", slice1, slice2); // FATAL: aliasing mutable pointers
///
/// Ok(())
/// }
/// # fn some_other_function() {}
/// ```
///
/// Overall there's some general rules of thumb when working with `Memory` and
/// getting raw pointers inside of it:
///
/// * If you never have a "long lived" pointer into memory, you're likely in the
/// clear. Care still needs to be taken in threaded scenarios or when/where
/// data is read, but you'll be shielded from many classes of issues.
/// * Long-lived pointers must always respect Rust'a aliasing rules. It's ok for
/// shared borrows to overlap with each other, but mutable borrows must
/// overlap with nothing.
/// * Long-lived pointers are only valid if `Memory` isn't used in an unsafe way
/// while the pointer is valid. This includes both aliasing and growth.
///
/// At this point it's worth reiterating again that working with `Memory` is
/// pretty tricky and that's not great! Proposals such as [interface types] are
/// intended to prevent wasm modules from even needing to import/export memory
/// in the first place, which obviates the need for all of these safety caveats!
/// Additionally over time we're still working out the best idioms to expose in
/// `wasmtime`, so if you've got ideas or questions please feel free to [open an
/// issue]!
///
/// ## `Memory` Safety and Threads
///
/// Currently the `wasmtime` crate does not implement the wasm threads proposal,
/// but it is planned to do so. It's additionally worthwhile discussing how this
/// affects memory safety and what was previously just discussed as well.
///
/// Once threads are added into the mix, all of the above rules still apply.
/// There's an additional, rule, however, that all reads and writes can
/// happen *concurrently*. This effectively means that long-lived borrows into
/// wasm memory are virtually never safe to have.
///
/// Mutable pointers are fundamentally unsafe to have in a concurrent scenario
/// in the face of arbitrary wasm code. Only if you dynamically know for sure
/// that wasm won't access a region would it be safe to construct a mutable
/// pointer. Additionally even shared pointers are largely unsafe because their
/// underlying contents may change, so unless `UnsafeCell` in one form or
/// another is used everywhere there's no safety.
///
/// One important point about concurrency is that `Memory::grow` can indeed
/// happen concurrently. This, however, will never relocate the base pointer.
/// Shared memories must always have a maximum size and they will be
/// preallocated such that growth will never relocate the base pointer. The
/// maximum length of the memory, however, will change over time.
///
/// Overall the general rule of thumb for shared memories is that you must
/// atomically read and write everything. Nothing can be borrowed and everything
/// must be eagerly copied out.
///
/// [interface types]: https://github.com/webassembly/interface-types
/// [open an issue]: https://github.com/bytecodealliance/wasmtime/issues/new
#[derive(Clone)]
pub struct Memory {
instance: StoreInstanceHandle,
wasmtime_export: wasmtime_runtime::ExportMemory,
}
impl Memory {
/// Creates a new WebAssembly memory given the configuration of `ty`.
///
/// The `store` argument is a general location for cache information, and
/// otherwise the memory will immediately be allocated according to the
/// type's configuration. All WebAssembly memory is initialized to zero.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// let engine = Engine::default();
/// let store = Store::new(&engine);
///
/// let memory_ty = MemoryType::new(Limits::new(1, None));
/// let memory = Memory::new(&store, memory_ty);
///
/// let module = Module::new(&engine, "(module (memory (import \"\" \"\") 1))")?;
/// let instance = Instance::new(&store, &module, &[memory.into()])?;
/// // ...
/// # Ok(())
/// # }
/// ```
pub fn new(store: &Store, ty: MemoryType) -> Memory {
let (instance, wasmtime_export) =
generate_memory_export(store, &ty).expect("generated memory");
Memory {
instance,
wasmtime_export,
}
}
/// Returns the underlying type of this memory.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// let engine = Engine::default();
/// let store = Store::new(&engine);
/// let module = Module::new(&engine, "(module (memory (export \"mem\") 1))")?;
/// let instance = Instance::new(&store, &module, &[])?;
/// let memory = instance.get_memory("mem").unwrap();
/// let ty = memory.ty();
/// assert_eq!(ty.limits().min(), 1);
/// # Ok(())
/// # }
/// ```
pub fn ty(&self) -> MemoryType {
MemoryType::from_wasmtime_memory(&self.wasmtime_export.memory.memory)
}
/// Returns this memory as a slice view that can be read natively in Rust.
///
/// # Safety
///
/// This is an unsafe operation because there is no guarantee that the
/// following operations do not happen concurrently while the slice is in
/// use:
///
/// * Data could be modified by calling into a wasm module.
/// * Memory could be relocated through growth by calling into a wasm
/// module.
/// * When threads are supported, non-atomic reads will race with other
/// writes.
///
/// Extreme care need be taken when the data of a `Memory` is read. The
/// above invariants all need to be upheld at a bare minimum, and in
/// general you'll need to ensure that while you're looking at slice you're
/// the only one who can possibly look at the slice and read/write it.
///
/// Be sure to keep in mind that `Memory` is reference counted, meaning
/// that there may be other users of this `Memory` instance elsewhere in
/// your program. Additionally `Memory` can be shared and used in any number
/// of wasm instances, so calling any wasm code should be considered
/// dangerous while you're holding a slice of memory.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub unsafe fn data_unchecked(&self) -> &[u8] {
self.data_unchecked_mut()
}
/// Returns this memory as a slice view that can be read and written
/// natively in Rust.
///
/// # Safety
///
/// All of the same safety caveats of [`Memory::data_unchecked`] apply
/// here, doubly so because this is returning a mutable slice! As a
/// double-extra reminder, remember that `Memory` is reference counted, so
/// you can very easily acquire two mutable slices by simply calling this
/// function twice. Extreme caution should be used when using this method,
/// and in general you probably want to result to unsafe accessors and the
/// `data` methods below.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub unsafe fn data_unchecked_mut(&self) -> &mut [u8] {
let definition = &*self.wasmtime_export.definition;
slice::from_raw_parts_mut(definition.base, definition.current_length)
}
/// Returns the base pointer, in the host's address space, that the memory
/// is located at.
///
/// When reading and manipulating memory be sure to read up on the caveats
/// of [`Memory::data_unchecked`] to make sure that you can safely
/// read/write the memory.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub fn data_ptr(&self) -> *mut u8 {
unsafe { (*self.wasmtime_export.definition).base }
}
/// Returns the byte length of this memory.
///
/// The returned value will be a multiple of the wasm page size, 64k.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub fn data_size(&self) -> usize {
unsafe { (*self.wasmtime_export.definition).current_length }
}
/// Returns the size, in pages, of this wasm memory.
pub fn size(&self) -> u32 {
(self.data_size() / wasmtime_environ::WASM_PAGE_SIZE as usize) as u32
}
/// Grows this WebAssembly memory by `delta` pages.
///
/// This will attempt to add `delta` more pages of memory on to the end of
/// this `Memory` instance. If successful this may relocate the memory and
/// cause [`Memory::data_ptr`] to return a new value. Additionally previous
/// slices into this memory may no longer be valid.
///
/// On success returns the number of pages this memory previously had
/// before the growth succeeded.
///
/// # Errors
///
/// Returns an error if memory could not be grown, for example if it exceeds
/// the maximum limits of this memory.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// let engine = Engine::default();
/// let store = Store::new(&engine);
/// let module = Module::new(&engine, "(module (memory (export \"mem\") 1 2))")?;
/// let instance = Instance::new(&store, &module, &[])?;
/// let memory = instance.get_memory("mem").unwrap();
///
/// assert_eq!(memory.size(), 1);
/// assert_eq!(memory.grow(1)?, 1);
/// assert_eq!(memory.size(), 2);
/// assert!(memory.grow(1).is_err());
/// assert_eq!(memory.size(), 2);
/// assert_eq!(memory.grow(0)?, 2);
/// # Ok(())
/// # }
/// ```
pub fn grow(&self, delta: u32) -> Result<u32> {
let index = self
.instance
.memory_index(unsafe { &*self.wasmtime_export.definition });
self.instance
.memory_grow(index, delta)
.ok_or_else(|| anyhow!("failed to grow memory"))
}
pub(crate) fn from_wasmtime_memory(
wasmtime_export: wasmtime_runtime::ExportMemory,
instance: StoreInstanceHandle,
) -> Memory {
Memory {
instance,
wasmtime_export,
}
}
pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Memory {
&self.wasmtime_export.memory.memory
}
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMMemoryImport {
wasmtime_runtime::VMMemoryImport {
from: self.wasmtime_export.definition,
vmctx: self.wasmtime_export.vmctx,
}
}
}
/// A linear memory. This trait provides an interface for raw memory buffers which are used
/// by wasmtime, e.g. inside ['Memory']. Such buffers are in principle not thread safe.
/// By implementing this trait together with MemoryCreator,
/// one can supply wasmtime with custom allocated host managed memory.
///
/// # Safety
/// The memory should be page aligned and a multiple of page size.
/// To prevent possible silent overflows, the memory should be protected by a guard page.
/// Additionally the safety concerns explained in ['Memory'], for accessing the memory
/// apply here as well.
///
/// Note that this is a relatively new and experimental feature and it is recommended
/// to be familiar with wasmtime runtime code to use it.
pub unsafe trait LinearMemory {
/// Returns the number of allocated wasm pages.
fn size(&self) -> u32;
/// Grow memory by the specified amount of wasm pages.
///
/// Returns `None` if memory can't be grown by the specified amount
/// of wasm pages.
fn grow(&self, delta: u32) -> Option<u32>;
/// Return the allocated memory as a mutable pointer to u8.
fn as_ptr(&self) -> *mut u8;
}
/// A memory creator. Can be used to provide a memory creator
/// to wasmtime which supplies host managed memory.
///
/// # Safety
/// This trait is unsafe, as the memory safety depends on proper implementation of
/// memory management. Memories created by the MemoryCreator should always be treated
/// as owned by wasmtime instance, and any modification of them outside of wasmtime
/// invoked routines is unsafe and may lead to corruption.
///
/// Note that this is a relatively new and experimental feature and it is recommended
/// to be familiar with wasmtime runtime code to use it.
pub unsafe trait MemoryCreator: Send + Sync {
/// Create a new `LinearMemory` object from the specified parameters.
///
/// The type of memory being created is specified by `ty` which indicates
/// both the minimum and maximum size, in wasm pages.
///
/// The `reserved_size_in_bytes` value indicates the expected size of the
/// reservation that is to be made for this memory. If this value is `None`
/// than the implementation is free to allocate memory as it sees fit. If
/// the value is `Some`, however, then the implementation is expected to
/// reserve that many bytes for the memory's allocation, plus the guard
/// size at the end. Note that this reservation need only be a virtual
/// memory reservation, physical memory does not need to be allocated
/// immediately. In this case `grow` should never move the base pointer and
/// the maximum size of `ty` is guaranteed to fit within `reserved_size_in_bytes`.
///
/// The `guard_size_in_bytes` parameter indicates how many bytes of space, after the
/// memory allocation, is expected to be unmapped. JIT code will elide
/// bounds checks based on the `guard_size_in_bytes` provided, so for JIT code to
/// work correctly the memory returned will need to be properly guarded with
/// `guard_size_in_bytes` bytes left unmapped after the base allocation.
///
/// Note that the `reserved_size_in_bytes` and `guard_size_in_bytes` options are tuned from
/// the various [`Config`](crate::Config) methods about memory
/// sizes/guards. Additionally these two values are guaranteed to be
/// multiples of the system page size.
fn new_memory(
&self,
ty: MemoryType,
reserved_size_in_bytes: Option<u64>,
guard_size_in_bytes: u64,
) -> Result<Box<dyn LinearMemory>, String>;
}
#[cfg(test)]
mod tests {
use crate::*;
// Assert that creating a memory via `Memory::new` respects the limits/tunables
// in `Config`.
#[test]
fn respect_tunables() {
let mut cfg = Config::new();
cfg.static_memory_maximum_size(0)
.dynamic_memory_guard_size(0);
let store = Store::new(&Engine::new(&cfg));
let ty = MemoryType::new(Limits::new(1, None));
let mem = Memory::new(&store, ty);
assert_eq!(mem.wasmtime_export.memory.offset_guard_size, 0);
match mem.wasmtime_export.memory.style {
wasmtime_environ::MemoryStyle::Dynamic => {}
other => panic!("unexpected style {:?}", other),
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportTable {
&self.wasmtime_export
}
}

View File

@@ -1,6 +1,6 @@
use crate::store::StoreInner;
use crate::trampoline::StoreInstanceHandle;
use crate::{Extern, ExternRef, FuncType, Memory, Store, Trap, Val, ValType};
use crate::{Extern, ExternRef, FuncType, Store, Trap, Val, ValType};
use anyhow::{bail, ensure, Context as _, Result};
use smallvec::{smallvec, SmallVec};
use std::cmp::max;
@@ -9,8 +9,9 @@ use std::mem;
use std::panic::{self, AssertUnwindSafe};
use std::ptr::{self, NonNull};
use std::rc::Weak;
use wasmtime_environ::wasm::EntityIndex;
use wasmtime_runtime::{
raise_user_trap, Export, InstanceHandle, VMContext, VMFunctionBody, VMSharedSignatureIndex,
raise_user_trap, InstanceHandle, VMContext, VMFunctionBody, VMSharedSignatureIndex,
VMTrampoline,
};
@@ -331,11 +332,8 @@ impl Func {
debug_assert!(
anyfunc.as_ref().type_index != wasmtime_runtime::VMSharedSignatureIndex::default()
);
let instance_handle = wasmtime_runtime::InstanceHandle::from_vmctx(anyfunc.as_ref().vmctx);
let export = wasmtime_runtime::ExportFunction { anyfunc };
let instance = store.existing_instance_handle(instance_handle);
let f = Func::from_wasmtime_function(export, instance);
let f = Func::from_wasmtime_function(&export, store);
Some(f)
}
@@ -373,8 +371,8 @@ impl Func {
///
/// Finally you can also optionally take [`Caller`] as the first argument of
/// your closure. If inserted then you're able to inspect the caller's
/// state, for example the [`Memory`] it has exported so you can read what
/// pointers point to.
/// state, for example the [`Memory`](crate::Memory) it has exported so you
/// can read what pointers point to.
///
/// Note that when using this API, the intention is to create as thin of a
/// layer as possible for when WebAssembly calls the function provided. With
@@ -649,24 +647,24 @@ impl Func {
self.export.anyfunc
}
pub(crate) fn from_wasmtime_function(
export: wasmtime_runtime::ExportFunction,
instance: StoreInstanceHandle,
pub(crate) unsafe fn from_wasmtime_function(
export: &wasmtime_runtime::ExportFunction,
store: &Store,
) -> Self {
// Each function signature in a module should have a trampoline stored
// on that module as well, so unwrap the result here since otherwise
// it's a bug in wasmtime.
let trampoline = instance
.store
let anyfunc = export.anyfunc.as_ref();
let trampoline = store
.signatures()
.borrow()
.lookup_shared(unsafe { export.anyfunc.as_ref().type_index })
.lookup_shared(anyfunc.type_index)
.expect("failed to retrieve trampoline from module")
.1;
Func {
instance,
export,
instance: store.existing_vmctx(anyfunc.vmctx),
export: export.clone(),
trampoline,
}
}
@@ -807,6 +805,10 @@ impl Func {
}
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportFunction {
&self.export
}
}
impl fmt::Debug for Func {
@@ -1506,10 +1508,16 @@ impl Caller<'_> {
debug_assert!(self.store.upgrade().is_some());
let handle =
Store::from_inner(self.store.upgrade()?).existing_instance_handle(instance);
let export = handle.lookup(name)?;
match export {
Export::Memory(m) => Some(Extern::Memory(Memory::from_wasmtime_memory(m, handle))),
Export::Function(f) => Some(Extern::Func(Func::from_wasmtime_function(f, handle))),
let index = handle.module().exports.get(name)?;
match index {
// Only allow memory/functions for now to emulate what interface
// types will once provide
EntityIndex::Memory(_) | EntityIndex::Function(_) => {
Some(Extern::from_wasmtime_export(
&handle.lookup_by_declaration(&index),
&handle.store,
))
}
_ => None,
}
}

View File

@@ -1,241 +1,22 @@
use crate::trampoline::StoreInstanceHandle;
use crate::types::matching;
use crate::{
Engine, Export, Extern, ExternType, Func, Global, InstanceType, Memory, Module, Store, Table,
Trap,
Engine, Export, Extern, Func, Global, InstanceType, Memory, Module, Store, Table, Trap,
};
use anyhow::{bail, Context, Error, Result};
use std::mem;
use std::sync::Arc;
use std::rc::Rc;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::{
EntityIndex, EntityType, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex,
TableIndex,
EntityIndex, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex, TableIndex,
};
use wasmtime_environ::Initializer;
use wasmtime_jit::TypeTables;
use wasmtime_runtime::{
Imports, InstanceHandle, InstantiationError, StackMapRegistry, VMContext,
Imports, InstantiationError, RuntimeInstance, StackMapRegistry, VMContext,
VMExternRefActivationsTable, VMFunctionBody, VMFunctionImport, VMGlobalImport, VMMemoryImport,
VMTableImport,
};
/// Performs all low-level steps necessary for instantiation.
///
/// This function will take all the arguments and attempt to do everything
/// necessary to instantiate the referenced instance. The trickiness of this
/// function stems from the implementation of the module-linking proposal where
/// we're handling nested instances, interleaved imports/aliases, etc. That's
/// all an internal implementation here ideally though!
///
/// * `store` - the store we're instantiating into
/// * `compiled_module` - the module that we're instantiating
/// * `all_modules` - the list of all modules that were part of the compilation
/// of `compiled_module`. This is only applicable in the module linking
/// proposal, otherwise this will just be a list containing `compiled_module`
/// itself.
/// * `type` - the type tables produced during compilation which
/// `compiled_module`'s metadata references.
/// * `parent_modules` - this is the list of compiled modules the parent has.
/// This is only applicable on recursive instantiations.
/// * `define_import` - this function, like the name implies, defines an import
/// into the provided builder. The expected entity that it's defining is also
/// passed in for the top-level case where type-checking is performed. This is
/// fallible because type checks may fail.
fn instantiate(
store: &Store,
module: &Module,
parent_modules: &PrimaryMap<ModuleIndex, Module>,
define_import: &mut dyn FnMut(&EntityIndex, &mut ImportsBuilder<'_>) -> Result<()>,
) -> Result<StoreInstanceHandle, Error> {
let compiled_module = module.compiled_module();
let env_module = compiled_module.module();
let mut imports = ImportsBuilder::new(store, module);
for initializer in env_module.initializers.iter() {
match initializer {
// Definition of an import depends on how our parent is providing
// imports, so we delegate to our custom closure. This will resolve
// to fetching from the import list for the top-level module and
// otherwise fetching from each nested instance's argument list for
// submodules.
Initializer::Import {
index,
module,
field,
} => {
define_import(index, &mut imports).with_context(|| match field {
Some(name) => format!("incompatible import type for `{}::{}`", module, name),
None => format!("incompatible import type for `{}`", module),
})?;
}
// This one's pretty easy, we're just picking up our parent's module
// and putting it into our own index space.
Initializer::AliasParentModule(idx) => {
imports.modules.push(parent_modules[*idx].clone());
}
// Turns out defining any kind of module is pretty easy, we're just
// slinging around pointers.
Initializer::DefineModule(idx) => {
imports.modules.push(module.submodule(*idx));
}
// Here we lookup our instance handle, find the right export,
// and then push that item into our own index space. We eschew
// type-checking since only valid modules reach this point.
//
// Note that export lookup here needs to happen by name. The
// `export` index is an index into our local type definition of the
// type of the instance to figure out what name it was assigned.
// This is where the subtyping happens!
//
// Note that the unsafety here is because we're asserting that the
// handle comes from our same store, but this should be true because
// we acquired the handle from an instance in the store.
Initializer::AliasInstanceExport { instance, export } => {
let instance_ty = env_module.instances[*instance];
let export_name = module.types().instance_signatures[instance_ty]
.exports
.get_index(*export)
.expect("validation bug - should be valid")
.0;
let handle = &imports.instances[*instance];
let entity_index = &handle.module().exports[export_name];
let item = Extern::from_wasmtime_export(
handle.lookup_by_declaration(entity_index),
unsafe { store.existing_instance_handle(handle.clone()) },
);
imports.push_extern(&item);
}
// Oh boy a recursive instantiation! The recursive arguments here
// are pretty simple, and the only slightly-meaty one is how
// arguments are pulled from `args` and pushed directly into the
// builder specified, which should be an easy enough
// copy-the-pointer operation in all cases.
//
// Note that this recursive call shouldn't result in an infinite
// loop because of wasm module validation which requires everything
// to be a DAG. Additionally the recursion should also be bounded
// due to validation. We may one day need to make this an iterative
// loop, however.
//
// Also note that there's some unsafety here around cloning
// `InstanceHandle` because the handle may not live long enough, but
// we're doing all of this in the context of our `Store` argument
// above so we should be safe here.
Initializer::Instantiate { module, args } => {
let mut args = args.iter();
let handle = instantiate(
store,
&imports.modules[*module],
&imports.modules,
&mut |_, builder| {
match *args.next().unwrap() {
EntityIndex::Global(i) => {
builder.globals.push(imports.globals[i]);
}
EntityIndex::Function(i) => {
builder.functions.push(imports.functions[i]);
}
EntityIndex::Table(i) => {
builder.tables.push(imports.tables[i]);
}
EntityIndex::Memory(i) => {
builder.memories.push(imports.memories[i]);
}
EntityIndex::Module(i) => {
builder.modules.push(imports.modules[i].clone());
}
EntityIndex::Instance(i) => {
builder
.instances
.push(unsafe { imports.instances[i].clone() });
}
}
Ok(())
},
)?;
imports.instances.push(unsafe { (*handle).clone() });
}
}
}
// With the above initialization done we've now acquired the final set of
// imports in all the right index spaces and everything. Time to carry on
// with the creation of our own instance.
let imports = imports.build();
// Register the module just before instantiation to ensure we have a
// trampoline registered for every signature and to preserve the module's
// compiled JIT code within the `Store`.
store.register_module(module);
let config = store.engine().config();
let instance = unsafe {
let instance = compiled_module.instantiate(
imports,
&store.lookup_shared_signature(module.types()),
config.memory_creator.as_ref().map(|a| a as _),
store.interrupts(),
Box::new(module.types().clone()),
store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
store.stack_map_registry() as *const StackMapRegistry as *mut _,
)?;
// After we've created the `InstanceHandle` we still need to run
// initialization to set up data/elements/etc. We do this after adding
// the `InstanceHandle` to the store though. This is required for safety
// because the start function (for example) may trap, but element
// initializers may have run which placed elements into other instance's
// tables. This means that from this point on, regardless of whether
// initialization is successful, we need to keep the instance alive.
let instance = store.add_instance(instance);
instance
.initialize(
config.features.bulk_memory,
&compiled_module.data_initializers(),
)
.map_err(|e| -> Error {
match e {
InstantiationError::Trap(trap) => Trap::from_runtime(store, trap).into(),
other => other.into(),
}
})?;
instance
};
let start_func = instance.handle.module().start_func;
// If a start function is present, invoke it. Make sure we use all the
// trap-handling configuration in `store` as well.
if let Some(start) = start_func {
let f = match instance
.handle
.lookup_by_declaration(&EntityIndex::Function(start))
{
wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(), // valid modules shouldn't hit this
};
let vmctx_ptr = instance.handle.vmctx_ptr();
unsafe {
super::func::invoke_wasm_and_catch_traps(vmctx_ptr, store, || {
mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(*mut VMContext, *mut VMContext),
>(f.anyfunc.as_ref().func_ptr.as_ptr())(
f.anyfunc.as_ref().vmctx, vmctx_ptr
)
})?;
}
}
Ok(instance)
}
/// An instantiated WebAssembly module.
///
/// This type represents the instantiation of a [`Module`]. Once instantiated
@@ -254,7 +35,8 @@ fn instantiate(
/// call any code or execute anything!
#[derive(Clone)]
pub struct Instance {
pub(crate) handle: StoreInstanceHandle,
pub(crate) store: Store,
pub(crate) items: RuntimeInstance,
}
impl Instance {
@@ -312,49 +94,29 @@ impl Instance {
/// [issue]: https://github.com/bytecodealliance/wasmtime/issues/727
/// [`ExternType`]: crate::ExternType
pub fn new(store: &Store, module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
if !Engine::same(store.engine(), module.engine()) {
bail!("cross-`Engine` instantiation is not currently supported");
}
// Perform some pre-flight checks before we get into the meat of
// instantiation.
let expected = module.compiled_module().module().imports().count();
if expected != imports.len() {
bail!("expected {} imports, found {}", expected, imports.len());
}
for import in imports {
if !import.comes_from_same_store(store) {
bail!("cross-`Store` instantiation is not currently supported");
let mut i = Instantiator::new(store, module, imports)?;
loop {
if let Some((instance, items)) = i.step()? {
Instantiator::start_raw(&instance)?;
if let Some(items) = items {
break Ok(Instance::from_wasmtime(&items, store));
}
}
}
let mut imports = imports.iter();
let handle = instantiate(store, module, &PrimaryMap::new(), &mut |idx, builder| {
let import = imports.next().expect("already checked the length");
builder.define_extern(idx, import)
})?;
Ok(Instance { handle })
}
pub(crate) fn from_wasmtime(handle: StoreInstanceHandle) -> Instance {
Instance { handle }
pub(crate) fn from_wasmtime(handle: &RuntimeInstance, store: &Store) -> Instance {
Instance {
items: handle.clone(),
store: store.clone(),
}
}
/// Returns the type signature of this instance.
pub fn ty(&self) -> InstanceType {
let mut ty = InstanceType::new();
let module = self.handle.module();
let types = self
.handle
.host_state()
.downcast_ref::<Arc<TypeTables>>()
.unwrap();
for (name, index) in module.exports.iter() {
ty.add_named_export(
name,
ExternType::from_wasmtime(types, &module.type_of(*index)),
);
for export in self.exports() {
ty.add_named_export(export.name(), export.ty());
}
ty
}
@@ -364,16 +126,15 @@ impl Instance {
/// This is the [`Store`] that generally serves as a sort of global cache
/// for various instance-related things.
pub fn store(&self) -> &Store {
&self.handle.store
&self.store
}
/// Returns the list of exported items from this [`Instance`].
pub fn exports<'instance>(
&'instance self,
) -> impl ExactSizeIterator<Item = Export<'instance>> + 'instance {
self.handle.exports().map(move |(name, entity_index)| {
let export = self.handle.lookup_by_declaration(entity_index);
let extern_ = Extern::from_wasmtime_export(export, self.handle.clone());
self.items.iter().map(move |(name, item)| {
let extern_ = unsafe { Extern::from_wasmtime_export(item, &self.store) };
Export::new(name, extern_)
})
}
@@ -385,8 +146,8 @@ impl Instance {
///
/// Returns `None` if there was no export named `name`.
pub fn get_export(&self, name: &str) -> Option<Extern> {
let export = self.handle.lookup(&name)?;
Some(Extern::from_wasmtime_export(export, self.handle.clone()))
let export = self.items.get(name)?;
Some(unsafe { Extern::from_wasmtime_export(export, &self.store) })
}
/// Looks up an exported [`Func`] value by name.
@@ -422,71 +183,366 @@ impl Instance {
}
}
struct Instantiator<'a> {
in_progress: Vec<ImportsBuilder<'a>>,
cur: ImportsBuilder<'a>,
store: &'a Store,
}
struct ImportsBuilder<'a> {
src: ImportSource<'a>,
functions: PrimaryMap<FuncIndex, VMFunctionImport>,
tables: PrimaryMap<TableIndex, VMTableImport>,
memories: PrimaryMap<MemoryIndex, VMMemoryImport>,
globals: PrimaryMap<GlobalIndex, VMGlobalImport>,
instances: PrimaryMap<InstanceIndex, InstanceHandle>,
instances: PrimaryMap<InstanceIndex, RuntimeInstance>,
modules: PrimaryMap<ModuleIndex, Module>,
module: &'a wasmtime_environ::Module,
matcher: matching::MatchCx<'a>,
initializer: usize,
module: Module,
}
impl<'a> ImportsBuilder<'a> {
fn new(store: &'a Store, module: &'a Module) -> ImportsBuilder<'a> {
let types = module.types();
let module = module.compiled_module().module();
ImportsBuilder {
module,
matcher: matching::MatchCx { store, types },
functions: PrimaryMap::with_capacity(module.num_imported_funcs),
tables: PrimaryMap::with_capacity(module.num_imported_tables),
memories: PrimaryMap::with_capacity(module.num_imported_memories),
globals: PrimaryMap::with_capacity(module.num_imported_globals),
instances: PrimaryMap::with_capacity(module.instances.len()),
modules: PrimaryMap::with_capacity(module.modules.len()),
enum ImportSource<'a> {
Runtime(&'a [Extern]),
Outer { initializer: usize },
}
impl<'a> Instantiator<'a> {
/// Creates a new instantiation context used to process all the initializer
/// directives of a module.
///
/// This doesn't do much work itself beyond setting things up.
fn new(store: &'a Store, module: &Module, imports: &'a [Extern]) -> Result<Instantiator<'a>> {
if !Engine::same(store.engine(), module.engine()) {
bail!("cross-`Engine` instantiation is not currently supported");
}
// Perform some pre-flight checks before we get into the meat of
// instantiation.
let expected = module.compiled_module().module().imports().count();
if expected != imports.len() {
bail!("expected {} imports, found {}", expected, imports.len());
}
for import in imports {
if !import.comes_from_same_store(store) {
bail!("cross-`Store` instantiation is not currently supported");
}
}
Ok(Instantiator {
in_progress: Vec::new(),
cur: ImportsBuilder::new(module, ImportSource::Runtime(imports)),
store,
})
}
/// Processes the next initializer for the next instance being created
/// without running any wasm code.
///
/// This function will process module initializers, handling recursive
/// instantiations of modules for module linking if necessary as well. This
/// does not actually execute any WebAssembly code, which means that it
/// will return whenever an instance is created (because its `start`
/// function may need to be executed).
///
/// If this function returns `None`, then it simply needs to be called
/// again to execute the next initializer. Otherwise this function has two
/// return values:
///
/// * The first is the raw handle to the instance that was just created.
/// This instance must have its start function executed by the caller.
/// * The second is an optional list of items to get wrapped up in an
/// `Instance`. This is only `Some` for the outermost instance that was
/// created. If this is `None` callers need to keep calling this function
/// since the instance created was simply for a recursive instance
/// defined here.
fn step(&mut self) -> Result<Option<(StoreInstanceHandle, Option<RuntimeInstance>)>> {
if self.cur.initializer == 0 {
self.store.bump_resource_counts(&self.cur.module)?;
}
// Read the current module's initializer and move forward the
// initializer pointer as well.
self.cur.initializer += 1;
match self
.cur
.module
.env_module()
.initializers
.get(self.cur.initializer - 1)
{
Some(Initializer::Import { index, name, field }) => {
match &mut self.cur.src {
// If imports are coming from the runtime-provided list
// (e.g. the root module being instantiated) then we
// need to typecheck each item here before recording it.
//
// Note the `unwrap` here should be ok given the validation
// above in `Instantiation::new`.
ImportSource::Runtime(list) => {
let (head, remaining) = list.split_first().unwrap();
*list = remaining;
let expected_ty =
self.cur.module.compiled_module().module().type_of(*index);
matching::MatchCx {
types: self.cur.module.types(),
store: self.store,
}
.extern_(&expected_ty, head)
.with_context(|| {
let extra = match field {
Some(name) => format!("::{}", name),
None => String::new(),
};
format!("incompatible import type for `{}{}`", name, extra)
})?;
self.cur.push(head);
}
// Otherwise if arguments are coming from our outer
// instance due to a recursive instantiation then we
// look in the previous initializer's mapping of
// arguments to figure out where to load the item from.
// Note that no typechecking is necessary here due to
// validation.
ImportSource::Outer { initializer } => {
debug_assert!(field.is_none());
let outer = self.in_progress.last().unwrap();
let args = match &outer.module.env_module().initializers[*initializer] {
Initializer::Instantiate { args, .. } => args,
_ => unreachable!(),
};
let index = args.get(name).expect("should be present after validation");
match *index {
EntityIndex::Global(i) => {
self.cur.globals.push(outer.globals[i]);
}
EntityIndex::Function(i) => {
self.cur.functions.push(outer.functions[i]);
}
EntityIndex::Table(i) => {
self.cur.tables.push(outer.tables[i]);
}
EntityIndex::Memory(i) => {
self.cur.memories.push(outer.memories[i]);
}
EntityIndex::Module(i) => {
self.cur.modules.push(outer.modules[i].clone());
}
EntityIndex::Instance(i) => {
self.cur.instances.push(outer.instances[i].clone());
}
}
}
}
}
// Here we lookup our instance handle, find the right export,
// and then push that item into our own index space. We eschew
// type-checking since only valid modules should reach this point.
Some(Initializer::AliasInstanceExport { instance, export }) => {
let export = &self.cur.instances[*instance][export];
let item = unsafe { Extern::from_wasmtime_export(export, self.store) };
self.cur.push(&item);
}
// A recursive instantiation of an instance.
//
// The `module` argument is used to create an import builder
// object, and we specify that the source of imports for the builder is
// this initializer's position so we can look at the `args` payload
// later.
//
// Once that's set up we save off `self.cur` into
// `self.in_progress` and start the instantiation of the child
// instance on the next execution of this function.
Some(Initializer::Instantiate { module, args: _ }) => {
let module = &self.cur.modules[*module];
let imports = ImportsBuilder::new(
module,
ImportSource::Outer {
initializer: self.cur.initializer - 1,
},
);
let prev = mem::replace(&mut self.cur, imports);
self.in_progress.push(prev);
}
// A new module is being defined, and the source of this module is
// our module's list of closed-over-modules.
//
// This is used for outer aliases.
Some(Initializer::DefineModule(upvar_index)) => {
self.cur
.modules
.push(self.cur.module.module_upvar(*upvar_index).clone());
}
// A new module is defined, created from a set of compiled
// artifacts. The new module value will be created with the
// specified artifacts being closed over as well as the specified
// set of module values in our index/upvar index spaces being closed
// over.
//
// This is used for defining submodules.
Some(Initializer::CreateModule {
artifact_index,
artifacts,
modules,
}) => {
let submodule = self.cur.module.create_submodule(
*artifact_index,
artifacts,
modules,
&self.cur.modules,
);
self.cur.modules.push(submodule);
}
// All initializers have been processed, which means we're ready to
// perform the actual raw instantiation with the raw import values.
// Once that's done if there's an in-progress module we record the
// instance in the index space. Otherwise this is the final module
// and we return the items out.
//
// Note that in all cases we return the raw instance handle to get
// the start function executed by the outer context.
None => {
let instance = self.instantiate_raw()?;
let items = self.runtime_instance(&instance);
let items = match self.in_progress.pop() {
Some(imports) => {
self.cur = imports;
self.cur.instances.push(items);
None
}
None => Some(items),
};
return Ok(Some((instance, items)));
}
}
Ok(None)
}
fn instantiate_raw(&self) -> Result<StoreInstanceHandle> {
let compiled_module = self.cur.module.compiled_module();
// Register the module just before instantiation to ensure we have a
// trampoline registered for every signature and to preserve the module's
// compiled JIT code within the `Store`.
self.store.register_module(&self.cur.module);
let config = self.store.engine().config();
unsafe {
let instance = compiled_module.instantiate(
self.cur.build(),
&self.store.lookup_shared_signature(self.cur.module.types()),
config.memory_creator.as_ref().map(|a| a as _),
self.store.interrupts(),
Box::new(()),
self.store.externref_activations_table() as *const VMExternRefActivationsTable
as *mut _,
self.store.stack_map_registry() as *const StackMapRegistry as *mut _,
)?;
// After we've created the `InstanceHandle` we still need to run
// initialization to set up data/elements/etc. We do this after adding
// the `InstanceHandle` to the store though. This is required for safety
// because the start function (for example) may trap, but element
// initializers may have run which placed elements into other instance's
// tables. This means that from this point on, regardless of whether
// initialization is successful, we need to keep the instance alive.
let instance = self.store.add_instance(instance);
instance
.initialize(
config.features.bulk_memory,
&compiled_module.data_initializers(),
)
.map_err(|e| -> Error {
match e {
InstantiationError::Trap(trap) => {
Trap::from_runtime(self.store, trap).into()
}
other => other.into(),
}
})?;
Ok(instance)
}
}
fn define_extern(&mut self, expected: &EntityIndex, actual: &Extern) -> Result<()> {
let expected_ty = self.module.type_of(*expected);
let compatible = match &expected_ty {
EntityType::Table(i) => match actual {
Extern::Table(e) => self.matcher.table(i, e),
_ => bail!("expected table, but found {}", actual.desc()),
},
EntityType::Memory(i) => match actual {
Extern::Memory(e) => self.matcher.memory(i, e),
_ => bail!("expected memory, but found {}", actual.desc()),
},
EntityType::Global(i) => match actual {
Extern::Global(e) => self.matcher.global(i, e),
_ => bail!("expected global, but found {}", actual.desc()),
},
EntityType::Function(i) => match actual {
Extern::Func(e) => self.matcher.func(*i, e),
_ => bail!("expected func, but found {}", actual.desc()),
},
EntityType::Instance(i) => match actual {
Extern::Instance(e) => self.matcher.instance(*i, e),
_ => bail!("expected instance, but found {}", actual.desc()),
},
EntityType::Module(i) => match actual {
Extern::Module(e) => self.matcher.module(*i, e),
_ => bail!("expected module, but found {}", actual.desc()),
},
EntityType::Event(_) => unimplemented!(),
};
if !compatible {
bail!("{} types incompatible", actual.desc());
fn start_raw(instance: &StoreInstanceHandle) -> Result<()> {
let start_func = instance.handle.module().start_func;
// If a start function is present, invoke it. Make sure we use all the
// trap-handling configuration in `store` as well.
if let Some(start) = start_func {
let f = match instance
.handle
.lookup_by_declaration(&EntityIndex::Function(start))
{
wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(), // valid modules shouldn't hit this
};
let vmctx_ptr = instance.handle.vmctx_ptr();
unsafe {
super::func::invoke_wasm_and_catch_traps(vmctx_ptr, &instance.store, || {
mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(*mut VMContext, *mut VMContext),
>(f.anyfunc.as_ref().func_ptr.as_ptr())(
f.anyfunc.as_ref().vmctx, vmctx_ptr
)
})?;
}
}
self.push_extern(actual);
Ok(())
}
fn push_extern(&mut self, item: &Extern) {
fn runtime_instance(&self, instance: &StoreInstanceHandle) -> RuntimeInstance {
let exports = instance
.handle
.module()
.exports
.iter()
.map(|(name, index)| {
// Note that instances and modules are not handled by
// `wasmtime_runtime`, they're handled by us in this crate. That
// means we need to handle that here, otherwise we defer to the
// instance to load the values.
let item = match index {
EntityIndex::Instance(i) => {
wasmtime_runtime::Export::Instance(self.cur.instances[*i].clone())
}
EntityIndex::Module(i) => {
wasmtime_runtime::Export::Module(Box::new(self.cur.modules[*i].clone()))
}
index => instance.handle.lookup_by_declaration(index),
};
(name.clone(), item)
})
.collect();
Rc::new(exports)
}
}
impl<'a> ImportsBuilder<'a> {
fn new(module: &Module, src: ImportSource<'a>) -> ImportsBuilder<'a> {
let raw = module.compiled_module().module();
ImportsBuilder {
src,
functions: PrimaryMap::with_capacity(raw.num_imported_funcs),
tables: PrimaryMap::with_capacity(raw.num_imported_tables),
memories: PrimaryMap::with_capacity(raw.num_imported_memories),
globals: PrimaryMap::with_capacity(raw.num_imported_globals),
instances: PrimaryMap::with_capacity(raw.instances.len()),
modules: PrimaryMap::with_capacity(raw.modules.len()),
module: module.clone(),
initializer: 0,
}
}
fn push(&mut self, item: &Extern) {
match item {
Extern::Func(i) => {
self.functions.push(i.vmimport());
@@ -501,8 +557,7 @@ impl<'a> ImportsBuilder<'a> {
self.memories.push(i.vmimport());
}
Extern::Instance(i) => {
debug_assert!(Store::same(i.store(), self.matcher.store));
self.instances.push(unsafe { (*i.handle).clone() });
self.instances.push(i.items.clone());
}
Extern::Module(m) => {
self.modules.push(m.clone());
@@ -510,17 +565,46 @@ impl<'a> ImportsBuilder<'a> {
}
}
fn build(&mut self) -> Imports<'_> {
fn build(&self) -> Imports<'_> {
Imports {
tables: self.tables.values().as_slice(),
globals: self.globals.values().as_slice(),
memories: self.memories.values().as_slice(),
functions: self.functions.values().as_slice(),
instances: mem::take(&mut self.instances),
modules: mem::take(&mut self.modules)
.into_iter()
.map(|(_, m)| Box::new(m) as Box<_>)
.collect(),
}
}
}
/// An internal structure to this crate to build an `Instance` from a list of
/// items with names. This is intended to stay private for now, it'll need an
/// audit of APIs if publicly exported.
#[derive(Default)]
pub(crate) struct InstanceBuilder {
items: RuntimeInstance,
}
impl InstanceBuilder {
pub(crate) fn new() -> InstanceBuilder {
InstanceBuilder::default()
}
pub(crate) fn insert(&mut self, name: &str, item: impl Into<Extern>) {
let items = Rc::get_mut(&mut self.items).unwrap();
let export = match item.into() {
Extern::Func(i) => wasmtime_runtime::Export::Function(i.wasmtime_export().clone()),
Extern::Memory(i) => wasmtime_runtime::Export::Memory(i.wasmtime_export().clone()),
Extern::Table(i) => wasmtime_runtime::Export::Table(i.wasmtime_export().clone()),
Extern::Global(i) => wasmtime_runtime::Export::Global(i.wasmtime_export().clone()),
Extern::Instance(i) => wasmtime_runtime::Export::Instance(i.items.clone()),
Extern::Module(i) => wasmtime_runtime::Export::Module(Box::new(i.clone())),
};
items.insert(name.to_string(), export);
}
pub(crate) fn finish(self, store: &Store) -> Instance {
Instance {
store: store.clone(),
items: self.items,
}
}
}

View File

@@ -241,6 +241,7 @@ mod frame_info;
mod func;
mod instance;
mod linker;
mod memory;
mod module;
mod r#ref;
mod sig_registry;
@@ -257,6 +258,7 @@ pub use crate::frame_info::{FrameInfo, FrameSymbol};
pub use crate::func::*;
pub use crate::instance::Instance;
pub use crate::linker::*;
pub use crate::memory::*;
pub use crate::module::Module;
pub use crate::r#ref::ExternRef;
pub use crate::store::*;

View File

@@ -1,3 +1,4 @@
use crate::instance::InstanceBuilder;
use crate::{
Extern, ExternType, Func, FuncType, GlobalType, ImportType, Instance, IntoFunc, Module, Store,
Trap,
@@ -654,7 +655,37 @@ impl Linker {
},
kind: self.import_kind(import.ty()),
};
self.map.get(&key).cloned()
if let Some(result) = self.map.get(&key).cloned() {
return Some(result);
}
// This is a key location where the module linking proposal is
// implemented. This logic allows single-level imports of an instance to
// get satisfied by multiple definitions of items within this `Linker`.
//
// The instance being import is iterated over to load the names from
// this `Linker` (recursively calling `get`). If anything isn't defined
// we return `None` since the entire value isn't defined. Otherwise when
// all values are loaded it's assembled into an `Instance` and
// returned`.
//
// Note that this isn't exactly the speediest implementation in the
// world. Ideally we would pre-create the `Instance` instead of creating
// it each time a module is instantiated. For now though while the
// module linking proposal is under development this should hopefully
// suffice.
if let ExternType::Instance(t) = import.ty() {
if import.name().is_none() {
let mut builder = InstanceBuilder::new();
for export in t.exports() {
let item = self.get(&export.as_import(import.module()))?;
builder.insert(export.name(), item);
}
return Some(builder.finish(&self.store).into());
}
}
None
}
/// Returns all items defined for the `module` and `name` pair.

View File

@@ -0,0 +1,578 @@
use crate::trampoline::{generate_memory_export, StoreInstanceHandle};
use crate::{MemoryType, Store};
use anyhow::{anyhow, Result};
use std::slice;
/// Error for out of bounds [`Memory`] access.
#[derive(Debug)]
#[non_exhaustive]
pub struct MemoryAccessError {
// Keep struct internals private for future extensibility.
_private: (),
}
impl std::fmt::Display for MemoryAccessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "out of bounds memory access")
}
}
impl std::error::Error for MemoryAccessError {}
/// A WebAssembly linear memory.
///
/// WebAssembly memories represent a contiguous array of bytes that have a size
/// that is always a multiple of the WebAssembly page size, currently 64
/// kilobytes.
///
/// WebAssembly memory is used for global data, statics in C/C++/Rust, shadow
/// stack memory, etc. Accessing wasm memory is generally quite fast!
///
/// # `Memory` and `Clone`
///
/// Memories are internally reference counted so you can `clone` a `Memory`. The
/// cloning process only performs a shallow clone, so two cloned `Memory`
/// instances are equivalent in their functionality.
///
/// # `Memory` and threads
///
/// It is intended that `Memory` is safe to share between threads. At this time
/// this is not implemented in `wasmtime`, however. This is planned to be
/// implemented though!
///
/// # `Memory` and Safety
///
/// Linear memory is a lynchpin of safety for WebAssembly, but it turns out
/// there are very few ways to safely inspect the contents of a memory from the
/// host (Rust). This is because memory safety is quite tricky when working with
/// a `Memory` and we're still working out the best idioms to encapsulate
/// everything safely where it's efficient and ergonomic. This section of
/// documentation, however, is intended to help educate a bit what is and isn't
/// safe when working with `Memory`.
///
/// For safety purposes you can think of a `Memory` as a glorified
/// `Rc<UnsafeCell<Vec<u8>>>`. There are a few consequences of this
/// interpretation:
///
/// * At any time someone else may have access to the memory (hence the `Rc`).
/// This could be a wasm instance, other host code, or a set of wasm instances
/// which all reference a `Memory`. When in doubt assume someone else has a
/// handle to your `Memory`.
///
/// * At any time, memory can be read from or written to (hence the
/// `UnsafeCell`). Anyone with a handle to a wasm memory can read/write to it.
/// Primarily other instances can execute the `load` and `store` family of
/// instructions, as well as any other which modifies or reads memory.
///
/// * At any time memory may grow (hence the `Vec<..>`). Growth may relocate the
/// base memory pointer (similar to how `vec.push(...)` can change the result
/// of `.as_ptr()`)
///
/// So given that we're working roughly with `Rc<UnsafeCell<Vec<u8>>>` that's a
/// lot to keep in mind! It's hopefully though sort of setting the stage as to
/// what you can safely do with memories.
///
/// Let's run through a few safe examples first of how you can use a `Memory`.
///
/// ```rust
/// use wasmtime::{Memory, MemoryAccessError};
///
/// // Memory can be read and written safely with the `Memory::read` and
/// // `Memory::write` methods.
/// // An error is returned if the copy did not succeed.
/// fn safe_examples(mem: &Memory) -> Result<(), MemoryAccessError> {
/// let offset = 5;
/// mem.write(offset, b"hello")?;
/// let mut buffer = [0u8; 5];
/// mem.read(offset, &mut buffer)?;
/// assert_eq!(b"hello", &buffer);
/// Ok(())
/// }
///
/// // You can also get direct, unsafe access to the memory, but must manually
/// // ensure that safety invariants are upheld.
///
/// fn correct_unsafe_examples(mem: &Memory) {
/// // Just like wasm, it's safe to read memory almost at any time. The
/// // gotcha here is that we need to be sure to load from the correct base
/// // pointer and perform the bounds check correctly. So long as this is
/// // all self contained here (e.g. not arbitrary code in the middle) we're
/// // good to go.
/// let byte = unsafe { mem.data_unchecked()[0x123] };
///
/// // Short-lived borrows of memory are safe, but they must be scoped and
/// // not have code which modifies/etc `Memory` while the borrow is active.
/// // For example if you want to read a string from memory it is safe to do
/// // so:
/// let string_base = 0xdead;
/// let string_len = 0xbeef;
/// let string = unsafe {
/// let bytes = &mem.data_unchecked()[string_base..][..string_len];
/// match std::str::from_utf8(bytes) {
/// Ok(s) => s.to_string(), // copy out of wasm memory
/// Err(_) => panic!("not valid utf-8"),
/// }
/// };
///
/// // Additionally like wasm you can write to memory at any point in time,
/// // again making sure that after you get the unchecked slice you don't
/// // execute code which could read/write/modify `Memory`:
/// unsafe {
/// mem.data_unchecked_mut()[0x123] = 3;
/// }
///
/// // When working with *borrows* that point directly into wasm memory you
/// // need to be extremely careful. Any functionality that operates on a
/// // borrow into wasm memory needs to be thoroughly audited to effectively
/// // not touch the `Memory` at all
/// let data_base = 0xfeed;
/// let data_len = 0xface;
/// unsafe {
/// let data = &mem.data_unchecked()[data_base..][..data_len];
/// host_function_that_doesnt_touch_memory(data);
///
/// // effectively the same rules apply to mutable borrows
/// let data_mut = &mut mem.data_unchecked_mut()[data_base..][..data_len];
/// host_function_that_doesnt_touch_memory(data);
/// }
/// }
/// # fn host_function_that_doesnt_touch_memory(_: &[u8]){}
/// ```
///
/// It's worth also, however, covering some examples of **incorrect**,
/// **unsafe** usages of `Memory`. Do not do these things!
///
/// ```rust
/// # use anyhow::Result;
/// use wasmtime::Memory;
///
/// // NOTE: All code in this function is not safe to execute and may cause
/// // segfaults/undefined behavior at runtime. Do not copy/paste these examples
/// // into production code!
/// unsafe fn unsafe_examples(mem: &Memory) -> Result<()> {
/// // First and foremost, any borrow can be invalidated at any time via the
/// // `Memory::grow` function. This can relocate memory which causes any
/// // previous pointer to be possibly invalid now.
/// let pointer: &u8 = &mem.data_unchecked()[0x100];
/// mem.grow(1)?; // invalidates `pointer`!
/// // println!("{}", *pointer); // FATAL: use-after-free
///
/// // Note that the use-after-free also applies to slices, whether they're
/// // slices of bytes or strings.
/// let slice: &[u8] = &mem.data_unchecked()[0x100..0x102];
/// mem.grow(1)?; // invalidates `slice`!
/// // println!("{:?}", slice); // FATAL: use-after-free
///
/// // Due to the reference-counted nature of `Memory` note that literal
/// // calls to `Memory::grow` are not sufficient to audit for. You'll need
/// // to be careful that any mutation of `Memory` doesn't happen while
/// // you're holding an active borrow.
/// let slice: &[u8] = &mem.data_unchecked()[0x100..0x102];
/// some_other_function(); // may invalidate `slice` through another `mem` reference
/// // println!("{:?}", slice); // FATAL: maybe a use-after-free
///
/// // An especially subtle aspect of accessing a wasm instance's memory is
/// // that you need to be extremely careful about aliasing. Anyone at any
/// // time can call `data_unchecked()` or `data_unchecked_mut()`, which
/// // means you can easily have aliasing mutable references:
/// let ref1: &u8 = &mem.data_unchecked()[0x100];
/// let ref2: &mut u8 = &mut mem.data_unchecked_mut()[0x100];
/// // *ref2 = *ref1; // FATAL: violates Rust's aliasing rules
///
/// // Note that aliasing applies to strings as well, for example this is
/// // not valid because the slices overlap.
/// let slice1: &mut [u8] = &mut mem.data_unchecked_mut()[0x100..][..3];
/// let slice2: &mut [u8] = &mut mem.data_unchecked_mut()[0x102..][..4];
/// // println!("{:?} {:?}", slice1, slice2); // FATAL: aliasing mutable pointers
///
/// Ok(())
/// }
/// # fn some_other_function() {}
/// ```
///
/// Overall there's some general rules of thumb when working with `Memory` and
/// getting raw pointers inside of it:
///
/// * If you never have a "long lived" pointer into memory, you're likely in the
/// clear. Care still needs to be taken in threaded scenarios or when/where
/// data is read, but you'll be shielded from many classes of issues.
/// * Long-lived pointers must always respect Rust'a aliasing rules. It's ok for
/// shared borrows to overlap with each other, but mutable borrows must
/// overlap with nothing.
/// * Long-lived pointers are only valid if `Memory` isn't used in an unsafe way
/// while the pointer is valid. This includes both aliasing and growth.
///
/// At this point it's worth reiterating again that working with `Memory` is
/// pretty tricky and that's not great! Proposals such as [interface types] are
/// intended to prevent wasm modules from even needing to import/export memory
/// in the first place, which obviates the need for all of these safety caveats!
/// Additionally over time we're still working out the best idioms to expose in
/// `wasmtime`, so if you've got ideas or questions please feel free to [open an
/// issue]!
///
/// ## `Memory` Safety and Threads
///
/// Currently the `wasmtime` crate does not implement the wasm threads proposal,
/// but it is planned to do so. It's additionally worthwhile discussing how this
/// affects memory safety and what was previously just discussed as well.
///
/// Once threads are added into the mix, all of the above rules still apply.
/// There's an additional, rule, however, that all reads and writes can
/// happen *concurrently*. This effectively means that long-lived borrows into
/// wasm memory are virtually never safe to have.
///
/// Mutable pointers are fundamentally unsafe to have in a concurrent scenario
/// in the face of arbitrary wasm code. Only if you dynamically know for sure
/// that wasm won't access a region would it be safe to construct a mutable
/// pointer. Additionally even shared pointers are largely unsafe because their
/// underlying contents may change, so unless `UnsafeCell` in one form or
/// another is used everywhere there's no safety.
///
/// One important point about concurrency is that `Memory::grow` can indeed
/// happen concurrently. This, however, will never relocate the base pointer.
/// Shared memories must always have a maximum size and they will be
/// preallocated such that growth will never relocate the base pointer. The
/// maximum length of the memory, however, will change over time.
///
/// Overall the general rule of thumb for shared memories is that you must
/// atomically read and write everything. Nothing can be borrowed and everything
/// must be eagerly copied out.
///
/// [interface types]: https://github.com/webassembly/interface-types
/// [open an issue]: https://github.com/bytecodealliance/wasmtime/issues/new
#[derive(Clone)]
pub struct Memory {
pub(crate) instance: StoreInstanceHandle,
wasmtime_export: wasmtime_runtime::ExportMemory,
}
impl Memory {
/// Creates a new WebAssembly memory given the configuration of `ty`.
///
/// The `store` argument is a general location for cache information, and
/// otherwise the memory will immediately be allocated according to the
/// type's configuration. All WebAssembly memory is initialized to zero.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// let engine = Engine::default();
/// let store = Store::new(&engine);
///
/// let memory_ty = MemoryType::new(Limits::new(1, None));
/// let memory = Memory::new(&store, memory_ty);
///
/// let module = Module::new(&engine, "(module (memory (import \"\" \"\") 1))")?;
/// let instance = Instance::new(&store, &module, &[memory.into()])?;
/// // ...
/// # Ok(())
/// # }
/// ```
pub fn new(store: &Store, ty: MemoryType) -> Memory {
let (instance, wasmtime_export) =
generate_memory_export(store, &ty).expect("generated memory");
Memory {
instance,
wasmtime_export,
}
}
/// Returns the underlying type of this memory.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// let engine = Engine::default();
/// let store = Store::new(&engine);
/// let module = Module::new(&engine, "(module (memory (export \"mem\") 1))")?;
/// let instance = Instance::new(&store, &module, &[])?;
/// let memory = instance.get_memory("mem").unwrap();
/// let ty = memory.ty();
/// assert_eq!(ty.limits().min(), 1);
/// # Ok(())
/// # }
/// ```
pub fn ty(&self) -> MemoryType {
MemoryType::from_wasmtime_memory(&self.wasmtime_export.memory.memory)
}
/// Safely reads memory contents at the given offset into a buffer.
///
/// The entire buffer will be filled.
///
/// If offset + buffer length exceed the current memory capacity, then the
/// buffer is left untouched and a [`MemoryAccessError`] is returned.
pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), MemoryAccessError> {
unsafe {
let slice = self
.data_unchecked()
.get(offset..)
.and_then(|s| s.get(..buffer.len()))
.ok_or(MemoryAccessError { _private: () })?;
buffer.copy_from_slice(slice);
Ok(())
}
}
/// Safely writes contents of a buffer to this memory at the given offset.
///
/// If the offset + buffer length exceed current memory capacity, then none
/// of the buffer is written to memory and a [`MemoryAccessError`] is
/// returned.
pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), MemoryAccessError> {
unsafe {
self.data_unchecked_mut()
.get_mut(offset..)
.and_then(|s| s.get_mut(..buffer.len()))
.ok_or(MemoryAccessError { _private: () })?
.copy_from_slice(buffer);
Ok(())
}
}
/// Returns this memory as a slice view that can be read natively in Rust.
///
/// # Safety
///
/// This is an unsafe operation because there is no guarantee that the
/// following operations do not happen concurrently while the slice is in
/// use:
///
/// * Data could be modified by calling into a wasm module.
/// * Memory could be relocated through growth by calling into a wasm
/// module.
/// * When threads are supported, non-atomic reads will race with other
/// writes.
///
/// Extreme care need be taken when the data of a `Memory` is read. The
/// above invariants all need to be upheld at a bare minimum, and in
/// general you'll need to ensure that while you're looking at slice you're
/// the only one who can possibly look at the slice and read/write it.
///
/// Be sure to keep in mind that `Memory` is reference counted, meaning
/// that there may be other users of this `Memory` instance elsewhere in
/// your program. Additionally `Memory` can be shared and used in any number
/// of wasm instances, so calling any wasm code should be considered
/// dangerous while you're holding a slice of memory.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub unsafe fn data_unchecked(&self) -> &[u8] {
self.data_unchecked_mut()
}
/// Returns this memory as a slice view that can be read and written
/// natively in Rust.
///
/// # Safety
///
/// All of the same safety caveats of [`Memory::data_unchecked`] apply
/// here, doubly so because this is returning a mutable slice! As a
/// double-extra reminder, remember that `Memory` is reference counted, so
/// you can very easily acquire two mutable slices by simply calling this
/// function twice. Extreme caution should be used when using this method,
/// and in general you probably want to result to unsafe accessors and the
/// `data` methods below.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub unsafe fn data_unchecked_mut(&self) -> &mut [u8] {
let definition = &*self.wasmtime_export.definition;
slice::from_raw_parts_mut(definition.base, definition.current_length)
}
/// Returns the base pointer, in the host's address space, that the memory
/// is located at.
///
/// When reading and manipulating memory be sure to read up on the caveats
/// of [`Memory::data_unchecked`] to make sure that you can safely
/// read/write the memory.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub fn data_ptr(&self) -> *mut u8 {
unsafe { (*self.wasmtime_export.definition).base }
}
/// Returns the byte length of this memory.
///
/// The returned value will be a multiple of the wasm page size, 64k.
///
/// For more information and examples see the documentation on the
/// [`Memory`] type.
pub fn data_size(&self) -> usize {
unsafe { (*self.wasmtime_export.definition).current_length }
}
/// Returns the size, in pages, of this wasm memory.
pub fn size(&self) -> u32 {
(self.data_size() / wasmtime_environ::WASM_PAGE_SIZE as usize) as u32
}
/// Grows this WebAssembly memory by `delta` pages.
///
/// This will attempt to add `delta` more pages of memory on to the end of
/// this `Memory` instance. If successful this may relocate the memory and
/// cause [`Memory::data_ptr`] to return a new value. Additionally previous
/// slices into this memory may no longer be valid.
///
/// On success returns the number of pages this memory previously had
/// before the growth succeeded.
///
/// # Errors
///
/// Returns an error if memory could not be grown, for example if it exceeds
/// the maximum limits of this memory.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::Result<()> {
/// let engine = Engine::default();
/// let store = Store::new(&engine);
/// let module = Module::new(&engine, "(module (memory (export \"mem\") 1 2))")?;
/// let instance = Instance::new(&store, &module, &[])?;
/// let memory = instance.get_memory("mem").unwrap();
///
/// assert_eq!(memory.size(), 1);
/// assert_eq!(memory.grow(1)?, 1);
/// assert_eq!(memory.size(), 2);
/// assert!(memory.grow(1).is_err());
/// assert_eq!(memory.size(), 2);
/// assert_eq!(memory.grow(0)?, 2);
/// # Ok(())
/// # }
/// ```
pub fn grow(&self, delta: u32) -> Result<u32> {
let index = self
.instance
.memory_index(unsafe { &*self.wasmtime_export.definition });
self.instance
.memory_grow(index, delta)
.ok_or_else(|| anyhow!("failed to grow memory"))
}
pub(crate) unsafe fn from_wasmtime_memory(
wasmtime_export: &wasmtime_runtime::ExportMemory,
store: &Store,
) -> Memory {
Memory {
instance: store.existing_vmctx(wasmtime_export.vmctx),
wasmtime_export: wasmtime_export.clone(),
}
}
pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Memory {
&self.wasmtime_export.memory.memory
}
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMMemoryImport {
wasmtime_runtime::VMMemoryImport {
from: self.wasmtime_export.definition,
vmctx: self.wasmtime_export.vmctx,
}
}
pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::ExportMemory {
&self.wasmtime_export
}
}
/// A linear memory. This trait provides an interface for raw memory buffers which are used
/// by wasmtime, e.g. inside ['Memory']. Such buffers are in principle not thread safe.
/// By implementing this trait together with MemoryCreator,
/// one can supply wasmtime with custom allocated host managed memory.
///
/// # Safety
/// The memory should be page aligned and a multiple of page size.
/// To prevent possible silent overflows, the memory should be protected by a guard page.
/// Additionally the safety concerns explained in ['Memory'], for accessing the memory
/// apply here as well.
///
/// Note that this is a relatively new and experimental feature and it is recommended
/// to be familiar with wasmtime runtime code to use it.
pub unsafe trait LinearMemory {
/// Returns the number of allocated wasm pages.
fn size(&self) -> u32;
/// Grow memory by the specified amount of wasm pages.
///
/// Returns `None` if memory can't be grown by the specified amount
/// of wasm pages.
fn grow(&self, delta: u32) -> Option<u32>;
/// Return the allocated memory as a mutable pointer to u8.
fn as_ptr(&self) -> *mut u8;
}
/// A memory creator. Can be used to provide a memory creator
/// to wasmtime which supplies host managed memory.
///
/// # Safety
/// This trait is unsafe, as the memory safety depends on proper implementation of
/// memory management. Memories created by the MemoryCreator should always be treated
/// as owned by wasmtime instance, and any modification of them outside of wasmtime
/// invoked routines is unsafe and may lead to corruption.
///
/// Note that this is a relatively new and experimental feature and it is recommended
/// to be familiar with wasmtime runtime code to use it.
pub unsafe trait MemoryCreator: Send + Sync {
/// Create a new `LinearMemory` object from the specified parameters.
///
/// The type of memory being created is specified by `ty` which indicates
/// both the minimum and maximum size, in wasm pages.
///
/// The `reserved_size_in_bytes` value indicates the expected size of the
/// reservation that is to be made for this memory. If this value is `None`
/// than the implementation is free to allocate memory as it sees fit. If
/// the value is `Some`, however, then the implementation is expected to
/// reserve that many bytes for the memory's allocation, plus the guard
/// size at the end. Note that this reservation need only be a virtual
/// memory reservation, physical memory does not need to be allocated
/// immediately. In this case `grow` should never move the base pointer and
/// the maximum size of `ty` is guaranteed to fit within `reserved_size_in_bytes`.
///
/// The `guard_size_in_bytes` parameter indicates how many bytes of space, after the
/// memory allocation, is expected to be unmapped. JIT code will elide
/// bounds checks based on the `guard_size_in_bytes` provided, so for JIT code to
/// work correctly the memory returned will need to be properly guarded with
/// `guard_size_in_bytes` bytes left unmapped after the base allocation.
///
/// Note that the `reserved_size_in_bytes` and `guard_size_in_bytes` options are tuned from
/// the various [`Config`](crate::Config) methods about memory
/// sizes/guards. Additionally these two values are guaranteed to be
/// multiples of the system page size.
fn new_memory(
&self,
ty: MemoryType,
reserved_size_in_bytes: Option<u64>,
guard_size_in_bytes: u64,
) -> Result<Box<dyn LinearMemory>, String>;
}
#[cfg(test)]
mod tests {
use crate::*;
// Assert that creating a memory via `Memory::new` respects the limits/tunables
// in `Config`.
#[test]
fn respect_tunables() {
let mut cfg = Config::new();
cfg.static_memory_maximum_size(0)
.dynamic_memory_guard_size(0);
let store = Store::new(&Engine::new(&cfg));
let ty = MemoryType::new(Limits::new(1, None));
let mem = Memory::new(&store, ty);
assert_eq!(mem.wasmtime_export.memory.offset_guard_size, 0);
match mem.wasmtime_export.memory.style {
wasmtime_environ::MemoryStyle::Dynamic => {}
other => panic!("unexpected style {:?}", other),
}
}
}

View File

@@ -2,12 +2,16 @@ use crate::types::{ExportType, ExternType, ImportType};
use crate::{Engine, ModuleType};
use anyhow::{bail, Context, Result};
use bincode::Options;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::Hash;
use std::path::Path;
use std::sync::Arc;
use wasmparser::Validator;
#[cfg(feature = "cache")]
use wasmtime_cache::ModuleCacheEntry;
use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::ModuleIndex;
use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables};
/// A compiled WebAssembly module, ready to be instantiated.
@@ -80,14 +84,72 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables};
/// [`Config`]: crate::Config
#[derive(Clone)]
pub struct Module {
engine: Engine,
data: Arc<ModuleData>,
index: usize,
inner: Arc<ModuleInner>,
}
pub(crate) struct ModuleData {
pub(crate) types: Arc<TypeTables>,
pub(crate) modules: Vec<CompiledModule>,
struct ModuleInner {
engine: Engine,
/// The compiled artifacts for this module that will be instantiated and
/// executed.
module: Arc<CompiledModule>,
/// Closed-over compilation artifacts used to create submodules when this
/// module is instantiated.
artifact_upvars: Vec<Arc<CompiledModule>>,
/// Closed-over module values which are used when this module is
/// instantiated.
module_upvars: Vec<Module>,
/// Type information of this module and all `artifact_upvars` compiled
/// modules.
types: Arc<TypeTables>,
}
/// A small helper struct which defines modules are serialized.
#[derive(serde::Serialize, serde::Deserialize)]
struct ModuleSerialized<'a> {
/// All compiled artifacts neeeded by this module, where the last entry in
/// this list is the artifacts for the module itself.
artifacts: Vec<MyCow<'a, CompilationArtifacts>>,
/// Closed-over module values that are also needed for this module.
modules: Vec<ModuleSerialized<'a>>,
/// The index into the list of type tables that are used for this module's
/// type tables.
type_tables: usize,
}
// This is like `std::borrow::Cow` but it doesn't have a `Clone` bound on `T`
enum MyCow<'a, T> {
Borrowed(&'a T),
Owned(T),
}
impl<'a, T> MyCow<'a, T> {
fn unwrap_owned(self) -> T {
match self {
MyCow::Owned(val) => val,
MyCow::Borrowed(_) => unreachable!(),
}
}
}
impl<'a, T: Serialize> Serialize for MyCow<'a, T> {
fn serialize<S>(&self, dst: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
match self {
MyCow::Borrowed(val) => val.serialize(dst),
MyCow::Owned(val) => val.serialize(dst),
}
}
}
impl<'a, 'b, T: Deserialize<'a>> Deserialize<'a> for MyCow<'b, T> {
fn deserialize<D>(src: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'a>,
{
Ok(MyCow::Owned(T::deserialize(src)?))
}
}
impl Module {
@@ -169,7 +231,8 @@ impl Module {
/// See [`Module::new`] for other details.
pub fn new_with_name(engine: &Engine, bytes: impl AsRef<[u8]>, name: &str) -> Result<Module> {
let mut module = Module::new(engine, bytes.as_ref())?;
Arc::get_mut(&mut module.data).unwrap().modules[module.index]
Arc::get_mut(&mut Arc::get_mut(&mut module.inner).unwrap().module)
.unwrap()
.module_mut()
.expect("mutable module")
.name = Some(name.to_string());
@@ -245,24 +308,30 @@ impl Module {
/// ```
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
#[cfg(feature = "cache")]
let (artifacts, types) = ModuleCacheEntry::new("wasmtime", engine.cache_config())
.get_data((engine.compiler(), binary), |(compiler, binary)| {
CompilationArtifacts::build(compiler, binary)
})?;
let (main_module, artifacts, types) =
ModuleCacheEntry::new("wasmtime", engine.cache_config())
.get_data((engine.compiler(), binary), |(compiler, binary)| {
CompilationArtifacts::build(compiler, binary)
})?;
#[cfg(not(feature = "cache"))]
let (artifacts, types) = CompilationArtifacts::build(engine.compiler(), binary)?;
let (main_module, artifacts, types) =
CompilationArtifacts::build(engine.compiler(), binary)?;
let modules = CompiledModule::from_artifacts_list(
let mut modules = CompiledModule::from_artifacts_list(
artifacts,
engine.compiler().isa(),
&*engine.config().profiler,
)?;
let module = modules.remove(main_module);
let types = Arc::new(types);
Ok(Module {
engine: engine.clone(),
index: 0,
data: Arc::new(ModuleData { types, modules }),
inner: Arc::new(ModuleInner {
engine: engine.clone(),
module,
types: Arc::new(types),
artifact_upvars: modules,
module_upvars: Vec::new(),
}),
})
}
@@ -311,21 +380,46 @@ impl Module {
/// Serialize compilation artifacts to the buffer. See also `deseriaize`.
pub fn serialize(&self) -> Result<Vec<u8>> {
let artifacts = (
compiler_fingerprint(&self.engine),
self.data
.modules
.iter()
.map(|i| i.compilation_artifacts())
.collect::<Vec<_>>(),
&*self.data.types,
self.index,
);
let mut pushed = HashMap::new();
let mut tables = Vec::new();
let module = self.serialized_module(&mut pushed, &mut tables);
let artifacts = (compiler_fingerprint(self.engine()), tables, module);
let buffer = bincode_options().serialize(&artifacts)?;
Ok(buffer)
}
fn serialized_module<'a>(
&'a self,
type_tables_pushed: &mut HashMap<usize, usize>,
type_tables: &mut Vec<&'a TypeTables>,
) -> ModuleSerialized<'a> {
// Deduplicate `Arc<TypeTables>` using our two parameters to ensure we
// serialize type tables as little as possible.
let ptr = Arc::as_ptr(self.types());
let type_tables_idx = *type_tables_pushed.entry(ptr as usize).or_insert_with(|| {
type_tables.push(self.types());
type_tables.len() - 1
});
ModuleSerialized {
artifacts: self
.inner
.artifact_upvars
.iter()
.map(|i| MyCow::Borrowed(i.compilation_artifacts()))
.chain(Some(MyCow::Borrowed(
self.compiled_module().compilation_artifacts(),
)))
.collect(),
modules: self
.inner
.module_upvars
.iter()
.map(|i| i.serialized_module(type_tables_pushed, type_tables))
.collect(),
type_tables: type_tables_idx,
}
}
/// Deserializes and creates a module from the compilation artifacts.
/// The `serialize` saves the compilation artifacts along with the host
/// fingerprint, which consists of target, compiler flags, and wasmtime
@@ -336,44 +430,117 @@ impl Module {
/// for modifications or curruptions. All responsibily of signing and its
/// verification falls on the embedder.
pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result<Module> {
let expected_fingerprint = compiler_fingerprint(engine);
let (fingerprint, artifacts, types, index) = bincode_options()
.deserialize::<(u64, _, _, _)>(serialized)
let (fingerprint, types, serialized) = bincode_options()
.deserialize::<(u64, Vec<TypeTables>, _)>(serialized)
.context("Deserialize compilation artifacts")?;
if fingerprint != expected_fingerprint {
if fingerprint != compiler_fingerprint(engine) {
bail!("Incompatible compilation artifact");
}
let modules = CompiledModule::from_artifacts_list(
artifacts,
engine.compiler().isa(),
&*engine.config().profiler,
)?;
let types = types.into_iter().map(Arc::new).collect::<Vec<_>>();
return mk(engine, &types, serialized);
let types = Arc::new(types);
Ok(Module {
engine: engine.clone(),
index,
data: Arc::new(ModuleData { modules, types }),
})
}
pub(crate) fn compiled_module(&self) -> &CompiledModule {
&self.data.modules[self.index]
}
pub(crate) fn submodule(&self, index: usize) -> Module {
assert!(index < self.data.modules.len());
Module {
engine: self.engine.clone(),
data: self.data.clone(),
index,
fn mk(
engine: &Engine,
types: &Vec<Arc<TypeTables>>,
module: ModuleSerialized<'_>,
) -> Result<Module> {
let mut artifacts = CompiledModule::from_artifacts_list(
module
.artifacts
.into_iter()
.map(|i| i.unwrap_owned())
.collect(),
engine.compiler().isa(),
&*engine.config().profiler,
)?;
let inner = ModuleInner {
engine: engine.clone(),
types: types[module.type_tables].clone(),
module: artifacts.pop().unwrap(),
artifact_upvars: artifacts,
module_upvars: module
.modules
.into_iter()
.map(|m| mk(engine, types, m))
.collect::<Result<Vec<_>>>()?,
};
Ok(Module {
inner: Arc::new(inner),
})
}
}
/// Creates a submodule `Module` value from the specified parameters.
///
/// This is used for creating submodules as part of module instantiation.
///
/// * `artifact_index` - the index in `artifact_upvars` that we're creating
/// a module for
/// * `artifact_upvars` - the mapping of indices of what artifact upvars are
/// needed for the submodule. The length of this array is the length of
/// the upvars array in the submodule to be created, and each element of
/// this array is an index into this module's upvar array.
/// * `module_upvars` - similar to `artifact_upvars` this is a mapping of
/// how to create the e`module_upvars` of the submodule being created.
/// Each entry in this array is either an index into this module's own
/// module upvars array or it's an index into `modules`, the list of
/// modules so far for the instance where this submodule is being
/// created.
/// * `modules` - array indexed by `module_upvars`.
///
/// Note that the real meat of this happens in `ModuleEnvironment`
/// translation inside of `wasmtime_environ`. This just does the easy thing
/// of handling all the indices, over there is where the indices are
/// actually calculated and such.
pub(crate) fn create_submodule(
&self,
artifact_index: usize,
artifact_upvars: &[usize],
module_upvars: &[wasmtime_environ::ModuleUpvar],
modules: &PrimaryMap<ModuleIndex, Module>,
) -> Module {
Module {
inner: Arc::new(ModuleInner {
types: self.types().clone(),
engine: self.engine().clone(),
module: self.inner.artifact_upvars[artifact_index].clone(),
artifact_upvars: artifact_upvars
.iter()
.map(|i| self.inner.artifact_upvars[*i].clone())
.collect(),
module_upvars: module_upvars
.iter()
.map(|i| match *i {
wasmtime_environ::ModuleUpvar::Inherit(i) => {
self.inner.module_upvars[i].clone()
}
wasmtime_environ::ModuleUpvar::Local(i) => modules[i].clone(),
})
.collect(),
}),
}
}
pub(crate) fn compiled_module(&self) -> &CompiledModule {
&self.inner.module
}
pub(crate) fn env_module(&self) -> &wasmtime_environ::Module {
self.compiled_module().module()
}
pub(crate) fn types(&self) -> &Arc<TypeTables> {
&self.data.types
&self.inner.types
}
/// Looks up the module upvar value at the `index` specified.
///
/// Note that this panics if `index` is out of bounds since this should
/// only be called for valid indices as part of instantiation.
pub(crate) fn module_upvar(&self, index: usize) -> &Module {
&self.inner.module_upvars[index]
}
/// Returns identifier/name that this [`Module`] has. This name
@@ -583,7 +750,7 @@ impl Module {
/// Returns the [`Engine`] that this [`Module`] was compiled by.
pub fn engine(&self) -> &Engine {
&self.engine
&self.inner.engine
}
}

View File

@@ -4,8 +4,9 @@ use crate::trampoline::StoreInstanceHandle;
use crate::{Engine, Module};
use anyhow::{bail, Result};
use std::any::Any;
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use std::collections::HashSet;
use std::convert::TryFrom;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::rc::{Rc, Weak};
@@ -13,8 +14,8 @@ use std::sync::Arc;
use wasmtime_environ::wasm;
use wasmtime_jit::{CompiledModule, ModuleCode, TypeTables};
use wasmtime_runtime::{
InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry, TrapInfo, VMExternRef,
VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex,
InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry, TrapInfo, VMContext,
VMExternRef, VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex,
};
/// A `Store` is a collection of WebAssembly instances and host-defined items.
@@ -67,6 +68,15 @@ pub(crate) struct StoreInner {
/// Set of all compiled modules that we're holding a strong reference to
/// the module's code for. This includes JIT functions, trampolines, etc.
modules: RefCell<HashSet<ArcModuleCode>>,
// Numbers of resources instantiated in this store.
instance_count: Cell<usize>,
memory_count: Cell<usize>,
table_count: Cell<usize>,
/// An adjustment to add to the fuel consumed value in `interrupts` above
/// to get the true amount of fuel consumed.
fuel_adj: Cell<i64>,
}
struct HostInfoKey(VMExternRef);
@@ -109,6 +119,10 @@ impl Store {
stack_map_registry: StackMapRegistry::default(),
frame_info: Default::default(),
modules: Default::default(),
instance_count: Default::default(),
memory_count: Default::default(),
table_count: Default::default(),
fuel_adj: Cell::new(0),
}),
}
}
@@ -188,8 +202,9 @@ impl Store {
None => return,
};
// Only register this module if it hasn't already been registered.
if !self.is_wasm_code(first_pc) {
self.inner.frame_info.borrow_mut().register(module);
let mut info = self.inner.frame_info.borrow_mut();
if !info.contains_pc(first_pc) {
info.register(module);
}
}
@@ -213,6 +228,43 @@ impl Store {
}
}
pub(crate) fn bump_resource_counts(&self, module: &Module) -> Result<()> {
let config = self.engine().config();
fn bump(slot: &Cell<usize>, max: usize, amt: usize, desc: &str) -> Result<()> {
let new = slot.get().saturating_add(amt);
if new > max {
bail!(
"resource limit exceeded: {} count too high at {}",
desc,
new
);
}
slot.set(new);
Ok(())
}
let module = module.env_module();
let memories = module.memory_plans.len() - module.num_imported_memories;
let tables = module.table_plans.len() - module.num_imported_tables;
bump(
&self.inner.instance_count,
config.max_instances,
1,
"instance",
)?;
bump(
&self.inner.memory_count,
config.max_memories,
memories,
"memory",
)?;
bump(&self.inner.table_count, config.max_tables, tables, "table")?;
Ok(())
}
pub(crate) unsafe fn add_instance(&self, handle: InstanceHandle) -> StoreInstanceHandle {
self.inner.instances.borrow_mut().push(handle.clone());
StoreInstanceHandle {
@@ -234,6 +286,10 @@ impl Store {
}
}
pub(crate) unsafe fn existing_vmctx(&self, cx: *mut VMContext) -> StoreInstanceHandle {
self.existing_instance_handle(InstanceHandle::from_vmctx(cx))
}
pub(crate) fn weak(&self) -> Weak<StoreInner> {
Rc::downgrade(&self.inner)
}
@@ -377,6 +433,64 @@ impl Store {
);
}
}
/// Returns the amount of fuel consumed by this store's execution so far.
///
/// If fuel consumption is not enabled via
/// [`Config::consume_fuel`](crate::Config::consume_fuel) then this
/// function will return `None`. Also note that fuel, if enabled, must be
/// originally configured via [`Store::add_fuel`].
pub fn fuel_consumed(&self) -> Option<u64> {
if !self.engine().config().tunables.consume_fuel {
return None;
}
let consumed = unsafe { *self.inner.interrupts.fuel_consumed.get() };
Some(u64::try_from(self.inner.fuel_adj.get() + consumed).unwrap())
}
/// Adds fuel to this [`Store`] for wasm to consume while executing.
///
/// For this method to work fuel consumption must be enabled via
/// [`Config::consume_fuel`](crate::Config::consume_fuel). By default a
/// [`Store`] starts with 0 fuel for wasm to execute with (meaning it will
/// immediately trap). This function must be called for the store to have
/// some fuel to allow WebAssembly to execute.
///
/// Note that at this time when fuel is entirely consumed it will cause
/// wasm to trap. More usages of fuel are planned for the future.
///
/// # Panics
///
/// This function will panic if the store's [`Config`](crate::Config) did
/// not have fuel consumption enabled.
pub fn add_fuel(&self, fuel: u64) {
assert!(self.engine().config().tunables.consume_fuel);
// Fuel is stored as an i64, so we need to cast it. If the provided fuel
// value overflows that just assume that i64::max will suffice. Wasm
// execution isn't fast enough to burn through i64::max fuel in any
// reasonable amount of time anyway.
let fuel = i64::try_from(fuel).unwrap_or(i64::max_value());
let adj = self.inner.fuel_adj.get();
let consumed_ptr = unsafe { &mut *self.inner.interrupts.fuel_consumed.get() };
match (consumed_ptr.checked_sub(fuel), adj.checked_add(fuel)) {
// If we succesfully did arithmetic without overflowing then we can
// just update our fields.
(Some(consumed), Some(adj)) => {
self.inner.fuel_adj.set(adj);
*consumed_ptr = consumed;
}
// Otherwise something overflowed. Make sure that we preserve the
// amount of fuel that's already consumed, but otherwise assume that
// we were given infinite fuel.
_ => {
self.inner.fuel_adj.set(i64::max_value());
*consumed_ptr = (*consumed_ptr + adj) - i64::max_value();
}
}
}
}
unsafe impl TrapInfo for Store {
@@ -384,8 +498,8 @@ unsafe impl TrapInfo for Store {
self
}
fn is_wasm_code(&self, addr: usize) -> bool {
self.frame_info().borrow().contains_pc(addr)
fn is_wasm_trap(&self, addr: usize) -> bool {
self.frame_info().borrow().lookup_trap_info(addr).is_some()
}
fn custom_signal_handler(&self, call: &dyn Fn(&SignalHandler) -> bool) -> bool {
@@ -398,6 +512,23 @@ unsafe impl TrapInfo for Store {
fn max_wasm_stack(&self) -> usize {
self.engine().config().max_wasm_stack
}
fn out_of_gas(&self) {
#[derive(Debug)]
struct OutOfGas;
impl fmt::Display for OutOfGas {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("all fuel consumed by WebAssembly")
}
}
impl std::error::Error for OutOfGas {}
unsafe {
wasmtime_runtime::raise_lib_trap(wasmtime_runtime::Trap::User(Box::new(OutOfGas)))
}
}
}
impl Default for Store {
@@ -431,6 +562,13 @@ pub struct InterruptHandle {
interrupts: Arc<VMInterrupts>,
}
// The `VMInterrupts` type is a pod-type with no destructor, and we only access
// `interrupts` from other threads, so add in these trait impls which are
// otherwise not available due to the `fuel_consumed` variable in
// `VMInterrupts`.
unsafe impl Send for InterruptHandle {}
unsafe impl Sync for InterruptHandle {}
impl InterruptHandle {
/// Flags that execution within this handle's original [`Store`] should be
/// interrupted.

View File

@@ -48,7 +48,7 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
module
.initializers
.push(wasmtime_environ::Initializer::Import {
module: "".into(),
name: "".into(),
field: None,
index: wasm::EntityIndex::Function(func_index),
});
@@ -80,7 +80,7 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
)?;
if let Some(x) = externref_init {
match handle.lookup("").unwrap() {
match handle.lookup_by_declaration(&wasm::EntityIndex::Global(global_id)) {
wasmtime_runtime::Export::Global(g) => unsafe {
*(*g.definition).as_externref_mut() = Some(x.inner);
},

View File

@@ -1,5 +1,5 @@
use super::create_handle::create_handle;
use crate::externals::{LinearMemory, MemoryCreator};
use crate::memory::{LinearMemory, MemoryCreator};
use crate::trampoline::StoreInstanceHandle;
use crate::Store;
use crate::{Limits, MemoryType};

View File

@@ -16,6 +16,7 @@ use crate::{FuncType, GlobalType, MemoryType, Store, TableType, Trap, Val};
use anyhow::Result;
use std::any::Any;
use std::ops::Deref;
use wasmtime_environ::wasm;
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody, VMTrampoline};
/// A wrapper around `wasmtime_runtime::InstanceHandle` which pairs it with the
@@ -55,7 +56,8 @@ pub fn generate_func_export(
VMTrampoline,
)> {
let (instance, trampoline) = create_handle_with_function(ft, func, store)?;
match instance.lookup("").expect("trampoline export") {
let idx = wasm::EntityIndex::Function(wasm::FuncIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Function(f) => Ok((instance, f, trampoline)),
_ => unreachable!(),
}
@@ -72,7 +74,8 @@ pub unsafe fn generate_raw_func_export(
state: Box<dyn Any>,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportFunction)> {
let instance = func::create_handle_with_raw_function(ft, func, trampoline, store, state)?;
match instance.lookup("").expect("trampoline export") {
let idx = wasm::EntityIndex::Function(wasm::FuncIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Function(f) => Ok((instance, f)),
_ => unreachable!(),
}
@@ -84,7 +87,8 @@ pub fn generate_global_export(
val: Val,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportGlobal)> {
let instance = create_global(store, gt, val)?;
match instance.lookup("").expect("global export") {
let idx = wasm::EntityIndex::Global(wasm::GlobalIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Global(g) => Ok((instance, g)),
_ => unreachable!(),
}
@@ -95,7 +99,8 @@ pub fn generate_memory_export(
m: &MemoryType,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportMemory)> {
let instance = create_handle_with_memory(store, m)?;
match instance.lookup("").expect("memory export") {
let idx = wasm::EntityIndex::Memory(wasm::MemoryIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Memory(m) => Ok((instance, m)),
_ => unreachable!(),
}
@@ -106,7 +111,8 @@ pub fn generate_table_export(
t: &TableType,
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportTable)> {
let instance = create_handle_with_table(store, t)?;
match instance.lookup("").expect("table export") {
let idx = wasm::EntityIndex::Table(wasm::TableIndex::from_u32(0));
match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Table(t) => Ok((instance, t)),
_ => unreachable!(),
}

View File

@@ -23,6 +23,7 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
let table_plan = wasmtime_environ::TablePlan::for_table(table, &tunable);
let table_id = module.table_plans.push(table_plan);
// TODO: can this `exports.insert` get removed?
module
.exports
.insert(String::new(), wasm::EntityIndex::Table(table_id));

View File

@@ -478,9 +478,9 @@ impl ModuleType {
/// Returns the list of imports associated with this module type.
pub fn imports(&self) -> impl ExactSizeIterator<Item = ImportType<'_>> {
self.imports.iter().map(|(module, name, ty)| ImportType {
module,
name: name.as_deref(),
self.imports.iter().map(|(name, field, ty)| ImportType {
module: name,
name: field.as_deref(),
ty: EntityOrExtern::Extern(ty),
})
}
@@ -506,13 +506,7 @@ impl ModuleType {
imports: ty
.imports
.iter()
.map(|(m, name, ty)| {
(
m.to_string(),
name.as_ref().map(|n| n.to_string()),
ExternType::from_wasmtime(types, ty),
)
})
.map(|(m, ty)| (m.to_string(), None, ExternType::from_wasmtime(types, ty)))
.collect(),
}
}
@@ -615,8 +609,9 @@ impl<'module> ImportType<'module> {
/// Returns the field name of the module that this import is expected to
/// come from.
///
/// Note that the name can be `None` for the module linking proposal. If the
/// module linking proposal is not enabled it's safe to unwrap this.
/// Note that this is optional due to the module linking proposal. If the
/// module linking proposal is enabled this is always `None`, otherwise this
/// is always `Some`.
pub fn name(&self) -> Option<&'module str> {
self.name
}
@@ -683,6 +678,17 @@ impl<'module> ExportType<'module> {
EntityOrExtern::Extern(e) => (*e).clone(),
}
}
pub(crate) fn as_import<'a>(&self, module: &'a str) -> ImportType<'a>
where
'module: 'a,
{
ImportType {
module,
name: Some(self.name),
ty: self.ty.clone(),
}
}
}
impl<'module> fmt::Debug for ExportType<'module> {

View File

@@ -1,5 +1,5 @@
use crate::Store;
use std::sync::Arc;
use crate::{Extern, Store};
use anyhow::{bail, Context, Result};
use wasmtime_environ::wasm::{
EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table,
};
@@ -11,22 +11,27 @@ pub struct MatchCx<'a> {
}
impl MatchCx<'_> {
pub fn global(&self, expected: &Global, actual: &crate::Global) -> bool {
pub fn global(&self, expected: &Global, actual: &crate::Global) -> Result<()> {
self.global_ty(expected, actual.wasmtime_ty())
}
fn global_ty(&self, expected: &Global, actual: &Global) -> bool {
expected.ty == actual.ty
fn global_ty(&self, expected: &Global, actual: &Global) -> Result<()> {
if expected.ty == actual.ty
&& expected.wasm_ty == actual.wasm_ty
&& expected.mutability == actual.mutability
{
Ok(())
} else {
bail!("global types incompatible")
}
}
pub fn table(&self, expected: &Table, actual: &crate::Table) -> bool {
pub fn table(&self, expected: &Table, actual: &crate::Table) -> Result<()> {
self.table_ty(expected, actual.wasmtime_ty())
}
fn table_ty(&self, expected: &Table, actual: &Table) -> bool {
expected.wasm_ty == actual.wasm_ty
fn table_ty(&self, expected: &Table, actual: &Table) -> Result<()> {
if expected.wasm_ty == actual.wasm_ty
&& expected.ty == actual.ty
&& expected.minimum <= actual.minimum
&& match expected.maximum {
@@ -36,14 +41,19 @@ impl MatchCx<'_> {
},
None => true,
}
{
Ok(())
} else {
bail!("table types incompatible")
}
}
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> bool {
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> Result<()> {
self.memory_ty(expected, actual.wasmtime_ty())
}
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> bool {
expected.shared == actual.shared
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> Result<()> {
if expected.shared == actual.shared
&& expected.minimum <= actual.minimum
&& match expected.maximum {
Some(expected) => match actual.maximum {
@@ -52,10 +62,15 @@ impl MatchCx<'_> {
},
None => true,
}
{
Ok(())
} else {
bail!("memory types incompatible")
}
}
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> bool {
match self
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> Result<()> {
let matches = match self
.store
.signatures()
.borrow()
@@ -65,31 +80,50 @@ impl MatchCx<'_> {
// If our expected signature isn't registered, then there's no way
// that `actual` can match it.
None => false,
};
if matches {
Ok(())
} else {
bail!("function types incompatible")
}
}
pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> bool {
let module = actual.handle.module();
self.exports_match(
expected,
actual
.handle
.host_state()
.downcast_ref::<Arc<TypeTables>>()
.unwrap(),
|name| module.exports.get(name).map(|idx| module.type_of(*idx)),
)
pub fn instance(&self, expected: InstanceTypeIndex, actual: &crate::Instance) -> Result<()> {
for (name, expected) in self.types.instance_signatures[expected].exports.iter() {
match actual.items.get(name) {
Some(item) => {
let item = unsafe { Extern::from_wasmtime_export(item, self.store) };
self.extern_(expected, &item)
.with_context(|| format!("instance export {:?} incompatible", name))?;
}
None => bail!("instance type missing export {:?}", name),
}
}
Ok(())
}
/// Validates that the type signature of `actual` matches the `expected`
/// module type signature.
pub fn module(&self, expected: ModuleTypeIndex, actual: &crate::Module) -> bool {
pub fn module(&self, expected: ModuleTypeIndex, actual: &crate::Module) -> Result<()> {
// This should only ever be invoked with module linking, and this is an
// early check that our `field` assertion below should always work as
// well.
assert!(self.store.engine().config().features.module_linking);
let expected_sig = &self.types.module_signatures[expected];
let module = actual.compiled_module().module();
self.imports_match(expected, actual.types(), module.imports())
&& self.exports_match(expected_sig.exports, actual.types(), |name| {
module.exports.get(name).map(|idx| module.type_of(*idx))
})
self.imports_match(
expected,
actual.types(),
module.imports().map(|(name, field, ty)| {
assert!(field.is_none()); // should be true if module linking is enabled
(name, ty)
}),
)?;
self.exports_match(expected_sig.exports, actual.types(), |name| {
module.exports.get(name).map(|idx| module.type_of(*idx))
})?;
Ok(())
}
/// Validates that the `actual_imports` list of module imports matches the
@@ -100,19 +134,25 @@ impl MatchCx<'_> {
&self,
expected: ModuleTypeIndex,
actual_types: &TypeTables,
mut actual_imports: impl Iterator<Item = (&'a str, Option<&'a str>, EntityType)>,
) -> bool {
actual_imports: impl Iterator<Item = (&'a str, EntityType)>,
) -> Result<()> {
// Imports match if all of the actual imports are satisfied by the
// expected set of imports. Note that we're reversing the order of the
// subtytpe matching here too.
let expected_sig = &self.types.module_signatures[expected];
for (_, _, expected) in expected_sig.imports.iter() {
let (_, _, ty) = match actual_imports.next() {
Some(e) => e,
None => return false,
for (name, actual_ty) in actual_imports {
let expected_ty = match expected_sig.imports.get(name) {
Some(ty) => ty,
None => bail!("expected type doesn't import {:?}", name),
};
if !self.extern_ty_matches(expected, &ty, actual_types) {
return false;
MatchCx {
types: actual_types,
store: self.store,
}
.extern_ty_matches(&actual_ty, expected_ty, self.types)
.with_context(|| format!("module import {:?} incompatible", name))?;
}
actual_imports.next().is_none()
Ok(())
}
/// Validates that all exports in `expected` are defined by `lookup` within
@@ -122,16 +162,19 @@ impl MatchCx<'_> {
expected: InstanceTypeIndex,
actual_types: &TypeTables,
lookup: impl Fn(&str) -> Option<EntityType>,
) -> bool {
) -> Result<()> {
// The `expected` type must be a subset of `actual`, meaning that all
// names in `expected` must be present in `actual`. Note that we do
// name-based lookup here instead of index-based lookup.
self.types.instance_signatures[expected].exports.iter().all(
|(name, expected)| match lookup(name) {
Some(ty) => self.extern_ty_matches(expected, &ty, actual_types),
None => false,
},
)
for (name, expected) in self.types.instance_signatures[expected].exports.iter() {
match lookup(name) {
Some(ty) => self
.extern_ty_matches(expected, &ty, actual_types)
.with_context(|| format!("export {:?} incompatible", name))?,
None => bail!("failed to find export {:?}", name),
}
}
Ok(())
}
/// Validates that the `expected` entity matches the `actual_ty` defined
@@ -141,34 +184,49 @@ impl MatchCx<'_> {
expected: &EntityType,
actual_ty: &EntityType,
actual_types: &TypeTables,
) -> bool {
) -> Result<()> {
let actual_desc = match actual_ty {
EntityType::Global(_) => "global",
EntityType::Module(_) => "module",
EntityType::Memory(_) => "memory",
EntityType::Event(_) => "event",
EntityType::Instance(_) => "instance",
EntityType::Table(_) => "table",
EntityType::Function(_) => "function",
};
match expected {
EntityType::Global(expected) => match actual_ty {
EntityType::Global(actual) => self.global_ty(expected, actual),
_ => false,
_ => bail!("expected global, but found {}", actual_desc),
},
EntityType::Table(expected) => match actual_ty {
EntityType::Table(actual) => self.table_ty(expected, actual),
_ => false,
_ => bail!("expected table, but found {}", actual_desc),
},
EntityType::Memory(expected) => match actual_ty {
EntityType::Memory(actual) => self.memory_ty(expected, actual),
_ => false,
_ => bail!("expected memory, but found {}", actual_desc),
},
EntityType::Function(expected) => match *actual_ty {
EntityType::Function(actual) => {
self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual]
if self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual]
{
Ok(())
} else {
bail!("function types incompatible")
}
}
_ => false,
_ => bail!("expected function, but found {}", actual_desc),
},
EntityType::Instance(expected) => match actual_ty {
EntityType::Instance(actual) => {
let sig = &actual_types.instance_signatures[*actual];
self.exports_match(*expected, actual_types, |name| {
sig.exports.get(name).cloned()
})
})?;
Ok(())
}
_ => false,
_ => bail!("expected instance, but found {}", actual_desc),
},
EntityType::Module(expected) => match actual_ty {
EntityType::Module(actual) => {
@@ -180,14 +238,48 @@ impl MatchCx<'_> {
self.imports_match(
*expected,
actual_types,
actual_module_sig.imports.iter().map(|(module, field, ty)| {
(module.as_str(), field.as_deref(), ty.clone())
}),
) && self.exports_match(expected_module_sig.exports, actual_types, |name| {
actual_module_sig
.imports
.iter()
.map(|(module, ty)| (module.as_str(), ty.clone())),
)?;
self.exports_match(expected_module_sig.exports, actual_types, |name| {
actual_instance_sig.exports.get(name).cloned()
})
})?;
Ok(())
}
_ => false,
_ => bail!("expected module, but found {}", actual_desc),
},
EntityType::Event(_) => unimplemented!(),
}
}
/// Validates that the `expected` type matches the type of `actual`
pub fn extern_(&self, expected: &EntityType, actual: &Extern) -> Result<()> {
match expected {
EntityType::Global(expected) => match actual {
Extern::Global(actual) => self.global(expected, actual),
_ => bail!("expected global, but found {}", actual.desc()),
},
EntityType::Table(expected) => match actual {
Extern::Table(actual) => self.table(expected, actual),
_ => bail!("expected table, but found {}", actual.desc()),
},
EntityType::Memory(expected) => match actual {
Extern::Memory(actual) => self.memory(expected, actual),
_ => bail!("expected memory, but found {}", actual.desc()),
},
EntityType::Function(expected) => match actual {
Extern::Func(actual) => self.func(*expected, actual),
_ => bail!("expected func, but found {}", actual.desc()),
},
EntityType::Instance(expected) => match actual {
Extern::Instance(actual) => self.instance(*expected, actual),
_ => bail!("expected instance, but found {}", actual.desc()),
},
EntityType::Module(expected) => match actual {
Extern::Module(actual) => self.module(*expected, actual),
_ => bail!("expected module, but found {}", actual.desc()),
},
EntityType::Event(_) => unimplemented!(),
}

View File

@@ -13,7 +13,7 @@ edition = "2018"
[dependencies]
anyhow = "1.0.19"
wasmtime = { path = "../wasmtime", version = "0.22.0", default-features = false }
wast = "29.0.0"
wast = "32.0.0"
[badges]
maintenance = { status = "actively-developed" }