Merge remote-tracking branch 'origin/main' into pch/wasi_common_cap_std
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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, ¶ms, &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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
Submodule crates/c-api/wasm-c-api updated: d9a80099d4...c9d3128465
2
crates/cache/Cargo.toml
vendored
2
crates/cache/Cargo.toml
vendored
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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) -> ();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, &[])
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>>,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
22
crates/wasi-crypto/Cargo.toml
Normal file
22
crates/wasi-crypto/Cargo.toml
Normal 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
220
crates/wasi-crypto/LICENSE
Normal 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.
|
||||
|
||||
56
crates/wasi-crypto/README.md
Normal file
56
crates/wasi-crypto/README.md
Normal 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
|
||||
1
crates/wasi-crypto/spec
Submodule
1
crates/wasi-crypto/spec
Submodule
Submodule crates/wasi-crypto/spec added at 6d7821dec3
31
crates/wasi-crypto/src/lib.rs
Normal file
31
crates/wasi-crypto/src/lib.rs
Normal 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."
|
||||
}
|
||||
}
|
||||
});
|
||||
292
crates/wasi-crypto/src/wiggle_interfaces/asymmetric_common.rs
Normal file
292
crates/wasi-crypto/src/wiggle_interfaces/asymmetric_common.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
150
crates/wasi-crypto/src/wiggle_interfaces/common.rs
Normal file
150
crates/wasi-crypto/src/wiggle_interfaces/common.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
67
crates/wasi-crypto/src/wiggle_interfaces/error.rs
Normal file
67
crates/wasi-crypto/src/wiggle_interfaces/error.rs
Normal 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
|
||||
}
|
||||
}
|
||||
40
crates/wasi-crypto/src/wiggle_interfaces/key_exchange.rs
Normal file
40
crates/wasi-crypto/src/wiggle_interfaces/key_exchange.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
38
crates/wasi-crypto/src/wiggle_interfaces/mod.rs
Normal file
38
crates/wasi-crypto/src/wiggle_interfaces/mod.rs
Normal 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;
|
||||
129
crates/wasi-crypto/src/wiggle_interfaces/signatures.rs
Normal file
129
crates/wasi-crypto/src/wiggle_interfaces/signatures.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
384
crates/wasi-crypto/src/wiggle_interfaces/symmetric.rs
Normal file
384
crates/wasi-crypto/src/wiggle_interfaces/symmetric.rs
Normal 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())?)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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.
|
||||
|
||||
578
crates/wasmtime/src/memory.rs
Normal file
578
crates/wasmtime/src/memory.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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!(),
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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!(),
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user