Merge remote-tracking branch 'origin/main' into pch/wasi_common_cap_std
This commit is contained in:
@@ -793,8 +793,15 @@
|
||||
* \typedef wasm_externkind_t
|
||||
* \brief Classifier for #wasm_externtype_t, defined by #wasm_externkind_enum
|
||||
*
|
||||
* This is returned from #wasm_extern_kind and #wasm_externtype_kind to
|
||||
* determine what kind of type is wrapped.
|
||||
*
|
||||
* \enum wasm_externkind_enum
|
||||
* \brief Kinds of external items for a wasm module.
|
||||
*
|
||||
* Note that this also includes #WASM_EXTERN_INSTANCE as well as
|
||||
* #WASM_EXTERN_MODULE and is intended to be used when #wasm_externkind_t is
|
||||
* used.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -208,6 +208,14 @@ WASMTIME_CONFIG_PROP(void, wasm_bulk_memory, bool)
|
||||
*/
|
||||
WASMTIME_CONFIG_PROP(void, wasm_multi_value, bool)
|
||||
|
||||
/**
|
||||
* \brief Configures whether the WebAssembly module linking proposal is
|
||||
* enabled.
|
||||
*
|
||||
* This setting is `false` by default.
|
||||
*/
|
||||
WASMTIME_CONFIG_PROP(void, wasm_module_linking, bool)
|
||||
|
||||
/**
|
||||
* \brief Configures how JIT code will be compiled.
|
||||
*
|
||||
@@ -961,6 +969,291 @@ WASM_API_EXTERN own wasmtime_error_t *wasmtime_module_deserialize(
|
||||
own wasm_module_t **ret
|
||||
);
|
||||
|
||||
/**
|
||||
* \struct wasm_instancetype_t
|
||||
* \brief An opaque object representing the type of a function.
|
||||
*
|
||||
* \typedef wasm_instancetype_t
|
||||
* \brief Convenience alias for #wasm_instancetype_t
|
||||
*
|
||||
* \struct wasm_instancetype_vec_t
|
||||
* \brief A list of #wasm_instancetype_t values.
|
||||
*
|
||||
* \var wasm_instancetype_vec_t::size
|
||||
* \brief Length of this vector.
|
||||
*
|
||||
* \var wasm_instancetype_vec_t::data
|
||||
* \brief Pointer to the base of this vector
|
||||
*
|
||||
* \typedef wasm_instancetype_vec_t
|
||||
* \brief Convenience alias for #wasm_instancetype_vec_t
|
||||
*
|
||||
* \fn void wasm_instancetype_delete(own wasm_instancetype_t *);
|
||||
* \brief Deletes a type.
|
||||
*
|
||||
* \fn void wasm_instancetype_vec_new_empty(own wasm_instancetype_vec_t *out);
|
||||
* \brief Creates an empty vector.
|
||||
*
|
||||
* See #wasm_byte_vec_new_empty for more information.
|
||||
*
|
||||
* \fn void wasm_instancetype_vec_new_uninitialized(own wasm_instancetype_vec_t *out, size_t);
|
||||
* \brief Creates a vector with the given capacity.
|
||||
*
|
||||
* See #wasm_byte_vec_new_uninitialized for more information.
|
||||
*
|
||||
* \fn void wasm_instancetype_vec_new(own wasm_instancetype_vec_t *out, size_t, own wasm_instancetype_t *const[]);
|
||||
* \brief Creates a vector with the provided contents.
|
||||
*
|
||||
* See #wasm_byte_vec_new for more information.
|
||||
*
|
||||
* \fn void wasm_instancetype_vec_copy(own wasm_instancetype_vec_t *out, const wasm_instancetype_vec_t *)
|
||||
* \brief Copies one vector to another
|
||||
*
|
||||
* See #wasm_byte_vec_copy for more information.
|
||||
*
|
||||
* \fn void wasm_instancetype_vec_delete(own wasm_instancetype_vec_t *out)
|
||||
* \brief Deallocates memory for a vector.
|
||||
*
|
||||
* See #wasm_byte_vec_delete for more information.
|
||||
*
|
||||
* \fn own wasm_instancetype_t* wasm_instancetype_copy(wasm_instancetype_t *)
|
||||
* \brief Creates a new value which matches the provided one.
|
||||
*
|
||||
* The caller is responsible for deleting the returned value.
|
||||
*/
|
||||
WASM_DECLARE_TYPE(instancetype)
|
||||
|
||||
/**
|
||||
* \brief Returns the list of exports that this instance type provides.
|
||||
*
|
||||
* This function does not take ownership of the provided instance type but
|
||||
* ownership of `out` is passed to the caller. Note that `out` is treated as
|
||||
* uninitialized when passed to this function.
|
||||
*/
|
||||
WASM_API_EXTERN void wasm_instancetype_exports(const wasm_instancetype_t*, own wasm_exporttype_vec_t* out);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_instancetype_t to a #wasm_externtype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_instancetype_t argument and should not
|
||||
* be deleted.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_externtype_t* wasm_instancetype_as_externtype(wasm_instancetype_t*);
|
||||
|
||||
/**
|
||||
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_instancetype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_instancetype_t argument and should not
|
||||
* be deleted. Returns `NULL` if the provided argument is not a
|
||||
* #wasm_instancetype_t.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_instancetype_t* wasm_externtype_as_instancetype(wasm_externtype_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_instancetype_t to a #wasm_externtype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_instancetype_t argument and should not
|
||||
* be deleted.
|
||||
*/
|
||||
WASM_API_EXTERN const wasm_externtype_t* wasm_instancetype_as_externtype_const(const wasm_instancetype_t*);
|
||||
|
||||
/**
|
||||
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_instancetype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_instancetype_t argument and should not
|
||||
* be deleted. Returns `NULL` if the provided argument is not a
|
||||
* #wasm_instancetype_t.
|
||||
*/
|
||||
WASM_API_EXTERN const wasm_instancetype_t* wasm_externtype_as_instancetype_const(const wasm_externtype_t*);
|
||||
|
||||
/**
|
||||
* \struct wasm_moduletype_t
|
||||
* \brief An opaque object representing the type of a function.
|
||||
*
|
||||
* \typedef wasm_moduletype_t
|
||||
* \brief Convenience alias for #wasm_moduletype_t
|
||||
*
|
||||
* \struct wasm_moduletype_vec_t
|
||||
* \brief A list of #wasm_moduletype_t values.
|
||||
*
|
||||
* \var wasm_moduletype_vec_t::size
|
||||
* \brief Length of this vector.
|
||||
*
|
||||
* \var wasm_moduletype_vec_t::data
|
||||
* \brief Pointer to the base of this vector
|
||||
*
|
||||
* \typedef wasm_moduletype_vec_t
|
||||
* \brief Convenience alias for #wasm_moduletype_vec_t
|
||||
*
|
||||
* \fn void wasm_moduletype_delete(own wasm_moduletype_t *);
|
||||
* \brief Deletes a type.
|
||||
*
|
||||
* \fn void wasm_moduletype_vec_new_empty(own wasm_moduletype_vec_t *out);
|
||||
* \brief Creates an empty vector.
|
||||
*
|
||||
* See #wasm_byte_vec_new_empty for more information.
|
||||
*
|
||||
* \fn void wasm_moduletype_vec_new_uninitialized(own wasm_moduletype_vec_t *out, size_t);
|
||||
* \brief Creates a vector with the given capacity.
|
||||
*
|
||||
* See #wasm_byte_vec_new_uninitialized for more information.
|
||||
*
|
||||
* \fn void wasm_moduletype_vec_new(own wasm_moduletype_vec_t *out, size_t, own wasm_moduletype_t *const[]);
|
||||
* \brief Creates a vector with the provided contents.
|
||||
*
|
||||
* See #wasm_byte_vec_new for more information.
|
||||
*
|
||||
* \fn void wasm_moduletype_vec_copy(own wasm_moduletype_vec_t *out, const wasm_moduletype_vec_t *)
|
||||
* \brief Copies one vector to another
|
||||
*
|
||||
* See #wasm_byte_vec_copy for more information.
|
||||
*
|
||||
* \fn void wasm_moduletype_vec_delete(own wasm_moduletype_vec_t *out)
|
||||
* \brief Deallocates memory for a vector.
|
||||
*
|
||||
* See #wasm_byte_vec_delete for more information.
|
||||
*
|
||||
* \fn own wasm_moduletype_t* wasm_moduletype_copy(wasm_moduletype_t *)
|
||||
* \brief Creates a new value which matches the provided one.
|
||||
*
|
||||
* The caller is responsible for deleting the returned value.
|
||||
*/
|
||||
WASM_DECLARE_TYPE(moduletype)
|
||||
|
||||
/**
|
||||
* \brief Returns the list of imports that this module type requires.
|
||||
*
|
||||
* This function does not take ownership of the provided module type but
|
||||
* ownership of `out` is passed to the caller. Note that `out` is treated as
|
||||
* uninitialized when passed to this function.
|
||||
*/
|
||||
WASM_API_EXTERN void wasm_moduletype_imports(const wasm_moduletype_t*, own wasm_importtype_vec_t* out);
|
||||
|
||||
/**
|
||||
* \brief Returns the list of exports that this module type provides.
|
||||
*
|
||||
* This function does not take ownership of the provided module type but
|
||||
* ownership of `out` is passed to the caller. Note that `out` is treated as
|
||||
* uninitialized when passed to this function.
|
||||
*/
|
||||
WASM_API_EXTERN void wasm_moduletype_exports(const wasm_moduletype_t*, own wasm_exporttype_vec_t* out);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_moduletype_t to a #wasm_externtype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_moduletype_t argument and should not
|
||||
* be deleted.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_externtype_t* wasm_moduletype_as_externtype(wasm_moduletype_t*);
|
||||
|
||||
/**
|
||||
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_moduletype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_moduletype_t argument and should not
|
||||
* be deleted. Returns `NULL` if the provided argument is not a
|
||||
* #wasm_moduletype_t.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_moduletype_t* wasm_externtype_as_moduletype(wasm_externtype_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_moduletype_t to a #wasm_externtype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_moduletype_t argument and should not
|
||||
* be deleted.
|
||||
*/
|
||||
WASM_API_EXTERN const wasm_externtype_t* wasm_moduletype_as_externtype_const(const wasm_moduletype_t*);
|
||||
|
||||
/**
|
||||
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_moduletype_t
|
||||
*
|
||||
* The returned value is owned by the #wasm_moduletype_t argument and should not
|
||||
* be deleted. Returns `NULL` if the provided argument is not a
|
||||
* #wasm_moduletype_t.
|
||||
*/
|
||||
WASM_API_EXTERN const wasm_moduletype_t* wasm_externtype_as_moduletype_const(const wasm_externtype_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_module_t to #wasm_extern_t.
|
||||
*
|
||||
* The returned #wasm_extern_t is owned by the #wasm_module_t argument. Callers
|
||||
* should not delete the returned value, and it only lives as long as the
|
||||
* #wasm_module_t argument.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_extern_t* wasm_module_as_extern(wasm_module_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_extern_t to #wasm_module_t.
|
||||
*
|
||||
* The returned #wasm_module_t is owned by the #wasm_extern_t argument. Callers
|
||||
* should not delete the returned value, and it only lives as long as the
|
||||
* #wasm_extern_t argument.
|
||||
*
|
||||
* If the #wasm_extern_t argument isn't a #wasm_module_t then `NULL` is returned.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_module_t* wasm_extern_as_module(wasm_extern_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_extern_t to #wasm_instance_t.
|
||||
*
|
||||
* The returned #wasm_instance_t is owned by the #wasm_extern_t argument. Callers
|
||||
* should not delete the returned value, and it only lives as long as the
|
||||
* #wasm_extern_t argument.
|
||||
*/
|
||||
WASM_API_EXTERN const wasm_module_t* wasm_extern_as_module_const(const wasm_extern_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_instance_t to #wasm_extern_t.
|
||||
*
|
||||
* The returned #wasm_extern_t is owned by the #wasm_instance_t argument. Callers
|
||||
* should not delete the returned value, and it only lives as long as the
|
||||
* #wasm_instance_t argument.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_extern_t* wasm_instance_as_extern(wasm_instance_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_extern_t to #wasm_instance_t.
|
||||
*
|
||||
* The returned #wasm_instance_t is owned by the #wasm_extern_t argument. Callers
|
||||
* should not delete the returned value, and it only lives as long as the
|
||||
* #wasm_extern_t argument.
|
||||
*
|
||||
* If the #wasm_extern_t argument isn't a #wasm_instance_t then `NULL` is returned.
|
||||
*/
|
||||
WASM_API_EXTERN wasm_instance_t* wasm_extern_as_instance(wasm_extern_t*);
|
||||
|
||||
/**
|
||||
* \brief Converts a #wasm_extern_t to #wasm_instance_t.
|
||||
*
|
||||
* The returned #wasm_instance_t is owned by the #wasm_extern_t argument. Callers
|
||||
* should not delete the returned value, and it only lives as long as the
|
||||
* #wasm_extern_t argument.
|
||||
*/
|
||||
WASM_API_EXTERN const wasm_instance_t* wasm_extern_as_instance_const(const wasm_extern_t*);
|
||||
|
||||
/**
|
||||
* \brief Returns the type of this instance.
|
||||
*
|
||||
* The returned #wasm_instancetype_t is expected to be deallocated by the caller.
|
||||
*/
|
||||
WASM_API_EXTERN own wasm_instancetype_t* wasm_instance_type(const wasm_instance_t*);
|
||||
|
||||
/**
|
||||
* \brief Returns the type of this module.
|
||||
*
|
||||
* The returned #wasm_moduletype_t is expected to be deallocated by the caller.
|
||||
*/
|
||||
WASM_API_EXTERN own wasm_moduletype_t* wasm_module_type(const wasm_module_t*);
|
||||
|
||||
/**
|
||||
* \brief Value of #wasm_externkind_enum corresponding to a wasm module.
|
||||
*/
|
||||
#define WASM_EXTERN_MODULE 4
|
||||
|
||||
/**
|
||||
* \brief Value of #wasm_externkind_enum corresponding to a wasm instance.
|
||||
*/
|
||||
#define WASM_EXTERN_INSTANCE 5
|
||||
|
||||
#undef own
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -85,6 +85,11 @@ pub extern "C" fn wasmtime_config_wasm_multi_value_set(c: &mut wasm_config_t, en
|
||||
c.config.wasm_multi_value(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasmtime_config_wasm_module_linking_set(c: &mut wasm_config_t, enable: bool) {
|
||||
c.config.wasm_module_linking(enable);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasmtime_config_strategy_set(
|
||||
c: &mut wasm_config_t,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::wasm_externkind_t;
|
||||
use crate::{wasm_externtype_t, wasm_func_t, wasm_global_t, wasm_memory_t, wasm_table_t};
|
||||
use crate::{
|
||||
wasm_externkind_t, wasm_externtype_t, wasm_func_t, wasm_global_t, wasm_instance_t,
|
||||
wasm_memory_t, wasm_module_t, wasm_table_t,
|
||||
};
|
||||
use wasmtime::Extern;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -16,6 +18,8 @@ pub extern "C" fn wasm_extern_kind(e: &wasm_extern_t) -> wasm_externkind_t {
|
||||
Extern::Global(_) => crate::WASM_EXTERN_GLOBAL,
|
||||
Extern::Table(_) => crate::WASM_EXTERN_TABLE,
|
||||
Extern::Memory(_) => crate::WASM_EXTERN_MEMORY,
|
||||
Extern::Instance(_) => crate::WASM_EXTERN_INSTANCE,
|
||||
Extern::Module(_) => crate::WASM_EXTERN_MODULE,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,3 +67,23 @@ pub extern "C" fn wasm_extern_as_memory(e: &wasm_extern_t) -> Option<&wasm_memor
|
||||
pub extern "C" fn wasm_extern_as_memory_const(e: &wasm_extern_t) -> Option<&wasm_memory_t> {
|
||||
wasm_extern_as_memory(e)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_extern_as_module(e: &wasm_extern_t) -> Option<&wasm_module_t> {
|
||||
wasm_module_t::try_from(e)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_extern_as_module_const(e: &wasm_extern_t) -> Option<&wasm_module_t> {
|
||||
wasm_extern_as_module(e)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_extern_as_instance(e: &wasm_extern_t) -> Option<&wasm_instance_t> {
|
||||
wasm_instance_t::try_from(e)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_extern_as_instance_const(e: &wasm_extern_t) -> Option<&wasm_instance_t> {
|
||||
wasm_extern_as_instance(e)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
use crate::{wasm_extern_t, wasm_extern_vec_t, wasm_module_t, wasm_trap_t};
|
||||
use crate::{wasm_store_t, wasmtime_error_t};
|
||||
use crate::{wasm_instancetype_t, wasm_store_t, wasmtime_error_t};
|
||||
use anyhow::Result;
|
||||
use std::ptr;
|
||||
use wasmtime::{Instance, Trap};
|
||||
use wasmtime::{Extern, Instance, Trap};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct wasm_instance_t {
|
||||
pub(crate) instance: Instance,
|
||||
ext: wasm_extern_t,
|
||||
}
|
||||
|
||||
wasmtime_c_api_macros::declare_ref!(wasm_instance_t);
|
||||
|
||||
impl wasm_instance_t {
|
||||
pub(crate) fn new(instance: Instance) -> wasm_instance_t {
|
||||
wasm_instance_t { instance: instance }
|
||||
wasm_instance_t {
|
||||
ext: wasm_extern_t {
|
||||
which: instance.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_from(e: &wasm_extern_t) -> Option<&wasm_instance_t> {
|
||||
match &e.which {
|
||||
Extern::Instance(_) => Some(unsafe { &*(e as *const _ as *const _) }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn instance(&self) -> &Instance {
|
||||
match &self.ext.which {
|
||||
Extern::Instance(i) => i,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +49,7 @@ pub unsafe extern "C" fn wasm_instance_new(
|
||||
store,
|
||||
wasm_module,
|
||||
imports,
|
||||
wasm_module.imports.len(),
|
||||
wasm_module.module().imports().len(),
|
||||
&mut instance,
|
||||
&mut trap,
|
||||
);
|
||||
@@ -92,7 +110,7 @@ fn _wasmtime_instance_new(
|
||||
.map(|import| import.which.clone())
|
||||
.collect::<Vec<_>>();
|
||||
handle_instantiate(
|
||||
Instance::new(store, &module.module, &imports),
|
||||
Instance::new(store, module.module(), &imports),
|
||||
instance_ptr,
|
||||
trap_ptr,
|
||||
)
|
||||
@@ -122,11 +140,16 @@ pub fn handle_instantiate(
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_instance_as_extern(m: &wasm_instance_t) -> &wasm_extern_t {
|
||||
&m.ext
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_instance_exports(instance: &wasm_instance_t, out: &mut wasm_extern_vec_t) {
|
||||
out.set_buffer(
|
||||
instance
|
||||
.instance
|
||||
.instance()
|
||||
.exports()
|
||||
.map(|e| {
|
||||
Some(Box::new(wasm_extern_t {
|
||||
@@ -136,3 +159,8 @@ pub extern "C" fn wasm_instance_exports(instance: &wasm_instance_t, out: &mut wa
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_instance_type(f: &wasm_instance_t) -> Box<wasm_instancetype_t> {
|
||||
Box::new(wasm_instancetype_t::new(f.instance().ty()))
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ pub extern "C" fn wasmtime_linker_define_instance(
|
||||
Ok(s) => s,
|
||||
Err(_) => return bad_utf8(),
|
||||
};
|
||||
handle_result(linker.instance(name, &instance.instance), |_linker| ())
|
||||
handle_result(linker.instance(name, instance.instance()), |_linker| ())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -78,7 +78,7 @@ pub extern "C" fn wasmtime_linker_instantiate(
|
||||
instance_ptr: &mut *mut wasm_instance_t,
|
||||
trap_ptr: &mut *mut wasm_trap_t,
|
||||
) -> Option<Box<wasmtime_error_t>> {
|
||||
let result = linker.linker.instantiate(&module.module);
|
||||
let result = linker.linker.instantiate(module.module());
|
||||
super::instance::handle_instantiate(result, instance_ptr, trap_ptr)
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ pub extern "C" fn wasmtime_linker_module(
|
||||
Ok(s) => s,
|
||||
Err(_) => return bad_utf8(),
|
||||
};
|
||||
handle_result(linker.module(name, &module.module), |_linker| ())
|
||||
handle_result(linker.module(name, module.module()), |_linker| ())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
use crate::{
|
||||
handle_result, wasm_byte_vec_t, wasm_engine_t, wasm_exporttype_t, wasm_exporttype_vec_t,
|
||||
wasm_importtype_t, wasm_importtype_vec_t, wasm_store_t, wasmtime_error_t,
|
||||
wasm_extern_t, wasm_importtype_t, wasm_importtype_vec_t, wasm_moduletype_t, wasm_store_t,
|
||||
wasmtime_error_t,
|
||||
};
|
||||
use std::ptr;
|
||||
use wasmtime::{Engine, Module};
|
||||
use wasmtime::{Engine, Extern, Module};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct wasm_module_t {
|
||||
pub(crate) module: Module,
|
||||
pub(crate) imports: Vec<wasm_importtype_t>,
|
||||
pub(crate) exports: Vec<wasm_exporttype_t>,
|
||||
ext: wasm_extern_t,
|
||||
}
|
||||
|
||||
wasmtime_c_api_macros::declare_ref!(wasm_module_t);
|
||||
|
||||
impl wasm_module_t {
|
||||
pub(crate) fn new(module: Module) -> wasm_module_t {
|
||||
wasm_module_t {
|
||||
ext: wasm_extern_t {
|
||||
which: module.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_from(e: &wasm_extern_t) -> Option<&wasm_module_t> {
|
||||
match &e.which {
|
||||
Extern::Module(_) => Some(unsafe { &*(e as *const _ as *const _) }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn module(&self) -> &Module {
|
||||
match &self.ext.which {
|
||||
Extern::Module(i) => i,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct wasm_shared_module_t {
|
||||
@@ -49,25 +72,7 @@ pub extern "C" fn wasmtime_module_new(
|
||||
) -> Option<Box<wasmtime_error_t>> {
|
||||
let binary = binary.as_slice();
|
||||
handle_result(Module::from_binary(&engine.engine, binary), |module| {
|
||||
let imports = module
|
||||
.imports()
|
||||
.map(|i| {
|
||||
wasm_importtype_t::new(
|
||||
i.module().to_owned(),
|
||||
i.name().map(|s| s.to_owned()),
|
||||
i.ty(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let exports = module
|
||||
.exports()
|
||||
.map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty()))
|
||||
.collect::<Vec<_>>();
|
||||
let module = Box::new(wasm_module_t {
|
||||
module: module,
|
||||
imports,
|
||||
exports,
|
||||
});
|
||||
let module = Box::new(wasm_module_t::new(module));
|
||||
*ret = Box::into_raw(module);
|
||||
})
|
||||
}
|
||||
@@ -86,30 +91,46 @@ pub extern "C" fn wasmtime_module_validate(
|
||||
handle_result(Module::validate(store.store.engine(), binary), |()| {})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_module_as_extern(m: &wasm_module_t) -> &wasm_extern_t {
|
||||
&m.ext
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_module_exports(module: &wasm_module_t, out: &mut wasm_exporttype_vec_t) {
|
||||
let buffer = module
|
||||
.exports
|
||||
.iter()
|
||||
.map(|et| Some(Box::new(et.clone())))
|
||||
let exports = module
|
||||
.module()
|
||||
.exports()
|
||||
.map(|e| {
|
||||
Some(Box::new(wasm_exporttype_t::new(
|
||||
e.name().to_owned(),
|
||||
e.ty(),
|
||||
)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
out.set_buffer(buffer);
|
||||
out.set_buffer(exports);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_module_imports(module: &wasm_module_t, out: &mut wasm_importtype_vec_t) {
|
||||
let buffer = module
|
||||
.imports
|
||||
.iter()
|
||||
.map(|it| Some(Box::new(it.clone())))
|
||||
let imports = module
|
||||
.module()
|
||||
.imports()
|
||||
.map(|i| {
|
||||
Some(Box::new(wasm_importtype_t::new(
|
||||
i.module().to_owned(),
|
||||
i.name().map(|s| s.to_owned()),
|
||||
i.ty(),
|
||||
)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
out.set_buffer(buffer);
|
||||
out.set_buffer(imports);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_module_share(module: &wasm_module_t) -> Box<wasm_shared_module_t> {
|
||||
Box::new(wasm_shared_module_t {
|
||||
module: module.module.clone(),
|
||||
module: module.module().clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -122,25 +143,7 @@ pub extern "C" fn wasm_module_obtain(
|
||||
if !Engine::same(store.store.engine(), module.engine()) {
|
||||
return None;
|
||||
}
|
||||
let imports = module
|
||||
.imports()
|
||||
.map(|i| {
|
||||
wasm_importtype_t::new(
|
||||
i.module().to_owned(),
|
||||
i.name().map(|s| s.to_owned()),
|
||||
i.ty(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let exports = module
|
||||
.exports()
|
||||
.map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty()))
|
||||
.collect::<Vec<_>>();
|
||||
Some(Box::new(wasm_module_t {
|
||||
module: module,
|
||||
imports,
|
||||
exports,
|
||||
}))
|
||||
Some(Box::new(wasm_module_t::new(module)))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -171,7 +174,7 @@ pub extern "C" fn wasmtime_module_serialize(
|
||||
module: &wasm_module_t,
|
||||
ret: &mut wasm_byte_vec_t,
|
||||
) -> Option<Box<wasmtime_error_t>> {
|
||||
handle_result(module.module.serialize(), |buf| {
|
||||
handle_result(module.module().serialize(), |buf| {
|
||||
ret.set_buffer(buf);
|
||||
})
|
||||
}
|
||||
@@ -185,26 +188,13 @@ pub extern "C" fn wasmtime_module_deserialize(
|
||||
handle_result(
|
||||
Module::deserialize(&engine.engine, binary.as_slice()),
|
||||
|module| {
|
||||
let imports = module
|
||||
.imports()
|
||||
.map(|i| {
|
||||
wasm_importtype_t::new(
|
||||
i.module().to_owned(),
|
||||
i.name().map(|s| s.to_owned()),
|
||||
i.ty(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let exports = module
|
||||
.exports()
|
||||
.map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty()))
|
||||
.collect::<Vec<_>>();
|
||||
let module = Box::new(wasm_module_t {
|
||||
module: module,
|
||||
imports,
|
||||
exports,
|
||||
});
|
||||
let module = Box::new(wasm_module_t::new(module));
|
||||
*ret = Box::into_raw(module);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_module_type(f: &wasm_module_t) -> Box<wasm_moduletype_t> {
|
||||
Box::new(wasm_moduletype_t::new(f.module().ty()))
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ pub const WASM_EXTERN_FUNC: wasm_externkind_t = 0;
|
||||
pub const WASM_EXTERN_GLOBAL: wasm_externkind_t = 1;
|
||||
pub const WASM_EXTERN_TABLE: wasm_externkind_t = 2;
|
||||
pub const WASM_EXTERN_MEMORY: wasm_externkind_t = 3;
|
||||
pub const WASMTIME_EXTERN_MODULE: wasm_externkind_t = 4;
|
||||
pub const WASMTIME_EXTERN_INSTANCE: wasm_externkind_t = 5;
|
||||
pub const WASM_EXTERN_MODULE: wasm_externkind_t = 4;
|
||||
pub const WASM_EXTERN_INSTANCE: wasm_externkind_t = 5;
|
||||
|
||||
impl wasm_externtype_t {
|
||||
pub(crate) fn new(ty: ExternType) -> wasm_externtype_t {
|
||||
@@ -63,8 +63,8 @@ pub extern "C" fn wasm_externtype_kind(et: &wasm_externtype_t) -> wasm_externkin
|
||||
CExternType::Table(_) => WASM_EXTERN_TABLE,
|
||||
CExternType::Global(_) => WASM_EXTERN_GLOBAL,
|
||||
CExternType::Memory(_) => WASM_EXTERN_MEMORY,
|
||||
CExternType::Instance(_) => WASMTIME_EXTERN_INSTANCE,
|
||||
CExternType::Module(_) => WASMTIME_EXTERN_MODULE,
|
||||
CExternType::Instance(_) => WASM_EXTERN_INSTANCE,
|
||||
CExternType::Module(_) => WASM_EXTERN_MODULE,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{wasm_externtype_t, wasm_limits_t, CExternType};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use crate::{wasm_exporttype_t, wasm_exporttype_vec_t, wasm_externtype_t, CExternType};
|
||||
use wasmtime::InstanceType;
|
||||
|
||||
#[repr(transparent)]
|
||||
@@ -13,24 +12,33 @@ wasmtime_c_api_macros::declare_ty!(wasm_instancetype_t);
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CInstanceType {
|
||||
pub(crate) ty: InstanceType,
|
||||
limits_cache: OnceCell<wasm_limits_t>,
|
||||
}
|
||||
|
||||
impl wasm_instancetype_t {
|
||||
pub(crate) fn new(ty: InstanceType) -> wasm_instancetype_t {
|
||||
wasm_instancetype_t {
|
||||
ext: wasm_externtype_t::new(ty.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_from(e: &wasm_externtype_t) -> Option<&wasm_instancetype_t> {
|
||||
match &e.which {
|
||||
CExternType::Instance(_) => Some(unsafe { &*(e as *const _ as *const _) }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ty(&self) -> &CInstanceType {
|
||||
match &self.ext.which {
|
||||
CExternType::Instance(f) => &f,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CInstanceType {
|
||||
pub(crate) fn new(ty: InstanceType) -> CInstanceType {
|
||||
CInstanceType {
|
||||
ty,
|
||||
limits_cache: OnceCell::new(),
|
||||
}
|
||||
CInstanceType { ty }
|
||||
}
|
||||
}
|
||||
#[no_mangle]
|
||||
@@ -44,3 +52,22 @@ pub extern "C" fn wasm_instancetype_as_externtype_const(
|
||||
) -> &wasm_externtype_t {
|
||||
&ty.ext
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_instancetype_exports(
|
||||
instance: &wasm_instancetype_t,
|
||||
out: &mut wasm_exporttype_vec_t,
|
||||
) {
|
||||
let exports = instance
|
||||
.ty()
|
||||
.ty
|
||||
.exports()
|
||||
.map(|e| {
|
||||
Some(Box::new(wasm_exporttype_t::new(
|
||||
e.name().to_owned(),
|
||||
e.ty(),
|
||||
)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
out.set_buffer(exports);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::{wasm_externtype_t, wasm_limits_t, CExternType};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use crate::{
|
||||
wasm_exporttype_t, wasm_exporttype_vec_t, wasm_externtype_t, wasm_importtype_t,
|
||||
wasm_importtype_vec_t, CExternType,
|
||||
};
|
||||
use wasmtime::ModuleType;
|
||||
|
||||
#[repr(transparent)]
|
||||
@@ -13,24 +15,33 @@ wasmtime_c_api_macros::declare_ty!(wasm_moduletype_t);
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CModuleType {
|
||||
pub(crate) ty: ModuleType,
|
||||
limits_cache: OnceCell<wasm_limits_t>,
|
||||
}
|
||||
|
||||
impl wasm_moduletype_t {
|
||||
pub(crate) fn new(ty: ModuleType) -> wasm_moduletype_t {
|
||||
wasm_moduletype_t {
|
||||
ext: wasm_externtype_t::new(ty.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_from(e: &wasm_externtype_t) -> Option<&wasm_moduletype_t> {
|
||||
match &e.which {
|
||||
CExternType::Module(_) => Some(unsafe { &*(e as *const _ as *const _) }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ty(&self) -> &CModuleType {
|
||||
match &self.ext.which {
|
||||
CExternType::Module(f) => &f,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CModuleType {
|
||||
pub(crate) fn new(ty: ModuleType) -> CModuleType {
|
||||
CModuleType {
|
||||
ty,
|
||||
limits_cache: OnceCell::new(),
|
||||
}
|
||||
CModuleType { ty }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,3 +56,42 @@ pub extern "C" fn wasm_moduletype_as_externtype_const(
|
||||
) -> &wasm_externtype_t {
|
||||
&ty.ext
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_moduletype_exports(
|
||||
module: &wasm_moduletype_t,
|
||||
out: &mut wasm_exporttype_vec_t,
|
||||
) {
|
||||
let exports = module
|
||||
.ty()
|
||||
.ty
|
||||
.exports()
|
||||
.map(|e| {
|
||||
Some(Box::new(wasm_exporttype_t::new(
|
||||
e.name().to_owned(),
|
||||
e.ty(),
|
||||
)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
out.set_buffer(exports);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wasm_moduletype_imports(
|
||||
module: &wasm_moduletype_t,
|
||||
out: &mut wasm_importtype_vec_t,
|
||||
) {
|
||||
let imports = module
|
||||
.ty()
|
||||
.ty
|
||||
.imports()
|
||||
.map(|i| {
|
||||
Some(Box::new(wasm_importtype_t::new(
|
||||
i.module().to_owned(),
|
||||
i.name().map(|s| s.to_owned()),
|
||||
i.ty(),
|
||||
)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
out.set_buffer(imports);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::wasm_valtype_t;
|
||||
use crate::{wasm_exporttype_t, wasm_extern_t, wasm_frame_t, wasm_val_t};
|
||||
use crate::{wasm_externtype_t, wasm_importtype_t, wasm_memorytype_t};
|
||||
use crate::{wasm_functype_t, wasm_globaltype_t, wasm_tabletype_t};
|
||||
use crate::{
|
||||
wasm_exporttype_t, wasm_extern_t, wasm_externtype_t, wasm_frame_t, wasm_functype_t,
|
||||
wasm_globaltype_t, wasm_importtype_t, wasm_instancetype_t, wasm_memorytype_t,
|
||||
wasm_moduletype_t, wasm_tabletype_t, wasm_val_t, wasm_valtype_t,
|
||||
};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
@@ -172,6 +173,24 @@ declare_vecs! {
|
||||
copy: wasm_memorytype_vec_copy,
|
||||
delete: wasm_memorytype_vec_delete,
|
||||
)
|
||||
(
|
||||
name: wasm_instancetype_vec_t,
|
||||
ty: Option<Box<wasm_instancetype_t>>,
|
||||
new: wasm_instancetype_vec_new,
|
||||
empty: wasm_instancetype_vec_new_empty,
|
||||
uninit: wasm_instancetype_vec_new_uninitialized,
|
||||
copy: wasm_instancetype_vec_copy,
|
||||
delete: wasm_instancetype_vec_delete,
|
||||
)
|
||||
(
|
||||
name: wasm_moduletype_vec_t,
|
||||
ty: Option<Box<wasm_moduletype_t>>,
|
||||
new: wasm_moduletype_vec_new,
|
||||
empty: wasm_moduletype_vec_new_empty,
|
||||
uninit: wasm_moduletype_vec_new_uninitialized,
|
||||
copy: wasm_moduletype_vec_copy,
|
||||
delete: wasm_moduletype_vec_delete,
|
||||
)
|
||||
(
|
||||
name: wasm_externtype_vec_t,
|
||||
ty: Option<Box<wasm_externtype_t>>,
|
||||
|
||||
@@ -1039,7 +1039,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
||||
callee: ir::Value,
|
||||
call_args: &[ir::Value],
|
||||
) -> WasmResult<ir::Inst> {
|
||||
let sig_index = self.module.types[ty_index].unwrap_function();
|
||||
let pointer_type = self.pointer_type();
|
||||
|
||||
let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0);
|
||||
@@ -1071,7 +1070,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
|
||||
let vmctx = self.vmctx(pos.func);
|
||||
let base = pos.ins().global_value(pointer_type, vmctx);
|
||||
let offset =
|
||||
i32::try_from(self.offsets.vmctx_vmshared_signature_id(sig_index)).unwrap();
|
||||
i32::try_from(self.offsets.vmctx_vmshared_signature_id(ty_index)).unwrap();
|
||||
|
||||
// Load the caller ID.
|
||||
let mut mem_flags = ir::MemFlags::trusted();
|
||||
|
||||
@@ -99,7 +99,7 @@ use std::sync::Mutex;
|
||||
use wasmtime_environ::{
|
||||
CompileError, CompiledFunction, Compiler, FunctionAddressMap, FunctionBodyData,
|
||||
InstructionAddressMap, ModuleTranslation, Relocation, RelocationTarget, StackMapInformation,
|
||||
TrapInformation, Tunables,
|
||||
TrapInformation, Tunables, TypeTables,
|
||||
};
|
||||
|
||||
mod func_environ;
|
||||
@@ -348,21 +348,22 @@ impl Compiler for Cranelift {
|
||||
mut input: FunctionBodyData<'_>,
|
||||
isa: &dyn isa::TargetIsa,
|
||||
tunables: &Tunables,
|
||||
types: &TypeTables,
|
||||
) -> Result<CompiledFunction, CompileError> {
|
||||
let module = &translation.module;
|
||||
let func_index = module.func_index(func_index);
|
||||
let mut context = Context::new();
|
||||
context.func.name = get_func_name(func_index);
|
||||
let sig_index = module.functions[func_index];
|
||||
context.func.signature = translation.native_signatures[sig_index].clone();
|
||||
if tunables.debug_info {
|
||||
context.func.signature = types.native_signatures[sig_index].clone();
|
||||
if tunables.generate_native_debuginfo {
|
||||
context.func.collect_debug_info();
|
||||
}
|
||||
|
||||
let mut func_env = FuncEnvironment::new(
|
||||
isa.frontend_config(),
|
||||
module,
|
||||
&translation.native_signatures,
|
||||
&types.native_signatures,
|
||||
tunables,
|
||||
);
|
||||
|
||||
@@ -434,7 +435,7 @@ impl Compiler for Cranelift {
|
||||
let address_transform =
|
||||
get_function_address_map(&context, &input, code_buf.len() as u32, isa);
|
||||
|
||||
let ranges = if tunables.debug_info {
|
||||
let ranges = if tunables.generate_native_debuginfo {
|
||||
let ranges = context.build_value_labels_ranges(isa).map_err(|error| {
|
||||
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
|
||||
})?;
|
||||
|
||||
@@ -13,7 +13,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
gimli = "0.23.0"
|
||||
wasmparser = "0.68.0"
|
||||
wasmparser = "0.70"
|
||||
object = { version = "0.22.0", default-features = false, features = ["read", "write"] }
|
||||
wasmtime-environ = { path = "../environ", version = "0.21.0" }
|
||||
target-lexicon = { version = "0.11.0", default-features = false }
|
||||
|
||||
@@ -16,7 +16,7 @@ anyhow = "1.0"
|
||||
cranelift-codegen = { path = "../../cranelift/codegen", version = "0.68.0", features = ["enable-serde"] }
|
||||
cranelift-entity = { path = "../../cranelift/entity", version = "0.68.0", features = ["enable-serde"] }
|
||||
cranelift-wasm = { path = "../../cranelift/wasm", version = "0.68.0", features = ["enable-serde"] }
|
||||
wasmparser = "0.68.0"
|
||||
wasmparser = "0.70"
|
||||
indexmap = { version = "1.0.2", features = ["serde-1"] }
|
||||
thiserror = "1.0.4"
|
||||
serde = { version = "1.0.94", features = ["derive"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! A `Compilation` contains the compiled function bodies for a WebAssembly
|
||||
//! module.
|
||||
|
||||
use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation, Tunables};
|
||||
use crate::{FunctionAddressMap, FunctionBodyData, ModuleTranslation, Tunables, TypeTables};
|
||||
use cranelift_codegen::{binemit, ir, isa, isa::unwind::UnwindInfo};
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmError};
|
||||
@@ -104,5 +104,6 @@ pub trait Compiler: Send + Sync {
|
||||
data: FunctionBodyData<'_>,
|
||||
isa: &dyn isa::TargetIsa,
|
||||
tunables: &Tunables,
|
||||
types: &TypeTables,
|
||||
) -> Result<CompiledFunction, CompileError>;
|
||||
}
|
||||
|
||||
@@ -2,20 +2,14 @@
|
||||
|
||||
use crate::tunables::Tunables;
|
||||
use crate::WASM_MAX_PAGES;
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_entity::{EntityRef, PrimaryMap};
|
||||
use cranelift_wasm::{
|
||||
DataIndex, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex,
|
||||
ElemIndex, EntityIndex, EntityType, FuncIndex, Global, GlobalIndex, InstanceIndex, Memory,
|
||||
MemoryIndex, ModuleIndex, SignatureIndex, Table, TableIndex, TypeIndex, WasmFuncType,
|
||||
};
|
||||
use cranelift_wasm::*;
|
||||
use indexmap::IndexMap;
|
||||
use more_asserts::assert_ge;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A WebAssembly table initializer.
|
||||
#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
@@ -121,23 +115,16 @@ impl TablePlan {
|
||||
}
|
||||
}
|
||||
|
||||
/// Different types that can appear in a module
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// Different types that can appear in a module.
|
||||
///
|
||||
/// Note that each of these variants are intended to index further into a
|
||||
/// separate table.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum ModuleType {
|
||||
/// A function type, indexed further into the `signatures` table.
|
||||
Function(SignatureIndex),
|
||||
/// A module type
|
||||
Module {
|
||||
/// The module's imports
|
||||
imports: Vec<(String, Option<String>, EntityType)>,
|
||||
/// The module's exports
|
||||
exports: Vec<(String, EntityType)>,
|
||||
},
|
||||
/// An instance type
|
||||
Instance {
|
||||
/// the instance's exports
|
||||
exports: Vec<(String, EntityType)>,
|
||||
},
|
||||
Module(ModuleTypeIndex),
|
||||
Instance(InstanceTypeIndex),
|
||||
}
|
||||
|
||||
impl ModuleType {
|
||||
@@ -153,17 +140,19 @@ impl ModuleType {
|
||||
|
||||
/// A translated WebAssembly module, excluding the function bodies and
|
||||
/// memory initializers.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Module {
|
||||
/// A unique identifier (within this process) for this module.
|
||||
#[serde(skip_serializing, skip_deserializing, default = "Module::next_id")]
|
||||
pub id: usize,
|
||||
/// 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>,
|
||||
|
||||
/// All import records, in the order they are declared in the module.
|
||||
pub imports: Vec<(String, Option<String>, EntityIndex)>,
|
||||
pub initializers: Vec<Initializer>,
|
||||
|
||||
/// Exported entities.
|
||||
pub exports: IndexMap<String, EntityIndex>,
|
||||
@@ -184,22 +173,19 @@ pub struct Module {
|
||||
/// WebAssembly table initializers.
|
||||
pub func_names: HashMap<FuncIndex, String>,
|
||||
|
||||
/// Unprocessed signatures exactly as provided by `declare_signature()`.
|
||||
pub signatures: PrimaryMap<SignatureIndex, WasmFuncType>,
|
||||
|
||||
/// Types declared in the wasm module.
|
||||
pub types: PrimaryMap<TypeIndex, ModuleType>,
|
||||
|
||||
/// Number of imported functions in the module.
|
||||
/// Number of imported or aliased functions in the module.
|
||||
pub num_imported_funcs: usize,
|
||||
|
||||
/// Number of imported tables in the module.
|
||||
/// Number of imported or aliased tables in the module.
|
||||
pub num_imported_tables: usize,
|
||||
|
||||
/// Number of imported memories in the module.
|
||||
/// Number of imported or aliased memories in the module.
|
||||
pub num_imported_memories: usize,
|
||||
|
||||
/// Number of imported globals in the module.
|
||||
/// Number of imported or aliased globals in the module.
|
||||
pub num_imported_globals: usize,
|
||||
|
||||
/// Types of functions, imported and local.
|
||||
@@ -214,54 +200,58 @@ pub struct Module {
|
||||
/// WebAssembly global variables.
|
||||
pub globals: PrimaryMap<GlobalIndex, Global>,
|
||||
|
||||
/// WebAssembly instances.
|
||||
pub instances: PrimaryMap<InstanceIndex, Instance>,
|
||||
/// The type of each wasm instance this module defines.
|
||||
pub instances: PrimaryMap<InstanceIndex, InstanceTypeIndex>,
|
||||
|
||||
/// WebAssembly modules.
|
||||
pub modules: PrimaryMap<ModuleIndex, TypeIndex>,
|
||||
/// The type of each nested wasm module this module contains.
|
||||
pub modules: PrimaryMap<ModuleIndex, ModuleTypeIndex>,
|
||||
}
|
||||
|
||||
/// Different forms an instance can take in a wasm module
|
||||
/// Initialization routines for creating an instance, encompassing imports,
|
||||
/// modules, instances, aliases, etc.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Instance {
|
||||
/// This is an imported instance with the specified type
|
||||
Import(TypeIndex),
|
||||
/// This is a locally created instance which instantiates the specified
|
||||
/// module with the given list of entities.
|
||||
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
|
||||
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
|
||||
/// index space.
|
||||
AliasParentModule(ModuleIndex),
|
||||
|
||||
/// A module from the parent's declared modules is inserted into our own
|
||||
/// index space.
|
||||
#[allow(missing_docs)]
|
||||
AliasInstanceExport {
|
||||
instance: InstanceIndex,
|
||||
export: usize,
|
||||
},
|
||||
|
||||
/// A module is being instantiated with previously configured intializers
|
||||
/// as arguments.
|
||||
Instantiate {
|
||||
/// The module that this instance is instantiating.
|
||||
module: ModuleIndex,
|
||||
/// The arguments provided to instantiation.
|
||||
args: Vec<EntityIndex>,
|
||||
},
|
||||
|
||||
/// A module is defined into the module index space, and which module is
|
||||
/// being defined is specified by the index payload.
|
||||
DefineModule(usize),
|
||||
}
|
||||
|
||||
impl Module {
|
||||
/// Allocates the module data structures.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: Self::next_id(),
|
||||
name: None,
|
||||
imports: Vec::new(),
|
||||
exports: IndexMap::new(),
|
||||
start_func: None,
|
||||
table_elements: Vec::new(),
|
||||
passive_elements: HashMap::new(),
|
||||
passive_data: HashMap::new(),
|
||||
func_names: HashMap::new(),
|
||||
num_imported_funcs: 0,
|
||||
num_imported_tables: 0,
|
||||
num_imported_memories: 0,
|
||||
num_imported_globals: 0,
|
||||
signatures: PrimaryMap::new(),
|
||||
functions: PrimaryMap::new(),
|
||||
table_plans: PrimaryMap::new(),
|
||||
memory_plans: PrimaryMap::new(),
|
||||
globals: PrimaryMap::new(),
|
||||
instances: PrimaryMap::new(),
|
||||
modules: PrimaryMap::new(),
|
||||
types: PrimaryMap::new(),
|
||||
}
|
||||
Module::default()
|
||||
}
|
||||
|
||||
/// Get the given passive element, if it exists.
|
||||
@@ -269,11 +259,6 @@ impl Module {
|
||||
self.passive_elements.get(&index).map(|es| &**es)
|
||||
}
|
||||
|
||||
fn next_id() -> usize {
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
NEXT_ID.fetch_add(1, SeqCst)
|
||||
}
|
||||
|
||||
/// Convert a `DefinedFuncIndex` into a `FuncIndex`.
|
||||
pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex {
|
||||
FuncIndex::new(self.num_imported_funcs + defined_func.index())
|
||||
@@ -362,17 +347,61 @@ impl Module {
|
||||
index.index() < self.num_imported_globals
|
||||
}
|
||||
|
||||
/// Convenience method for looking up the original Wasm signature of a
|
||||
/// function.
|
||||
pub fn wasm_func_type(&self, func_index: FuncIndex) -> &WasmFuncType {
|
||||
&self.signatures[self.functions[func_index]]
|
||||
/// Returns an iterator of all the imports in this module, along with their
|
||||
/// 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))),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the type of an item based on its index
|
||||
pub fn type_of(&self, index: EntityIndex) -> EntityType {
|
||||
match index {
|
||||
EntityIndex::Global(i) => EntityType::Global(self.globals[i]),
|
||||
EntityIndex::Table(i) => EntityType::Table(self.table_plans[i].table),
|
||||
EntityIndex::Memory(i) => EntityType::Memory(self.memory_plans[i].memory),
|
||||
EntityIndex::Function(i) => EntityType::Function(self.functions[i]),
|
||||
EntityIndex::Instance(i) => EntityType::Instance(self.instances[i]),
|
||||
EntityIndex::Module(i) => EntityType::Module(self.modules[i]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Module {
|
||||
fn default() -> Module {
|
||||
Module::new()
|
||||
}
|
||||
/// All types which are recorded for the entirety of a translation.
|
||||
///
|
||||
/// Note that this is shared amongst all modules coming out of a translation
|
||||
/// in the case of nested modules and the module linking proposal.
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct TypeTables {
|
||||
pub wasm_signatures: PrimaryMap<SignatureIndex, WasmFuncType>,
|
||||
pub native_signatures: PrimaryMap<SignatureIndex, ir::Signature>,
|
||||
pub module_signatures: PrimaryMap<ModuleTypeIndex, ModuleSignature>,
|
||||
pub instance_signatures: PrimaryMap<InstanceTypeIndex, InstanceSignature>,
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// what type they're importing.
|
||||
pub imports: Vec<(String, Option<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)]
|
||||
pub struct InstanceSignature {
|
||||
/// The name of what's being exported as well as its type signature.
|
||||
pub exports: IndexMap<String, EntityType>,
|
||||
}
|
||||
|
||||
mod passive_data_serde {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use crate::module::{MemoryPlan, Module, ModuleType, TableElements, TablePlan};
|
||||
use crate::module::{
|
||||
Initializer, InstanceSignature, MemoryPlan, Module, ModuleSignature, ModuleType, TableElements,
|
||||
TablePlan, TypeTables,
|
||||
};
|
||||
use crate::tunables::Tunables;
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_codegen::ir::{AbiParam, ArgumentPurpose};
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
use cranelift_entity::PrimaryMap;
|
||||
use cranelift_wasm::{
|
||||
self, translate_module, DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType,
|
||||
FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex,
|
||||
TargetEnvironment, TypeIndex, WasmError, WasmFuncType, WasmResult,
|
||||
self, translate_module, Alias, DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType,
|
||||
FuncIndex, Global, GlobalIndex, InstanceIndex, InstanceTypeIndex, Memory, MemoryIndex,
|
||||
ModuleIndex, ModuleTypeIndex, SignatureIndex, Table, TableIndex, TargetEnvironment, TypeIndex,
|
||||
WasmError, WasmFuncType, WasmResult,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
@@ -27,10 +31,15 @@ pub struct ModuleEnvironment<'data> {
|
||||
/// the module linking proposal.
|
||||
results: Vec<ModuleTranslation<'data>>,
|
||||
|
||||
/// Modules which are in-progress for being translated (our parents) and
|
||||
/// we'll resume once we finish the current module. This is only applicable
|
||||
/// with the module linking proposal.
|
||||
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,
|
||||
|
||||
/// 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,
|
||||
@@ -46,9 +55,6 @@ pub struct ModuleTranslation<'data> {
|
||||
/// Module information.
|
||||
pub module: Module,
|
||||
|
||||
/// Map of native signatures
|
||||
pub native_signatures: PrimaryMap<SignatureIndex, ir::Signature>,
|
||||
|
||||
/// References to the function bodies.
|
||||
pub function_body_inputs: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'data>>,
|
||||
|
||||
@@ -58,11 +64,23 @@ pub struct ModuleTranslation<'data> {
|
||||
/// DWARF debug information, if enabled, parsed from the module.
|
||||
pub debuginfo: DebugInfoData<'data>,
|
||||
|
||||
/// Indexes into the returned list of translations that are submodules of
|
||||
/// this module.
|
||||
pub submodules: Vec<usize>,
|
||||
/// Set if debuginfo was found but it was not parsed due to `Tunables`
|
||||
/// configuration.
|
||||
pub has_unparsed_debuginfo: bool,
|
||||
|
||||
/// When we're parsing the code section this will be incremented so we know
|
||||
/// 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>,
|
||||
|
||||
/// Used as a pointer into the above list as the module code section is
|
||||
/// parsed.
|
||||
num_modules_defined: usize,
|
||||
}
|
||||
|
||||
/// Contains function data: byte code and its offset in the module.
|
||||
@@ -81,8 +99,8 @@ pub struct DebugInfoData<'a> {
|
||||
pub wasm_file: WasmFileInfo,
|
||||
debug_loc: gimli::DebugLoc<Reader<'a>>,
|
||||
debug_loclists: gimli::DebugLocLists<Reader<'a>>,
|
||||
debug_ranges: gimli::DebugRanges<Reader<'a>>,
|
||||
debug_rnglists: gimli::DebugRngLists<Reader<'a>>,
|
||||
pub debug_ranges: gimli::DebugRanges<Reader<'a>>,
|
||||
pub debug_rnglists: gimli::DebugRngLists<Reader<'a>>,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
@@ -124,7 +142,9 @@ 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,
|
||||
@@ -135,12 +155,28 @@ impl<'data> ModuleEnvironment<'data> {
|
||||
self.target_config.pointer_type()
|
||||
}
|
||||
|
||||
/// Translate a wasm module using this environment. This consumes the
|
||||
/// `ModuleEnvironment` and produces a `ModuleTranslation`.
|
||||
pub fn translate(mut self, data: &'data [u8]) -> WasmResult<Vec<ModuleTranslation<'data>>> {
|
||||
/// Translate a wasm module using this environment.
|
||||
///
|
||||
/// This consumes the `ModuleEnvironment` and produces a list of
|
||||
/// `ModuleTranslation`s as well as a `TypeTables`. The list of module
|
||||
/// translations corresponds to all wasm modules found in the input `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.
|
||||
///
|
||||
/// The `TypeTables` structure returned contains intern'd versions of types
|
||||
/// referenced from each module translation. This primarily serves as the
|
||||
/// source of truth for module-linking use cases where modules can refer to
|
||||
/// other module's types. All `SignatureIndex`, `ModuleTypeIndex`, and
|
||||
/// `InstanceTypeIndex` values are resolved through the returned tables.
|
||||
pub fn translate(
|
||||
mut self,
|
||||
data: &'data [u8],
|
||||
) -> WasmResult<(Vec<ModuleTranslation<'data>>, TypeTables)> {
|
||||
translate_module(data, &mut self)?;
|
||||
assert!(self.results.len() > 0);
|
||||
Ok(self.results)
|
||||
Ok((self.results, self.types))
|
||||
}
|
||||
|
||||
fn declare_export(&mut self, export: EntityIndex, name: &str) -> WasmResult<()> {
|
||||
@@ -152,9 +188,11 @@ impl<'data> ModuleEnvironment<'data> {
|
||||
}
|
||||
|
||||
fn register_dwarf_section(&mut self, name: &str, data: &'data [u8]) {
|
||||
if !self.tunables.debug_info {
|
||||
if !self.tunables.generate_native_debuginfo && !self.tunables.parse_wasm_debuginfo {
|
||||
self.result.has_unparsed_debuginfo = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if !name.starts_with(".debug_") {
|
||||
return;
|
||||
}
|
||||
@@ -203,16 +241,23 @@ impl<'data> TargetEnvironment for ModuleEnvironment<'data> {
|
||||
impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data> {
|
||||
fn reserve_types(&mut self, num: u32) -> WasmResult<()> {
|
||||
let num = usize::try_from(num).unwrap();
|
||||
self.result.module.types.reserve_exact(num);
|
||||
self.result.native_signatures.reserve_exact(num);
|
||||
self.result.module.types.reserve(num);
|
||||
self.types.native_signatures.reserve(num);
|
||||
self.types.wasm_signatures.reserve(num);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn declare_type_func(&mut self, wasm: WasmFuncType, sig: ir::Signature) -> WasmResult<()> {
|
||||
let sig = translate_signature(sig, self.pointer_type());
|
||||
// TODO: Deduplicate signatures.
|
||||
self.result.native_signatures.push(sig);
|
||||
let sig_index = self.result.module.signatures.push(wasm);
|
||||
|
||||
// FIXME(#2469): Signatures should be deduplicated in these two tables
|
||||
// since `SignatureIndex` is already a index space separate from the
|
||||
// module's index space. Note that this may get more urgent with
|
||||
// module-linking modules where types are more likely to get repeated
|
||||
// (across modules).
|
||||
let sig_index = self.types.native_signatures.push(sig);
|
||||
let sig_index2 = self.types.wasm_signatures.push(wasm);
|
||||
debug_assert_eq!(sig_index, sig_index2);
|
||||
self.result
|
||||
.module
|
||||
.types
|
||||
@@ -233,10 +278,19 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
.iter()
|
||||
.map(|e| (e.0.to_string(), e.1.clone()))
|
||||
.collect();
|
||||
self.result
|
||||
.module
|
||||
|
||||
// FIXME(#2469): Like signatures above we should probably deduplicate
|
||||
// the listings of module types since with module linking it's possible
|
||||
// you'll need to write down the module type in multiple locations.
|
||||
let exports = self
|
||||
.types
|
||||
.push(ModuleType::Module { imports, exports });
|
||||
.instance_signatures
|
||||
.push(InstanceSignature { exports });
|
||||
let idx = self
|
||||
.types
|
||||
.module_signatures
|
||||
.push(ModuleSignature { imports, exports });
|
||||
self.result.module.types.push(ModuleType::Module(idx));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -245,19 +299,46 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
.iter()
|
||||
.map(|e| (e.0.to_string(), e.1.clone()))
|
||||
.collect();
|
||||
self.result
|
||||
.module
|
||||
|
||||
// FIXME(#2469): Like signatures above we should probably deduplicate
|
||||
// the listings of instance types since with module linking it's
|
||||
// possible you'll need to write down the module type in multiple
|
||||
// locations.
|
||||
let idx = self
|
||||
.types
|
||||
.push(ModuleType::Instance { exports });
|
||||
.instance_signatures
|
||||
.push(InstanceSignature { exports });
|
||||
self.result.module.types.push(ModuleType::Instance(idx));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn type_to_signature(&self, index: TypeIndex) -> WasmResult<SignatureIndex> {
|
||||
match self.result.module.types[index] {
|
||||
ModuleType::Function(sig) => Ok(sig),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_to_module_type(&self, index: TypeIndex) -> WasmResult<ModuleTypeIndex> {
|
||||
match self.result.module.types[index] {
|
||||
ModuleType::Module(sig) => Ok(sig),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_to_instance_type(&self, index: TypeIndex) -> WasmResult<InstanceTypeIndex> {
|
||||
match self.result.module.types[index] {
|
||||
ModuleType::Instance(sig) => Ok(sig),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reserve_imports(&mut self, num: u32) -> WasmResult<()> {
|
||||
Ok(self
|
||||
.result
|
||||
.module
|
||||
.imports
|
||||
.reserve_exact(usize::try_from(num).unwrap()))
|
||||
.initializers
|
||||
.reserve(usize::try_from(num).unwrap()))
|
||||
}
|
||||
|
||||
fn declare_func_import(
|
||||
@@ -273,11 +354,11 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
);
|
||||
let sig_index = self.result.module.types[index].unwrap_function();
|
||||
let func_index = self.result.module.functions.push(sig_index);
|
||||
self.result.module.imports.push((
|
||||
module.to_owned(),
|
||||
field.map(|s| s.to_owned()),
|
||||
EntityIndex::Function(func_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.result.module.num_imported_funcs += 1;
|
||||
self.result.debuginfo.wasm_file.imported_func_count += 1;
|
||||
Ok(())
|
||||
@@ -296,11 +377,11 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
);
|
||||
let plan = TablePlan::for_table(table, &self.tunables);
|
||||
let table_index = self.result.module.table_plans.push(plan);
|
||||
self.result.module.imports.push((
|
||||
module.to_owned(),
|
||||
field.map(|s| s.to_owned()),
|
||||
EntityIndex::Table(table_index),
|
||||
));
|
||||
self.result.module.initializers.push(Initializer::Import {
|
||||
module: module.to_owned(),
|
||||
field: field.map(|s| s.to_owned()),
|
||||
index: EntityIndex::Table(table_index),
|
||||
});
|
||||
self.result.module.num_imported_tables += 1;
|
||||
Ok(())
|
||||
}
|
||||
@@ -321,11 +402,11 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
}
|
||||
let plan = MemoryPlan::for_memory(memory, &self.tunables);
|
||||
let memory_index = self.result.module.memory_plans.push(plan);
|
||||
self.result.module.imports.push((
|
||||
module.to_owned(),
|
||||
field.map(|s| s.to_owned()),
|
||||
EntityIndex::Memory(memory_index),
|
||||
));
|
||||
self.result.module.initializers.push(Initializer::Import {
|
||||
module: module.to_owned(),
|
||||
field: field.map(|s| s.to_owned()),
|
||||
index: EntityIndex::Memory(memory_index),
|
||||
});
|
||||
self.result.module.num_imported_memories += 1;
|
||||
Ok(())
|
||||
}
|
||||
@@ -342,15 +423,47 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
"Imported globals must be declared first"
|
||||
);
|
||||
let global_index = self.result.module.globals.push(global);
|
||||
self.result.module.imports.push((
|
||||
module.to_owned(),
|
||||
field.map(|s| s.to_owned()),
|
||||
EntityIndex::Global(global_index),
|
||||
));
|
||||
self.result.module.initializers.push(Initializer::Import {
|
||||
module: module.to_owned(),
|
||||
field: field.map(|s| s.to_owned()),
|
||||
index: EntityIndex::Global(global_index),
|
||||
});
|
||||
self.result.module.num_imported_globals += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn declare_module_import(
|
||||
&mut self,
|
||||
ty_index: TypeIndex,
|
||||
module: &'data str,
|
||||
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),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn declare_instance_import(
|
||||
&mut self,
|
||||
ty_index: TypeIndex,
|
||||
module: &'data str,
|
||||
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),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reserve_func_types(&mut self, num: u32) -> WasmResult<()> {
|
||||
self.result
|
||||
.module
|
||||
@@ -436,6 +549,14 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
self.declare_export(EntityIndex::Global(global_index), name)
|
||||
}
|
||||
|
||||
fn declare_module_export(&mut self, index: ModuleIndex, name: &str) -> WasmResult<()> {
|
||||
self.declare_export(EntityIndex::Module(index), name)
|
||||
}
|
||||
|
||||
fn declare_instance_export(&mut self, index: InstanceIndex, name: &str) -> WasmResult<()> {
|
||||
self.declare_export(EntityIndex::Instance(index), name)
|
||||
}
|
||||
|
||||
fn declare_start_func(&mut self, func_index: FuncIndex) -> WasmResult<()> {
|
||||
debug_assert!(self.result.module.start_func.is_none());
|
||||
self.result.module.start_func = Some(func_index);
|
||||
@@ -493,11 +614,11 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
validator: FuncValidator<ValidatorResources>,
|
||||
body: FunctionBody<'data>,
|
||||
) -> WasmResult<()> {
|
||||
if self.tunables.debug_info {
|
||||
if self.tunables.generate_native_debuginfo {
|
||||
let func_index = self.result.code_index + self.result.module.num_imported_funcs as u32;
|
||||
let func_index = FuncIndex::from_u32(func_index);
|
||||
let sig_index = self.result.module.functions[func_index];
|
||||
let sig = &self.result.module.signatures[sig_index];
|
||||
let sig = &self.types.wasm_signatures[sig_index];
|
||||
let mut locals = Vec::new();
|
||||
for pair in body.get_locals_reader()? {
|
||||
locals.push(pair?);
|
||||
@@ -563,7 +684,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
|
||||
fn declare_module_name(&mut self, name: &'data str) {
|
||||
self.result.module.name = Some(name.to_string());
|
||||
if self.tunables.debug_info {
|
||||
if self.tunables.generate_native_debuginfo {
|
||||
self.result.debuginfo.name_section.module_name = Some(name);
|
||||
}
|
||||
}
|
||||
@@ -573,7 +694,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
.module
|
||||
.func_names
|
||||
.insert(func_index, name.to_string());
|
||||
if self.tunables.debug_info {
|
||||
if self.tunables.generate_native_debuginfo {
|
||||
self.result
|
||||
.debuginfo
|
||||
.name_section
|
||||
@@ -583,7 +704,7 @@ impl<'data> cranelift_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data
|
||||
}
|
||||
|
||||
fn declare_local_name(&mut self, func_index: FuncIndex, local: u32, name: &'data str) {
|
||||
if self.tunables.debug_info {
|
||||
if self.tunables.generate_native_debuginfo {
|
||||
self.result
|
||||
.debuginfo
|
||||
.name_section
|
||||
@@ -623,31 +744,161 @@ and for re-adding support for interface types you can see this issue:
|
||||
}
|
||||
|
||||
fn reserve_modules(&mut self, amount: u32) {
|
||||
let extra = self.results.capacity() + (amount as usize) - self.results.len();
|
||||
self.results.reserve(extra);
|
||||
self.result.submodules.reserve(amount as usize);
|
||||
// Go ahead and reserve space in the final `results` array for `amount`
|
||||
// more modules.
|
||||
self.modules_to_be += amount as usize;
|
||||
self.results.reserve(self.modules_to_be);
|
||||
|
||||
// Then also reserve space in our own local module's metadata fields
|
||||
// we'll be adding to.
|
||||
self.result.module.modules.reserve(amount as usize);
|
||||
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) {
|
||||
// skip the first module since `self.result` is already empty and we'll
|
||||
// be translating into that.
|
||||
// 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 in_progress = mem::replace(&mut self.result, ModuleTranslation::default());
|
||||
self.in_progress.push(in_progress);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
self.modules_to_be -= 1;
|
||||
}
|
||||
|
||||
fn module_end(&mut self, index: usize) {
|
||||
let to_continue = match self.in_progress.pop() {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
assert_eq!(index, 0);
|
||||
ModuleTranslation::default()
|
||||
assert!(self.result.num_modules_defined == self.result.module_initializer_indexes.len());
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
fn reserve_instances(&mut self, amt: u32) {
|
||||
self.result.module.instances.reserve(amt as usize);
|
||||
self.result.module.initializers.reserve(amt as usize);
|
||||
}
|
||||
|
||||
fn declare_instance(&mut self, module: ModuleIndex, args: Vec<EntityIndex>) -> WasmResult<()> {
|
||||
// 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.
|
||||
let module_ty = self.result.module.modules[module];
|
||||
let instance_ty = self.types.module_signatures[module_ty].exports;
|
||||
self.result.module.instances.push(instance_ty);
|
||||
self.result
|
||||
.module
|
||||
.initializers
|
||||
.push(Initializer::Instantiate { module, args });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn declare_alias(&mut self, alias: Alias) -> WasmResult<()> {
|
||||
match alias {
|
||||
// Types are easy, we statically know everything so we're just
|
||||
// copying some pointers from our parent module to our own module.
|
||||
//
|
||||
// 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];
|
||||
self.result.module.types.push(ty);
|
||||
}
|
||||
};
|
||||
let finished = mem::replace(&mut self.result, to_continue);
|
||||
self.result.submodules.push(self.results.len());
|
||||
self.results.push(finished);
|
||||
|
||||
// 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);
|
||||
self.result
|
||||
.module
|
||||
.initializers
|
||||
.push(Initializer::AliasParentModule(parent_idx));
|
||||
}
|
||||
|
||||
// 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 } => {
|
||||
let ty = self.result.module.instances[instance];
|
||||
match &self.types.instance_signatures[ty].exports[export] {
|
||||
EntityType::Global(g) => {
|
||||
self.result.module.globals.push(g.clone());
|
||||
self.result.module.num_imported_globals += 1;
|
||||
}
|
||||
EntityType::Memory(mem) => {
|
||||
let plan = MemoryPlan::for_memory(*mem, &self.tunables);
|
||||
self.result.module.memory_plans.push(plan);
|
||||
self.result.module.num_imported_memories += 1;
|
||||
}
|
||||
EntityType::Table(t) => {
|
||||
let plan = TablePlan::for_table(*t, &self.tunables);
|
||||
self.result.module.table_plans.push(plan);
|
||||
self.result.module.num_imported_tables += 1;
|
||||
}
|
||||
EntityType::Function(sig) => {
|
||||
self.result.module.functions.push(*sig);
|
||||
self.result.module.num_imported_funcs += 1;
|
||||
self.result.debuginfo.wasm_file.imported_func_count += 1;
|
||||
}
|
||||
EntityType::Instance(sig) => {
|
||||
self.result.module.instances.push(*sig);
|
||||
}
|
||||
EntityType::Module(sig) => {
|
||||
self.result.module.modules.push(*sig);
|
||||
}
|
||||
EntityType::Event(_) => unimplemented!(),
|
||||
}
|
||||
self.result
|
||||
.module
|
||||
.initializers
|
||||
.push(Initializer::AliasInstanceExport { instance, export })
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,11 @@ pub struct Tunables {
|
||||
/// The size in bytes of the offset guard for dynamic heaps.
|
||||
pub dynamic_memory_offset_guard_size: u64,
|
||||
|
||||
/// Whether or not to generate DWARF debug information.
|
||||
pub debug_info: bool,
|
||||
/// Whether or not to generate native DWARF debug information.
|
||||
pub generate_native_debuginfo: bool,
|
||||
|
||||
/// Whether or not to retain DWARF sections in compiled modules.
|
||||
pub parse_wasm_debuginfo: bool,
|
||||
|
||||
/// Whether or not to enable the ability to interrupt wasm code dynamically.
|
||||
///
|
||||
@@ -51,7 +54,8 @@ impl Default for Tunables {
|
||||
/// wasting too much memory.
|
||||
dynamic_memory_offset_guard_size: 0x1_0000,
|
||||
|
||||
debug_info: false,
|
||||
generate_native_debuginfo: false,
|
||||
parse_wasm_debuginfo: true,
|
||||
interruptable: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::BuiltinFunctionIndex;
|
||||
use cranelift_codegen::ir;
|
||||
use cranelift_wasm::{
|
||||
DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, GlobalIndex, MemoryIndex,
|
||||
SignatureIndex, TableIndex,
|
||||
TableIndex, TypeIndex,
|
||||
};
|
||||
use more_asserts::assert_lt;
|
||||
use std::convert::TryFrom;
|
||||
@@ -78,7 +78,7 @@ impl VMOffsets {
|
||||
pub fn new(pointer_size: u8, module: &Module) -> Self {
|
||||
Self {
|
||||
pointer_size,
|
||||
num_signature_ids: cast_to_u32(module.signatures.len()),
|
||||
num_signature_ids: cast_to_u32(module.types.len()),
|
||||
num_imported_functions: cast_to_u32(module.num_imported_funcs),
|
||||
num_imported_tables: cast_to_u32(module.num_imported_tables),
|
||||
num_imported_memories: cast_to_u32(module.num_imported_memories),
|
||||
@@ -430,7 +430,7 @@ impl VMOffsets {
|
||||
}
|
||||
|
||||
/// Return the offset to `VMSharedSignatureId` index `index`.
|
||||
pub fn vmctx_vmshared_signature_id(&self, index: SignatureIndex) -> u32 {
|
||||
pub fn vmctx_vmshared_signature_id(&self, index: TypeIndex) -> u32 {
|
||||
assert_lt!(index.as_u32(), self.num_signature_ids);
|
||||
self.vmctx_signature_ids_begin()
|
||||
.checked_add(
|
||||
|
||||
@@ -12,11 +12,15 @@ arbitrary = { version = "0.4.1", features = ["derive"] }
|
||||
env_logger = "0.8.1"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.1"
|
||||
wasmparser = "0.68.0"
|
||||
wasmprinter = "0.2.15"
|
||||
wasmparser = "0.70"
|
||||
wasmprinter = "0.2.17"
|
||||
wasmtime = { path = "../wasmtime" }
|
||||
wasmtime-wast = { path = "../wast" }
|
||||
wasm-smith = "0.1.10"
|
||||
wasm-smith = "0.3.0"
|
||||
wasmi = "0.7.0"
|
||||
|
||||
[dev-dependencies]
|
||||
wat = "1.0.28"
|
||||
|
||||
[features]
|
||||
experimental_x64 = ["wasmtime/experimental_x64"]
|
||||
|
||||
@@ -38,6 +38,7 @@ pub fn fuzz_default_config(strategy: wasmtime::Strategy) -> anyhow::Result<wasmt
|
||||
.cranelift_nan_canonicalization(true)
|
||||
.wasm_bulk_memory(true)
|
||||
.wasm_reference_types(true)
|
||||
.wasm_module_linking(true)
|
||||
.strategy(strategy)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
|
||||
pub mod dummy;
|
||||
|
||||
use arbitrary::Arbitrary;
|
||||
use dummy::dummy_imports;
|
||||
use log::debug;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
|
||||
use std::time::Duration;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use wasmtime::*;
|
||||
use wasmtime_wast::WastContext;
|
||||
|
||||
@@ -54,8 +57,13 @@ fn log_wat(wat: &str) {
|
||||
/// Performs initial validation, and returns early if the Wasm is invalid.
|
||||
///
|
||||
/// You can control which compiler is used via passing a `Strategy`.
|
||||
pub fn instantiate(wasm: &[u8], strategy: Strategy) {
|
||||
instantiate_with_config(wasm, crate::fuzz_default_config(strategy).unwrap(), None);
|
||||
pub fn instantiate(wasm: &[u8], known_valid: bool, strategy: Strategy) {
|
||||
instantiate_with_config(
|
||||
wasm,
|
||||
known_valid,
|
||||
crate::fuzz_default_config(strategy).unwrap(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
|
||||
@@ -64,42 +72,48 @@ pub fn instantiate(wasm: &[u8], strategy: Strategy) {
|
||||
/// The engine will be configured using provided config.
|
||||
///
|
||||
/// See also `instantiate` functions.
|
||||
pub fn instantiate_with_config(wasm: &[u8], mut config: Config, timeout: Option<Duration>) {
|
||||
pub fn instantiate_with_config(
|
||||
wasm: &[u8],
|
||||
known_valid: bool,
|
||||
mut config: Config,
|
||||
timeout: Option<Duration>,
|
||||
) {
|
||||
crate::init_fuzzing();
|
||||
|
||||
config.interruptable(timeout.is_some());
|
||||
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();
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(timeout);
|
||||
handle.interrupt();
|
||||
});
|
||||
timeout_state.spawn_timeout(timeout, move || handle.interrupt());
|
||||
}
|
||||
|
||||
log_wasm(wasm);
|
||||
let module = match Module::new(&engine, wasm) {
|
||||
Ok(module) => module,
|
||||
Err(_) => return,
|
||||
Err(_) if !known_valid => return,
|
||||
Err(e) => panic!("failed to compile module: {:?}", e),
|
||||
};
|
||||
let imports = dummy_imports(&store, module.imports());
|
||||
|
||||
let imports = match dummy_imports(&store, module.imports()) {
|
||||
Ok(imps) => imps,
|
||||
Err(_) => {
|
||||
// There are some value types that we can't synthesize a
|
||||
// dummy value for (e.g. externrefs) and for modules that
|
||||
// import things of these types we skip instantiation.
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Don't unwrap this: there can be instantiation-/link-time errors that
|
||||
// aren't caught during validation or compilation. For example, an imported
|
||||
// table might not have room for an element segment that we want to
|
||||
// initialize into it.
|
||||
let _result = Instance::new(&store, &module, &imports);
|
||||
match Instance::new(&store, &module, &imports) {
|
||||
Ok(_) => {}
|
||||
// Allow traps which can happen normally with `unreachable`
|
||||
Err(e) if e.downcast_ref::<Trap>().is_some() => {}
|
||||
Err(e) => panic!("failed to instantiate {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile the Wasm buffer, and implicitly fail if we have an unexpected
|
||||
@@ -151,31 +165,14 @@ pub fn differential_execution(
|
||||
let engine = Engine::new(config);
|
||||
let store = Store::new(&engine);
|
||||
|
||||
let module = match Module::new(&engine, &wasm) {
|
||||
Ok(module) => module,
|
||||
// The module might rely on some feature that our config didn't
|
||||
// enable or something like that.
|
||||
Err(e) => {
|
||||
eprintln!("Warning: failed to compile `wasm-opt -ttf` module: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let module = Module::new(&engine, &wasm).unwrap();
|
||||
|
||||
// TODO: we should implement tracing versions of these dummy imports
|
||||
// that record a trace of the order that imported functions were called
|
||||
// in and with what values. Like the results of exported functions,
|
||||
// calls to imports should also yield the same values for each
|
||||
// configuration, and we should assert that.
|
||||
let imports = match dummy_imports(&store, module.imports()) {
|
||||
Ok(imps) => imps,
|
||||
Err(e) => {
|
||||
// There are some value types that we can't synthesize a
|
||||
// dummy value for (e.g. externrefs) and for modules that
|
||||
// import things of these types we skip instantiation.
|
||||
eprintln!("Warning: failed to synthesize dummy imports: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let imports = dummy_imports(&store, module.imports());
|
||||
|
||||
// Don't unwrap this: there can be instantiation-/link-time errors that
|
||||
// aren't caught during validation or compilation. For example, an imported
|
||||
@@ -201,10 +198,7 @@ pub fn differential_execution(
|
||||
init_hang_limit(&instance);
|
||||
|
||||
let ty = f.ty();
|
||||
let params = match dummy::dummy_values(ty.params()) {
|
||||
Ok(p) => p,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let params = dummy::dummy_values(ty.params());
|
||||
let this_result = f.call(¶ms).map_err(|e| e.downcast::<Trap>().unwrap());
|
||||
|
||||
let existing_result = export_func_results
|
||||
@@ -249,24 +243,8 @@ pub fn differential_execution(
|
||||
(Val::I32(lhs), Val::I32(rhs)) if lhs == rhs => continue,
|
||||
(Val::I64(lhs), Val::I64(rhs)) if lhs == rhs => continue,
|
||||
(Val::V128(lhs), Val::V128(rhs)) if lhs == rhs => continue,
|
||||
(Val::F32(lhs), Val::F32(rhs)) => {
|
||||
let lhs = f32::from_bits(*lhs);
|
||||
let rhs = f32::from_bits(*rhs);
|
||||
if lhs == rhs || (lhs.is_nan() && rhs.is_nan()) {
|
||||
continue;
|
||||
} else {
|
||||
fail()
|
||||
}
|
||||
}
|
||||
(Val::F64(lhs), Val::F64(rhs)) => {
|
||||
let lhs = f64::from_bits(*lhs);
|
||||
let rhs = f64::from_bits(*rhs);
|
||||
if lhs == rhs || (lhs.is_nan() && rhs.is_nan()) {
|
||||
continue;
|
||||
} else {
|
||||
fail()
|
||||
}
|
||||
}
|
||||
(Val::F32(lhs), Val::F32(rhs)) if f32_equal(*lhs, *rhs) => continue,
|
||||
(Val::F64(lhs), Val::F64(rhs)) if f64_equal(*lhs, *rhs) => continue,
|
||||
(Val::ExternRef(_), Val::ExternRef(_))
|
||||
| (Val::FuncRef(_), Val::FuncRef(_)) => continue,
|
||||
_ => fail(),
|
||||
@@ -278,6 +256,18 @@ pub fn differential_execution(
|
||||
}
|
||||
}
|
||||
|
||||
fn f32_equal(a: u32, b: u32) -> bool {
|
||||
let a = f32::from_bits(a);
|
||||
let b = f32::from_bits(b);
|
||||
a == b || (a.is_nan() && b.is_nan())
|
||||
}
|
||||
|
||||
fn f64_equal(a: u64, b: u64) -> bool {
|
||||
let a = f64::from_bits(a);
|
||||
let b = f64::from_bits(b);
|
||||
a == b || (a.is_nan() && b.is_nan())
|
||||
}
|
||||
|
||||
/// Invoke the given API calls.
|
||||
pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
|
||||
use crate::generators::api::ApiCall;
|
||||
@@ -346,16 +336,7 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
|
||||
};
|
||||
|
||||
let store = store.as_ref().unwrap();
|
||||
|
||||
let imports = match dummy_imports(store, module.imports()) {
|
||||
Ok(imps) => imps,
|
||||
Err(_) => {
|
||||
// There are some value types that we can't synthesize a
|
||||
// dummy value for (e.g. externrefs) and for modules that
|
||||
// import things of these types we skip instantiation.
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let imports = dummy_imports(store, module.imports());
|
||||
|
||||
// Don't unwrap this: there can be instantiation-/link-time errors that
|
||||
// aren't caught during validation or compilation. For example, an imported
|
||||
@@ -401,10 +382,7 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
|
||||
let nth = nth % funcs.len();
|
||||
let f = &funcs[nth];
|
||||
let ty = f.ty();
|
||||
let params = match dummy::dummy_values(ty.params()) {
|
||||
Ok(p) => p,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let params = dummy::dummy_values(ty.params());
|
||||
let _ = f.call(¶ms);
|
||||
}
|
||||
}
|
||||
@@ -479,3 +457,225 @@ pub fn table_ops(config: crate::generators::Config, ops: crate::generators::tabl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration options for wasm-smith such that generated modules always
|
||||
/// conform to certain specifications.
|
||||
#[derive(Default, Debug, Arbitrary, Clone)]
|
||||
pub struct DifferentialWasmiModuleConfig;
|
||||
|
||||
impl wasm_smith::Config for DifferentialWasmiModuleConfig {
|
||||
fn allow_start_export(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn min_funcs(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_funcs(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn min_memories(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_memories(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn max_imports(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn min_exports(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn max_memory_pages(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn memory_max_size_required(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform differential execution between Cranelift and wasmi, diffing the
|
||||
/// resulting memory image when execution terminates. This relies on the
|
||||
/// module-under-test to be instrumented to bound the execution time. Invoke
|
||||
/// with a module generated by `wasm-smith` using the
|
||||
/// `DiferentialWasmiModuleConfig` configuration type for best results.
|
||||
///
|
||||
/// May return `None` if we early-out due to a rejected fuzz config; these
|
||||
/// should be rare if modules are generated appropriately.
|
||||
pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Config) -> Option<()> {
|
||||
crate::init_fuzzing();
|
||||
|
||||
// Instantiate wasmi module and instance.
|
||||
let wasmi_module = wasmi::Module::from_buffer(&wasm[..]).ok()?;
|
||||
let wasmi_instance =
|
||||
wasmi::ModuleInstance::new(&wasmi_module, &wasmi::ImportsBuilder::default()).ok()?;
|
||||
let wasmi_instance = wasmi_instance.assert_no_start();
|
||||
|
||||
// TODO(paritytech/wasmi#19): wasmi does not currently canonicalize NaNs. To avoid spurious
|
||||
// fuzz failures, for now let's fuzz only integer Wasm programs.
|
||||
if wasmi_module.deny_floating_point().is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Instantiate wasmtime module and instance.
|
||||
let mut wasmtime_config = config.to_wasmtime();
|
||||
wasmtime_config.cranelift_nan_canonicalization(true);
|
||||
let wasmtime_engine = Engine::new(&wasmtime_config);
|
||||
let wasmtime_store = Store::new(&wasmtime_engine);
|
||||
let wasmtime_module =
|
||||
Module::new(&wasmtime_engine, &wasm).expect("Wasmtime can compile module");
|
||||
let wasmtime_instance = Instance::new(&wasmtime_store, &wasmtime_module, &[])
|
||||
.expect("Wasmtime can instantiate module");
|
||||
|
||||
// Introspect wasmtime module to find name of an exported function and of an
|
||||
// exported memory. Stop when we have one of each. (According to the config
|
||||
// above, there should be at most one of each.)
|
||||
let (func_name, memory_name) = {
|
||||
let mut func_name = None;
|
||||
let mut memory_name = None;
|
||||
for e in wasmtime_module.exports() {
|
||||
match e.ty() {
|
||||
wasmtime::ExternType::Func(..) => func_name = Some(e.name().to_string()),
|
||||
wasmtime::ExternType::Memory(..) => memory_name = Some(e.name().to_string()),
|
||||
_ => {}
|
||||
}
|
||||
if func_name.is_some() && memory_name.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(func_name?, memory_name?)
|
||||
};
|
||||
|
||||
let wasmi_mem_export = wasmi_instance.export_by_name(&memory_name[..]).unwrap();
|
||||
let wasmi_mem = wasmi_mem_export.as_memory().unwrap();
|
||||
let wasmi_main_export = wasmi_instance.export_by_name(&func_name[..]).unwrap();
|
||||
let wasmi_main = wasmi_main_export.as_func().unwrap();
|
||||
let wasmi_val = wasmi::FuncInstance::invoke(&wasmi_main, &[], &mut wasmi::NopExternals);
|
||||
|
||||
let wasmtime_mem = wasmtime_instance
|
||||
.get_memory(&memory_name[..])
|
||||
.expect("memory export is present");
|
||||
let wasmtime_main = wasmtime_instance
|
||||
.get_func(&func_name[..])
|
||||
.expect("function export is present");
|
||||
let wasmtime_vals = wasmtime_main.call(&[]);
|
||||
let wasmtime_val = wasmtime_vals.map(|v| v.iter().next().cloned());
|
||||
|
||||
debug!(
|
||||
"Successful execution: wasmi returned {:?}, wasmtime returned {:?}",
|
||||
wasmi_val, wasmtime_val
|
||||
);
|
||||
|
||||
let show_wat = || {
|
||||
if let Ok(s) = wasmprinter::print_bytes(&wasm[..]) {
|
||||
eprintln!("wat:\n{}\n", s);
|
||||
}
|
||||
};
|
||||
|
||||
match (&wasmi_val, &wasmtime_val) {
|
||||
(&Ok(Some(wasmi::RuntimeValue::I32(a))), &Ok(Some(Val::I32(b)))) if a == b => {}
|
||||
(&Ok(Some(wasmi::RuntimeValue::F32(a))), &Ok(Some(Val::F32(b))))
|
||||
if f32_equal(a.to_bits(), b) => {}
|
||||
(&Ok(Some(wasmi::RuntimeValue::I64(a))), &Ok(Some(Val::I64(b)))) if a == b => {}
|
||||
(&Ok(Some(wasmi::RuntimeValue::F64(a))), &Ok(Some(Val::F64(b))))
|
||||
if f64_equal(a.to_bits(), b) => {}
|
||||
(&Ok(None), &Ok(None)) => {}
|
||||
(&Err(_), &Err(_)) => {}
|
||||
_ => {
|
||||
show_wat();
|
||||
panic!(
|
||||
"Values do not match: wasmi returned {:?}; wasmtime returned {:?}",
|
||||
wasmi_val, wasmtime_val
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if wasmi_mem.current_size().0 != wasmtime_mem.size() as usize {
|
||||
show_wat();
|
||||
panic!("resulting memories are not the same size");
|
||||
}
|
||||
|
||||
// Wasmi memory may be stored non-contiguously; copy it out to a contiguous chunk.
|
||||
let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size()];
|
||||
wasmi_mem
|
||||
.get_into(0, &mut wasmi_buf[..])
|
||||
.expect("can access wasmi memory");
|
||||
|
||||
let wasmtime_slice = unsafe { wasmtime_mem.data_unchecked() };
|
||||
|
||||
if wasmi_buf.len() >= 64 {
|
||||
debug!("-> First 64 bytes of wasmi heap: {:?}", &wasmi_buf[0..64]);
|
||||
debug!(
|
||||
"-> First 64 bytes of Wasmtime heap: {:?}",
|
||||
&wasmtime_slice[0..64]
|
||||
);
|
||||
}
|
||||
|
||||
if &wasmi_buf[..] != &wasmtime_slice[..] {
|
||||
show_wat();
|
||||
panic!("memory contents are not equal");
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SignalOnDrop {
|
||||
state: Arc<(Mutex<bool>, Condvar)>,
|
||||
thread: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl SignalOnDrop {
|
||||
fn spawn_timeout(&mut self, dur: Duration, closure: impl FnOnce() + Send + 'static) {
|
||||
let state = self.state.clone();
|
||||
let start = Instant::now();
|
||||
self.thread = Some(std::thread::spawn(move || {
|
||||
// Using our mutex/condvar we wait here for the first of `dur` to
|
||||
// pass or the `SignalOnDrop` instance to get dropped.
|
||||
let (lock, cvar) = &*state;
|
||||
let mut signaled = lock.lock().unwrap();
|
||||
while !*signaled {
|
||||
// Adjust our requested `dur` based on how much time has passed.
|
||||
let dur = match dur.checked_sub(start.elapsed()) {
|
||||
Some(dur) => dur,
|
||||
None => break,
|
||||
};
|
||||
let (lock, result) = cvar.wait_timeout(signaled, dur).unwrap();
|
||||
signaled = lock;
|
||||
// If we timed out for sure then there's no need to continue
|
||||
// since we'll just abort on the next `checked_sub` anyway.
|
||||
if result.timed_out() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
drop(signaled);
|
||||
|
||||
closure();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SignalOnDrop {
|
||||
fn drop(&mut self) {
|
||||
if let Some(thread) = self.thread.take() {
|
||||
let (lock, cvar) = &*self.state;
|
||||
// Signal our thread that we've been dropped and wake it up if it's
|
||||
// blocked.
|
||||
let mut g = lock.lock().unwrap();
|
||||
*g = true;
|
||||
cvar.notify_one();
|
||||
drop(g);
|
||||
|
||||
// ... and then wait for the thread to exit to ensure we clean up
|
||||
// after ourselves.
|
||||
thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
//! Dummy implementations of things that a Wasm module can import.
|
||||
|
||||
use wasmtime::{
|
||||
Extern, ExternType, Func, FuncType, Global, GlobalType, ImportType, Memory, MemoryType, Store,
|
||||
Table, TableType, Trap, Val, ValType,
|
||||
};
|
||||
use std::fmt::Write;
|
||||
use wasmtime::*;
|
||||
|
||||
/// Create a set of dummy functions/globals/etc for the given imports.
|
||||
pub fn dummy_imports<'module>(
|
||||
store: &Store,
|
||||
import_tys: impl Iterator<Item = ImportType<'module>>,
|
||||
) -> Result<Vec<Extern>, Trap> {
|
||||
) -> Vec<Extern> {
|
||||
import_tys
|
||||
.map(|imp| {
|
||||
Ok(match imp.ty() {
|
||||
ExternType::Func(func_ty) => Extern::Func(dummy_func(&store, func_ty)),
|
||||
ExternType::Global(global_ty) => Extern::Global(dummy_global(&store, global_ty)?),
|
||||
ExternType::Table(table_ty) => Extern::Table(dummy_table(&store, table_ty)?),
|
||||
ExternType::Memory(mem_ty) => Extern::Memory(dummy_memory(&store, mem_ty)),
|
||||
|
||||
// FIXME(#2094)
|
||||
ExternType::Instance(_) => unimplemented!(),
|
||||
ExternType::Module(_) => unimplemented!(),
|
||||
})
|
||||
.map(|imp| match imp.ty() {
|
||||
ExternType::Func(func_ty) => Extern::Func(dummy_func(&store, func_ty)),
|
||||
ExternType::Global(global_ty) => Extern::Global(dummy_global(&store, global_ty)),
|
||||
ExternType::Table(table_ty) => Extern::Table(dummy_table(&store, table_ty)),
|
||||
ExternType::Memory(mem_ty) => Extern::Memory(dummy_memory(&store, mem_ty)),
|
||||
ExternType::Instance(instance_ty) => {
|
||||
Extern::Instance(dummy_instance(&store, instance_ty))
|
||||
}
|
||||
ExternType::Module(module_ty) => Extern::Module(dummy_module(&store, module_ty)),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -30,55 +26,326 @@ pub fn dummy_imports<'module>(
|
||||
pub fn dummy_func(store: &Store, ty: FuncType) -> Func {
|
||||
Func::new(store, ty.clone(), move |_, _, results| {
|
||||
for (ret_ty, result) in ty.results().zip(results) {
|
||||
*result = dummy_value(ret_ty)?;
|
||||
*result = dummy_value(ret_ty);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a dummy value for the given value type.
|
||||
pub fn dummy_value(val_ty: ValType) -> Result<Val, Trap> {
|
||||
Ok(match val_ty {
|
||||
pub fn dummy_value(val_ty: ValType) -> Val {
|
||||
match val_ty {
|
||||
ValType::I32 => Val::I32(0),
|
||||
ValType::I64 => Val::I64(0),
|
||||
ValType::F32 => Val::F32(0),
|
||||
ValType::F64 => Val::F64(0),
|
||||
ValType::V128 => {
|
||||
return Err(Trap::new(
|
||||
"dummy_value: unsupported function return type: v128".to_string(),
|
||||
))
|
||||
}
|
||||
ValType::ExternRef => {
|
||||
return Err(Trap::new(
|
||||
"dummy_value: unsupported function return type: externref".to_string(),
|
||||
))
|
||||
}
|
||||
ValType::FuncRef => {
|
||||
return Err(Trap::new(
|
||||
"dummy_value: unsupported function return type: funcref".to_string(),
|
||||
))
|
||||
}
|
||||
})
|
||||
ValType::V128 => Val::V128(0),
|
||||
ValType::ExternRef => Val::ExternRef(None),
|
||||
ValType::FuncRef => Val::FuncRef(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a sequence of dummy values for the given types.
|
||||
pub fn dummy_values(val_tys: impl IntoIterator<Item = ValType>) -> Result<Vec<Val>, Trap> {
|
||||
pub fn dummy_values(val_tys: impl IntoIterator<Item = ValType>) -> Vec<Val> {
|
||||
val_tys.into_iter().map(dummy_value).collect()
|
||||
}
|
||||
|
||||
/// Construct a dummy global for the given global type.
|
||||
pub fn dummy_global(store: &Store, ty: GlobalType) -> Result<Global, Trap> {
|
||||
let val = dummy_value(ty.content().clone())?;
|
||||
Ok(Global::new(store, ty, val).unwrap())
|
||||
pub fn dummy_global(store: &Store, ty: GlobalType) -> Global {
|
||||
let val = dummy_value(ty.content().clone());
|
||||
Global::new(store, ty, val).unwrap()
|
||||
}
|
||||
|
||||
/// Construct a dummy table for the given table type.
|
||||
pub fn dummy_table(store: &Store, ty: TableType) -> Result<Table, Trap> {
|
||||
let init_val = dummy_value(ty.element().clone())?;
|
||||
Ok(Table::new(store, ty, init_val).unwrap())
|
||||
pub fn dummy_table(store: &Store, ty: TableType) -> Table {
|
||||
let init_val = dummy_value(ty.element().clone());
|
||||
Table::new(store, ty, init_val).unwrap()
|
||||
}
|
||||
|
||||
/// Construct a dummy memory for the given memory type.
|
||||
pub fn dummy_memory(store: &Store, ty: MemoryType) -> Memory {
|
||||
Memory::new(store, ty)
|
||||
}
|
||||
|
||||
/// Construct a dummy instance for the given instance type.
|
||||
///
|
||||
/// This is done by using the expected type to generate a module on-the-fly
|
||||
/// which we the instantiate.
|
||||
pub fn dummy_instance(store: &Store, ty: InstanceType) -> Instance {
|
||||
let mut wat = WatGenerator::new();
|
||||
for ty in ty.exports() {
|
||||
wat.export(&ty);
|
||||
}
|
||||
let module = Module::new(store.engine(), &wat.finish()).unwrap();
|
||||
Instance::new(store, &module, &[]).unwrap()
|
||||
}
|
||||
|
||||
/// Construct a dummy module for the given module type.
|
||||
///
|
||||
/// This is done by using the expected type to generate a module on-the-fly.
|
||||
pub fn dummy_module(store: &Store, ty: ModuleType) -> Module {
|
||||
let mut wat = WatGenerator::new();
|
||||
for ty in ty.imports() {
|
||||
wat.import(&ty);
|
||||
}
|
||||
for ty in ty.exports() {
|
||||
wat.export(&ty);
|
||||
}
|
||||
Module::new(store.engine(), &wat.finish()).unwrap()
|
||||
}
|
||||
|
||||
struct WatGenerator {
|
||||
tmp: usize,
|
||||
dst: String,
|
||||
}
|
||||
|
||||
impl WatGenerator {
|
||||
fn new() -> WatGenerator {
|
||||
WatGenerator {
|
||||
tmp: 0,
|
||||
dst: String::from("(module\n"),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(mut self) -> String {
|
||||
self.dst.push_str(")\n");
|
||||
self.dst
|
||||
}
|
||||
|
||||
fn import(&mut self, ty: &ImportType<'_>) {
|
||||
write!(self.dst, "(import ").unwrap();
|
||||
self.str(ty.module());
|
||||
write!(self.dst, " ").unwrap();
|
||||
if let Some(field) = ty.name() {
|
||||
self.str(field);
|
||||
write!(self.dst, " ").unwrap();
|
||||
}
|
||||
self.item_ty(&ty.ty());
|
||||
writeln!(self.dst, ")").unwrap();
|
||||
}
|
||||
|
||||
fn item_ty(&mut self, ty: &ExternType) {
|
||||
match ty {
|
||||
ExternType::Memory(mem) => {
|
||||
write!(
|
||||
self.dst,
|
||||
"(memory {} {})",
|
||||
mem.limits().min(),
|
||||
match mem.limits().max() {
|
||||
Some(max) => max.to_string(),
|
||||
None => String::new(),
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
ExternType::Table(table) => {
|
||||
write!(
|
||||
self.dst,
|
||||
"(table {} {} {})",
|
||||
table.limits().min(),
|
||||
match table.limits().max() {
|
||||
Some(max) => max.to_string(),
|
||||
None => String::new(),
|
||||
},
|
||||
wat_ty(table.element()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
ExternType::Global(ty) => {
|
||||
if ty.mutability() == Mutability::Const {
|
||||
write!(self.dst, "(global {})", wat_ty(ty.content())).unwrap();
|
||||
} else {
|
||||
write!(self.dst, "(global (mut {}))", wat_ty(ty.content())).unwrap();
|
||||
}
|
||||
}
|
||||
ExternType::Func(ty) => {
|
||||
write!(self.dst, "(func ").unwrap();
|
||||
self.func_sig(ty);
|
||||
write!(self.dst, ")").unwrap();
|
||||
}
|
||||
ExternType::Instance(ty) => {
|
||||
writeln!(self.dst, "(instance").unwrap();
|
||||
for ty in ty.exports() {
|
||||
write!(self.dst, "(export ").unwrap();
|
||||
self.str(ty.name());
|
||||
write!(self.dst, " ").unwrap();
|
||||
self.item_ty(&ty.ty());
|
||||
writeln!(self.dst, ")").unwrap();
|
||||
}
|
||||
write!(self.dst, ")").unwrap();
|
||||
}
|
||||
ExternType::Module(ty) => {
|
||||
writeln!(self.dst, "(module").unwrap();
|
||||
for ty in ty.imports() {
|
||||
self.import(&ty);
|
||||
writeln!(self.dst, "").unwrap();
|
||||
}
|
||||
for ty in ty.exports() {
|
||||
write!(self.dst, "(export ").unwrap();
|
||||
self.str(ty.name());
|
||||
write!(self.dst, " ").unwrap();
|
||||
self.item_ty(&ty.ty());
|
||||
writeln!(self.dst, ")").unwrap();
|
||||
}
|
||||
write!(self.dst, ")").unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn export(&mut self, ty: &ExportType<'_>) {
|
||||
let wat_name = format!("item{}", self.tmp);
|
||||
self.tmp += 1;
|
||||
let item_ty = ty.ty();
|
||||
self.item(&wat_name, &item_ty);
|
||||
|
||||
write!(self.dst, "(export ").unwrap();
|
||||
self.str(ty.name());
|
||||
write!(self.dst, " (").unwrap();
|
||||
match item_ty {
|
||||
ExternType::Memory(_) => write!(self.dst, "memory").unwrap(),
|
||||
ExternType::Global(_) => write!(self.dst, "global").unwrap(),
|
||||
ExternType::Func(_) => write!(self.dst, "func").unwrap(),
|
||||
ExternType::Instance(_) => write!(self.dst, "instance").unwrap(),
|
||||
ExternType::Table(_) => write!(self.dst, "table").unwrap(),
|
||||
ExternType::Module(_) => write!(self.dst, "module").unwrap(),
|
||||
}
|
||||
writeln!(self.dst, " ${}))", wat_name).unwrap();
|
||||
}
|
||||
|
||||
fn item(&mut self, name: &str, ty: &ExternType) {
|
||||
match ty {
|
||||
ExternType::Memory(mem) => {
|
||||
write!(
|
||||
self.dst,
|
||||
"(memory ${} {} {})\n",
|
||||
name,
|
||||
mem.limits().min(),
|
||||
match mem.limits().max() {
|
||||
Some(max) => max.to_string(),
|
||||
None => String::new(),
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
ExternType::Table(table) => {
|
||||
write!(
|
||||
self.dst,
|
||||
"(table ${} {} {} {})\n",
|
||||
name,
|
||||
table.limits().min(),
|
||||
match table.limits().max() {
|
||||
Some(max) => max.to_string(),
|
||||
None => String::new(),
|
||||
},
|
||||
wat_ty(table.element()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
ExternType::Global(ty) => {
|
||||
write!(self.dst, "(global ${} ", name).unwrap();
|
||||
if ty.mutability() == Mutability::Var {
|
||||
write!(self.dst, "(mut ").unwrap();
|
||||
}
|
||||
write!(self.dst, "{}", wat_ty(ty.content())).unwrap();
|
||||
if ty.mutability() == Mutability::Var {
|
||||
write!(self.dst, ")").unwrap();
|
||||
}
|
||||
write!(self.dst, " (").unwrap();
|
||||
self.value(ty.content());
|
||||
writeln!(self.dst, "))").unwrap();
|
||||
}
|
||||
ExternType::Func(ty) => {
|
||||
write!(self.dst, "(func ${} ", name).unwrap();
|
||||
self.func_sig(ty);
|
||||
for ty in ty.results() {
|
||||
writeln!(self.dst, "").unwrap();
|
||||
self.value(&ty);
|
||||
}
|
||||
writeln!(self.dst, ")").unwrap();
|
||||
}
|
||||
ExternType::Module(ty) => {
|
||||
writeln!(self.dst, "(module ${}", name).unwrap();
|
||||
for ty in ty.imports() {
|
||||
self.import(&ty);
|
||||
}
|
||||
for ty in ty.exports() {
|
||||
self.export(&ty);
|
||||
}
|
||||
self.dst.push_str(")\n");
|
||||
}
|
||||
ExternType::Instance(ty) => {
|
||||
writeln!(self.dst, "(module ${}_module", name).unwrap();
|
||||
for ty in ty.exports() {
|
||||
self.export(&ty);
|
||||
}
|
||||
self.dst.push_str(")\n");
|
||||
writeln!(self.dst, "(instance ${} (instantiate ${0}_module))", name).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn func_sig(&mut self, ty: &FuncType) {
|
||||
write!(self.dst, "(param ").unwrap();
|
||||
for ty in ty.params() {
|
||||
write!(self.dst, "{} ", wat_ty(&ty)).unwrap();
|
||||
}
|
||||
write!(self.dst, ") (result ").unwrap();
|
||||
for ty in ty.results() {
|
||||
write!(self.dst, "{} ", wat_ty(&ty)).unwrap();
|
||||
}
|
||||
write!(self.dst, ")").unwrap();
|
||||
}
|
||||
|
||||
fn value(&mut self, ty: &ValType) {
|
||||
match ty {
|
||||
ValType::I32 => write!(self.dst, "i32.const 0").unwrap(),
|
||||
ValType::I64 => write!(self.dst, "i64.const 0").unwrap(),
|
||||
ValType::F32 => write!(self.dst, "f32.const 0").unwrap(),
|
||||
ValType::F64 => write!(self.dst, "f64.const 0").unwrap(),
|
||||
ValType::V128 => write!(self.dst, "v128.const i32x4 0 0 0 0").unwrap(),
|
||||
ValType::ExternRef => write!(self.dst, "ref.null extern").unwrap(),
|
||||
ValType::FuncRef => write!(self.dst, "ref.null func").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn str(&mut self, name: &str) {
|
||||
let mut bytes = [0; 4];
|
||||
self.dst.push_str("\"");
|
||||
for c in name.chars() {
|
||||
let v = c as u32;
|
||||
if v >= 0x20 && v < 0x7f && c != '"' && c != '\\' && v < 0xff {
|
||||
self.dst.push(c);
|
||||
} else {
|
||||
for byte in c.encode_utf8(&mut bytes).as_bytes() {
|
||||
self.hex_byte(*byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dst.push_str("\"");
|
||||
}
|
||||
|
||||
fn hex_byte(&mut self, byte: u8) {
|
||||
fn to_hex(b: u8) -> char {
|
||||
if b < 10 {
|
||||
(b'0' + b) as char
|
||||
} else {
|
||||
(b'a' + b - 10) as char
|
||||
}
|
||||
}
|
||||
self.dst.push('\\');
|
||||
self.dst.push(to_hex((byte >> 4) & 0xf));
|
||||
self.dst.push(to_hex(byte & 0xf));
|
||||
}
|
||||
}
|
||||
|
||||
fn wat_ty(ty: &ValType) -> &'static str {
|
||||
match ty {
|
||||
ValType::I32 => "i32",
|
||||
ValType::I64 => "i64",
|
||||
ValType::F32 => "f32",
|
||||
ValType::F64 => "f64",
|
||||
ValType::V128 => "v128",
|
||||
ValType::ExternRef => "externref",
|
||||
ValType::FuncRef => "funcref",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ 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.68.0"
|
||||
wasmparser = "0.70"
|
||||
more-asserts = "0.2.1"
|
||||
anyhow = "1.0"
|
||||
cfg-if = "1.0"
|
||||
@@ -36,6 +36,7 @@ log = "0.4"
|
||||
gimli = { version = "0.23.0", default-features = false, features = ["write"] }
|
||||
object = { version = "0.22.0", default-features = false, features = ["write"] }
|
||||
serde = { version = "1.0.94", features = ["derive"] }
|
||||
addr2line = { version = "0.14", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version = "0.3.8", features = ["winnt", "impl-default"] }
|
||||
|
||||
@@ -14,7 +14,7 @@ use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
|
||||
use wasmtime_environ::wasm::{DefinedMemoryIndex, MemoryIndex};
|
||||
use wasmtime_environ::{
|
||||
CompiledFunctions, Compiler as EnvCompiler, DebugInfoData, Module, ModuleMemoryOffset,
|
||||
ModuleTranslation, Tunables, VMOffsets,
|
||||
ModuleTranslation, Tunables, TypeTables, VMOffsets,
|
||||
};
|
||||
|
||||
/// Select which kind of compilation to use.
|
||||
@@ -127,19 +127,26 @@ impl Compiler {
|
||||
pub fn compile<'data>(
|
||||
&self,
|
||||
translation: &mut ModuleTranslation,
|
||||
types: &TypeTables,
|
||||
) -> Result<Compilation, SetupError> {
|
||||
let functions = mem::take(&mut translation.function_body_inputs);
|
||||
let functions = functions.into_iter().collect::<Vec<_>>();
|
||||
let funcs = maybe_parallel!(functions.(into_iter | into_par_iter))
|
||||
.map(|(index, func)| {
|
||||
self.compiler
|
||||
.compile_function(translation, index, func, &*self.isa, &self.tunables)
|
||||
self.compiler.compile_function(
|
||||
translation,
|
||||
index,
|
||||
func,
|
||||
&*self.isa,
|
||||
&self.tunables,
|
||||
types,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.collect::<CompiledFunctions>();
|
||||
|
||||
let dwarf_sections = if self.tunables.debug_info && !funcs.is_empty() {
|
||||
let dwarf_sections = if self.tunables.generate_native_debuginfo && !funcs.is_empty() {
|
||||
transform_dwarf_data(
|
||||
&*self.isa,
|
||||
&translation.module,
|
||||
@@ -150,7 +157,8 @@ impl Compiler {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let (obj, unwind_info) = build_object(&*self.isa, &translation, &funcs, dwarf_sections)?;
|
||||
let (obj, unwind_info) =
|
||||
build_object(&*self.isa, &translation, types, &funcs, dwarf_sections)?;
|
||||
|
||||
Ok(Compilation {
|
||||
obj,
|
||||
|
||||
@@ -12,15 +12,19 @@ use object::File as ObjectFile;
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::any::Any;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use wasmtime_debug::create_gdbjit_image;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::isa::TargetIsa;
|
||||
use wasmtime_environ::wasm::{DefinedFuncIndex, SignatureIndex};
|
||||
use wasmtime_environ::wasm::{
|
||||
DefinedFuncIndex, InstanceTypeIndex, ModuleTypeIndex, SignatureIndex, WasmFuncType,
|
||||
};
|
||||
use wasmtime_environ::{
|
||||
CompileError, DataInitializer, DataInitializerLocation, FunctionAddressMap, Module,
|
||||
ModuleEnvironment, ModuleTranslation, StackMapInformation, TrapInformation,
|
||||
CompileError, DataInitializer, DataInitializerLocation, DebugInfoData, FunctionAddressMap,
|
||||
InstanceSignature, Module, ModuleEnvironment, ModuleSignature, ModuleTranslation,
|
||||
StackMapInformation, TrapInformation,
|
||||
};
|
||||
use wasmtime_profiling::ProfilingAgent;
|
||||
use wasmtime_runtime::{
|
||||
@@ -69,8 +73,31 @@ pub struct CompilationArtifacts {
|
||||
/// Descriptions of compiled functions
|
||||
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
||||
|
||||
/// Debug info presence flags.
|
||||
debug_info: bool,
|
||||
/// Whether or not native debug information is available in `obj`
|
||||
native_debug_info_present: bool,
|
||||
|
||||
/// Whether or not the original wasm module contained debug information that
|
||||
/// we skipped and did not parse.
|
||||
has_unparsed_debuginfo: bool,
|
||||
|
||||
/// Debug information found in the wasm file, used for symbolicating
|
||||
/// backtraces.
|
||||
debug_info: Option<DebugInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DebugInfo {
|
||||
data: Box<[u8]>,
|
||||
code_section_offset: u64,
|
||||
debug_abbrev: Range<usize>,
|
||||
debug_addr: Range<usize>,
|
||||
debug_info: Range<usize>,
|
||||
debug_line: Range<usize>,
|
||||
debug_line_str: Range<usize>,
|
||||
debug_ranges: Range<usize>,
|
||||
debug_rnglists: Range<usize>,
|
||||
debug_str: Range<usize>,
|
||||
debug_str_offsets: Range<usize>,
|
||||
}
|
||||
|
||||
impl CompilationArtifacts {
|
||||
@@ -78,8 +105,8 @@ impl CompilationArtifacts {
|
||||
pub fn build(
|
||||
compiler: &Compiler,
|
||||
data: &[u8],
|
||||
) -> Result<Vec<CompilationArtifacts>, SetupError> {
|
||||
let translations = ModuleEnvironment::new(
|
||||
) -> Result<(Vec<CompilationArtifacts>, TypeTables), SetupError> {
|
||||
let (translations, types) = ModuleEnvironment::new(
|
||||
compiler.frontend_config(),
|
||||
compiler.tunables(),
|
||||
compiler.features(),
|
||||
@@ -87,17 +114,19 @@ impl CompilationArtifacts {
|
||||
.translate(data)
|
||||
.map_err(|error| SetupError::Compile(CompileError::Wasm(error)))?;
|
||||
|
||||
maybe_parallel!(translations.(into_iter | into_par_iter))
|
||||
let list = maybe_parallel!(translations.(into_iter | into_par_iter))
|
||||
.map(|mut translation| {
|
||||
let Compilation {
|
||||
obj,
|
||||
unwind_info,
|
||||
funcs,
|
||||
} = compiler.compile(&mut translation)?;
|
||||
} = compiler.compile(&mut translation, &types)?;
|
||||
|
||||
let ModuleTranslation {
|
||||
module,
|
||||
data_initializers,
|
||||
debuginfo,
|
||||
has_unparsed_debuginfo,
|
||||
..
|
||||
} = translation;
|
||||
|
||||
@@ -126,14 +155,30 @@ impl CompilationArtifacts {
|
||||
address_map: func.address_map,
|
||||
})
|
||||
.collect(),
|
||||
debug_info: compiler.tunables().debug_info,
|
||||
native_debug_info_present: compiler.tunables().generate_native_debuginfo,
|
||||
debug_info: if compiler.tunables().parse_wasm_debuginfo {
|
||||
Some(debuginfo.into())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
has_unparsed_debuginfo,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, SetupError>>()
|
||||
.collect::<Result<Vec<_>, SetupError>>()?;
|
||||
Ok((
|
||||
list,
|
||||
TypeTables {
|
||||
wasm_signatures: types.wasm_signatures,
|
||||
module_signatures: types.module_signatures,
|
||||
instance_signatures: types.instance_signatures,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct FinishedFunctions(PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>);
|
||||
unsafe impl Send for FinishedFunctions {}
|
||||
unsafe impl Sync for FinishedFunctions {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct FunctionInfo {
|
||||
@@ -142,8 +187,15 @@ struct FunctionInfo {
|
||||
stack_maps: Vec<StackMapInformation>,
|
||||
}
|
||||
|
||||
unsafe impl Send for FinishedFunctions {}
|
||||
unsafe impl Sync for FinishedFunctions {}
|
||||
/// This is intended to mirror the type tables in `wasmtime_environ`, except that
|
||||
/// it doesn't store the native signatures which are no longer needed past compilation.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct TypeTables {
|
||||
pub wasm_signatures: PrimaryMap<SignatureIndex, WasmFuncType>,
|
||||
pub module_signatures: PrimaryMap<ModuleTypeIndex, ModuleSignature>,
|
||||
pub instance_signatures: PrimaryMap<InstanceTypeIndex, InstanceSignature>,
|
||||
}
|
||||
|
||||
/// Container for data needed for an Instance function to exist.
|
||||
pub struct ModuleCode {
|
||||
@@ -196,7 +248,7 @@ impl CompiledModule {
|
||||
})?;
|
||||
|
||||
// Register GDB JIT images; initialize profiler and load the wasm module.
|
||||
let dbg_jit_registration = if artifacts.debug_info {
|
||||
let dbg_jit_registration = if artifacts.native_debug_info_present {
|
||||
let bytes = create_dbg_image(
|
||||
artifacts.obj.to_vec(),
|
||||
code_range,
|
||||
@@ -336,6 +388,85 @@ impl CompiledModule {
|
||||
pub fn code(&self) -> &Arc<ModuleCode> {
|
||||
&self.code
|
||||
}
|
||||
|
||||
/// Creates a new symbolication context which can be used to further
|
||||
/// symbolicate stack traces.
|
||||
///
|
||||
/// Basically this makes a thing which parses debuginfo and can tell you
|
||||
/// what filename and line number a wasm pc comes from.
|
||||
pub fn symbolize_context(&self) -> Result<Option<SymbolizeContext>, gimli::Error> {
|
||||
use gimli::EndianSlice;
|
||||
let info = match &self.artifacts.debug_info {
|
||||
Some(info) => info,
|
||||
None => return Ok(None),
|
||||
};
|
||||
// For now we clone the data into the `SymbolizeContext`, but if this
|
||||
// becomes prohibitive we could always `Arc` it with our own allocation
|
||||
// here.
|
||||
let data = info.data.clone();
|
||||
let endian = gimli::LittleEndian;
|
||||
let cx = addr2line::Context::from_sections(
|
||||
EndianSlice::new(&data[info.debug_abbrev.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_addr.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_info.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_line.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_line_str.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_ranges.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_rnglists.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_str.clone()], endian).into(),
|
||||
EndianSlice::new(&data[info.debug_str_offsets.clone()], endian).into(),
|
||||
EndianSlice::new(&[], endian),
|
||||
)?;
|
||||
Ok(Some(SymbolizeContext {
|
||||
// See comments on `SymbolizeContext` for why we do this static
|
||||
// lifetime promotion.
|
||||
inner: unsafe {
|
||||
std::mem::transmute::<Addr2LineContext<'_>, Addr2LineContext<'static>>(cx)
|
||||
},
|
||||
code_section_offset: info.code_section_offset,
|
||||
_data: data,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns whether the original wasm module had unparsed debug information
|
||||
/// based on the tunables configuration.
|
||||
pub fn has_unparsed_debuginfo(&self) -> bool {
|
||||
self.artifacts.has_unparsed_debuginfo
|
||||
}
|
||||
}
|
||||
|
||||
type Addr2LineContext<'a> = addr2line::Context<gimli::EndianSlice<'a, gimli::LittleEndian>>;
|
||||
|
||||
/// A context which contains dwarf debug information to translate program
|
||||
/// counters back to filenames and line numbers.
|
||||
pub struct SymbolizeContext {
|
||||
// Note the `'static` lifetime on `inner`. That's actually a bunch of slices
|
||||
// which point back into the `_data` field. We currently unsafely manage
|
||||
// this by saying that when inside the struct it's `'static` (since we own
|
||||
// the referenced data just next to it) and we only loan out borrowed
|
||||
// references.
|
||||
_data: Box<[u8]>,
|
||||
inner: Addr2LineContext<'static>,
|
||||
code_section_offset: u64,
|
||||
}
|
||||
|
||||
impl SymbolizeContext {
|
||||
/// Returns access to the [`addr2line::Context`] which can be used to query
|
||||
/// frame information with.
|
||||
pub fn addr2line(&self) -> &Addr2LineContext<'_> {
|
||||
// Here we demote our synthetic `'static` lifetime which doesn't
|
||||
// actually exist back to a lifetime that's tied to `&self`, which
|
||||
// should be safe.
|
||||
unsafe {
|
||||
std::mem::transmute::<&Addr2LineContext<'static>, &Addr2LineContext<'_>>(&self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the offset of the code section in the original wasm file, used
|
||||
/// to calculate lookup values into the DWARF.
|
||||
pub fn code_section_offset(&self) -> u64 {
|
||||
self.code_section_offset
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to `DataInitializer`, but owns its own copy of the data rather
|
||||
@@ -420,3 +551,37 @@ fn build_code_memory(
|
||||
|
||||
Ok((code_memory, code_range, finished_functions, trampolines))
|
||||
}
|
||||
|
||||
impl From<DebugInfoData<'_>> for DebugInfo {
|
||||
fn from(raw: DebugInfoData<'_>) -> DebugInfo {
|
||||
use gimli::Section;
|
||||
|
||||
let mut data = Vec::new();
|
||||
let mut push = |section: &[u8]| {
|
||||
data.extend_from_slice(section);
|
||||
data.len() - section.len()..data.len()
|
||||
};
|
||||
let debug_abbrev = push(raw.dwarf.debug_abbrev.reader().slice());
|
||||
let debug_addr = push(raw.dwarf.debug_addr.reader().slice());
|
||||
let debug_info = push(raw.dwarf.debug_info.reader().slice());
|
||||
let debug_line = push(raw.dwarf.debug_line.reader().slice());
|
||||
let debug_line_str = push(raw.dwarf.debug_line_str.reader().slice());
|
||||
let debug_ranges = push(raw.debug_ranges.reader().slice());
|
||||
let debug_rnglists = push(raw.debug_rnglists.reader().slice());
|
||||
let debug_str = push(raw.dwarf.debug_str.reader().slice());
|
||||
let debug_str_offsets = push(raw.dwarf.debug_str_offsets.reader().slice());
|
||||
DebugInfo {
|
||||
data: data.into(),
|
||||
debug_abbrev,
|
||||
debug_addr,
|
||||
debug_info,
|
||||
debug_line,
|
||||
debug_line_str,
|
||||
debug_ranges,
|
||||
debug_rnglists,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
code_section_offset: raw.wasm_file.code_section_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ pub mod trampoline;
|
||||
|
||||
pub use crate::code_memory::CodeMemory;
|
||||
pub use crate::compiler::{Compilation, CompilationStrategy, Compiler};
|
||||
pub use crate::instantiate::{CompilationArtifacts, CompiledModule, ModuleCode, SetupError};
|
||||
pub use crate::instantiate::{
|
||||
CompilationArtifacts, CompiledModule, ModuleCode, SetupError, SymbolizeContext, TypeTables,
|
||||
};
|
||||
pub use crate::link::link_module;
|
||||
|
||||
/// Version number of this crate.
|
||||
|
||||
@@ -8,7 +8,7 @@ use wasmtime_debug::DwarfSection;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
|
||||
use wasmtime_environ::wasm::{FuncIndex, SignatureIndex};
|
||||
use wasmtime_environ::{CompiledFunctions, ModuleTranslation};
|
||||
use wasmtime_environ::{CompiledFunctions, ModuleTranslation, TypeTables};
|
||||
use wasmtime_obj::{ObjectBuilder, ObjectBuilderTarget};
|
||||
|
||||
pub use wasmtime_obj::utils;
|
||||
@@ -24,6 +24,7 @@ pub enum ObjectUnwindInfo {
|
||||
pub(crate) fn build_object(
|
||||
isa: &dyn TargetIsa,
|
||||
translation: &ModuleTranslation,
|
||||
types: &TypeTables,
|
||||
funcs: &CompiledFunctions,
|
||||
dwarf_sections: Vec<DwarfSection>,
|
||||
) -> Result<(Object, Vec<ObjectUnwindInfo>), anyhow::Error> {
|
||||
@@ -38,10 +39,16 @@ pub(crate) fn build_object(
|
||||
.map(|info| ObjectUnwindInfo::Func(translation.module.func_index(index), info.clone()))
|
||||
}));
|
||||
|
||||
let mut trampolines = PrimaryMap::with_capacity(translation.module.signatures.len());
|
||||
let mut trampolines = PrimaryMap::with_capacity(types.native_signatures.len());
|
||||
let mut cx = FunctionBuilderContext::new();
|
||||
// Build trampolines for every signature.
|
||||
for (i, native_sig) in translation.native_signatures.iter() {
|
||||
//
|
||||
// TODO: for the module linking proposal this builds too many native
|
||||
// signatures. This builds trampolines for all signatures for all modules
|
||||
// for each module. That's a lot of trampolines! We should instead figure
|
||||
// out a way to share trampolines amongst all modules when compiling
|
||||
// module-linking modules.
|
||||
for (i, native_sig) in types.native_signatures.iter() {
|
||||
let func = build_trampoline(isa, &mut cx, native_sig, std::mem::size_of::<u128>())?;
|
||||
// Preserve trampoline function unwind info.
|
||||
if let Some(info) = &func.unwind_info {
|
||||
|
||||
@@ -24,7 +24,7 @@ more-asserts = "0.2.1"
|
||||
smallvec = "1.0.0"
|
||||
thiserror = "1.0.9"
|
||||
typemap = "0.3"
|
||||
wasmparser = "0.68.0"
|
||||
wasmparser = "0.70"
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.2"
|
||||
|
||||
@@ -13,6 +13,6 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
lightbeam = { path = "..", version = "0.21.0" }
|
||||
wasmparser = "0.68"
|
||||
wasmparser = "0.70"
|
||||
cranelift-codegen = { path = "../../../cranelift/codegen", version = "0.68.0" }
|
||||
wasmtime-environ = { path = "../../environ", version = "0.21.0" }
|
||||
|
||||
@@ -9,12 +9,12 @@ use cranelift_codegen::isa;
|
||||
use lightbeam::{CodeGenSession, NullOffsetSink, Sinks};
|
||||
use wasmtime_environ::wasm::{
|
||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex,
|
||||
GlobalIndex, MemoryIndex, SignatureIndex, TableIndex,
|
||||
GlobalIndex, MemoryIndex, SignatureIndex, TableIndex, TypeIndex,
|
||||
};
|
||||
use wasmtime_environ::{
|
||||
entity::PrimaryMap, BuiltinFunctionIndex, CompileError, CompiledFunction, Compiler,
|
||||
FunctionBodyData, Module, ModuleTranslation, Relocation, RelocationTarget, TrapInformation,
|
||||
Tunables, VMOffsets,
|
||||
Tunables, TypeTables, VMOffsets,
|
||||
};
|
||||
|
||||
/// A compiler that compiles a WebAssembly module with Lightbeam, directly translating the Wasm file.
|
||||
@@ -28,13 +28,14 @@ impl Compiler for Lightbeam {
|
||||
function_body: FunctionBodyData<'_>,
|
||||
isa: &dyn isa::TargetIsa,
|
||||
tunables: &Tunables,
|
||||
types: &TypeTables,
|
||||
) -> Result<CompiledFunction, CompileError> {
|
||||
if tunables.debug_info {
|
||||
if tunables.generate_native_debuginfo {
|
||||
return Err(CompileError::DebugInfoNotSupported);
|
||||
}
|
||||
let func_index = translation.module.func_index(i);
|
||||
|
||||
let env = FuncEnvironment::new(isa.frontend_config().pointer_bytes(), translation);
|
||||
let env = FuncEnvironment::new(isa.frontend_config().pointer_bytes(), translation, types);
|
||||
let mut codegen_session: CodeGenSession<_> = CodeGenSession::new(
|
||||
translation.function_body_inputs.len() as u32,
|
||||
&env,
|
||||
@@ -180,11 +181,15 @@ struct FuncEnvironment<'module_environment> {
|
||||
}
|
||||
|
||||
impl<'module_environment> FuncEnvironment<'module_environment> {
|
||||
fn new(pointer_bytes: u8, translation: &'module_environment ModuleTranslation<'_>) -> Self {
|
||||
fn new(
|
||||
pointer_bytes: u8,
|
||||
translation: &'module_environment ModuleTranslation<'_>,
|
||||
types: &'module_environment TypeTables,
|
||||
) -> Self {
|
||||
Self {
|
||||
module: &translation.module,
|
||||
offsets: VMOffsets::new(pointer_bytes, &translation.module),
|
||||
native_signatures: &translation.native_signatures,
|
||||
native_signatures: &types.native_signatures,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +327,7 @@ impl lightbeam::ModuleContext for FuncEnvironment<'_> {
|
||||
}
|
||||
fn vmctx_vmshared_signature_id(&self, signature_idx: u32) -> u32 {
|
||||
self.offsets
|
||||
.vmctx_vmshared_signature_id(SignatureIndex::from_u32(signature_idx))
|
||||
.vmctx_vmshared_signature_id(TypeIndex::from_u32(signature_idx))
|
||||
}
|
||||
|
||||
// TODO: type of a global
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::ptr;
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::isa::TargetFrontendConfig;
|
||||
use wasmtime_environ::wasm::GlobalInit;
|
||||
use wasmtime_environ::{Module, TargetSharedSignatureIndex, VMOffsets};
|
||||
use wasmtime_environ::{Module, ModuleType, TargetSharedSignatureIndex, VMOffsets};
|
||||
|
||||
pub struct TableRelocation {
|
||||
pub index: usize,
|
||||
@@ -25,16 +25,19 @@ pub fn layout_vmcontext(
|
||||
// Assign unique indices to unique signatures.
|
||||
let mut signature_registry = HashMap::new();
|
||||
let mut signature_registry_len = signature_registry.len();
|
||||
for (index, sig) in module.signatures.iter() {
|
||||
for (index, sig) in module.types.iter() {
|
||||
let offset = ofs.vmctx_vmshared_signature_id(index) as usize;
|
||||
let target_index = match signature_registry.entry(sig) {
|
||||
Entry::Occupied(o) => *o.get(),
|
||||
Entry::Vacant(v) => {
|
||||
assert_le!(signature_registry_len, std::u32::MAX as usize);
|
||||
let id = TargetSharedSignatureIndex::new(signature_registry_len as u32);
|
||||
signature_registry_len += 1;
|
||||
*v.insert(id)
|
||||
}
|
||||
let target_index = match sig {
|
||||
ModuleType::Function(sig) => match signature_registry.entry(sig) {
|
||||
Entry::Occupied(o) => *o.get(),
|
||||
Entry::Vacant(v) => {
|
||||
assert_le!(signature_registry_len, std::u32::MAX as usize);
|
||||
let id = TargetSharedSignatureIndex::new(signature_registry_len as u32);
|
||||
signature_registry_len += 1;
|
||||
*v.insert(id)
|
||||
}
|
||||
},
|
||||
_ => TargetSharedSignatureIndex::new(u32::max_value()),
|
||||
};
|
||||
unsafe {
|
||||
let to = out.as_mut_ptr().add(offset) as *mut TargetSharedSignatureIndex;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::vmcontext::{
|
||||
VMCallerCheckedAnyfunc, VMContext, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
|
||||
};
|
||||
use crate::InstanceHandle;
|
||||
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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Export {
|
||||
pub enum Export<'a> {
|
||||
/// A function export value.
|
||||
Function(ExportFunction),
|
||||
|
||||
@@ -19,6 +20,12 @@ pub enum Export {
|
||||
|
||||
/// A global export value.
|
||||
Global(ExportGlobal),
|
||||
|
||||
/// An instance
|
||||
Instance(&'a InstanceHandle),
|
||||
|
||||
/// A module
|
||||
Module(&'a dyn Any),
|
||||
}
|
||||
|
||||
/// A function export value.
|
||||
@@ -31,8 +38,8 @@ pub struct ExportFunction {
|
||||
pub anyfunc: NonNull<VMCallerCheckedAnyfunc>,
|
||||
}
|
||||
|
||||
impl From<ExportFunction> for Export {
|
||||
fn from(func: ExportFunction) -> Export {
|
||||
impl<'a> From<ExportFunction> for Export<'a> {
|
||||
fn from(func: ExportFunction) -> Export<'a> {
|
||||
Export::Function(func)
|
||||
}
|
||||
}
|
||||
@@ -48,8 +55,8 @@ pub struct ExportTable {
|
||||
pub table: TablePlan,
|
||||
}
|
||||
|
||||
impl From<ExportTable> for Export {
|
||||
fn from(func: ExportTable) -> Export {
|
||||
impl<'a> From<ExportTable> for Export<'a> {
|
||||
fn from(func: ExportTable) -> Export<'a> {
|
||||
Export::Table(func)
|
||||
}
|
||||
}
|
||||
@@ -65,8 +72,8 @@ pub struct ExportMemory {
|
||||
pub memory: MemoryPlan,
|
||||
}
|
||||
|
||||
impl From<ExportMemory> for Export {
|
||||
fn from(func: ExportMemory) -> Export {
|
||||
impl<'a> From<ExportMemory> for Export<'a> {
|
||||
fn from(func: ExportMemory) -> Export<'a> {
|
||||
Export::Memory(func)
|
||||
}
|
||||
}
|
||||
@@ -82,8 +89,8 @@ pub struct ExportGlobal {
|
||||
pub global: Global,
|
||||
}
|
||||
|
||||
impl From<ExportGlobal> for Export {
|
||||
fn from(func: ExportGlobal) -> Export {
|
||||
impl<'a> From<ExportGlobal> for Export<'a> {
|
||||
fn from(func: ExportGlobal) -> Export<'a> {
|
||||
Export::Global(func)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
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.
|
||||
///
|
||||
/// Note that each of these fields are slices, not `PrimaryMap`. They should be
|
||||
/// Note that some of these fields are slices, not `PrimaryMap`. They should be
|
||||
/// stored in index-order as with the module that we're providing the imports
|
||||
/// for, and indexing is all done the same way as the main module's index
|
||||
/// spaces.
|
||||
#[derive(Clone, Default)]
|
||||
///
|
||||
/// Also note that the way we compile modules means that for the module linking
|
||||
/// proposal all `alias` directives should map to imported items. This means
|
||||
/// that each of these items aren't necessarily directly imported, but may be
|
||||
/// aliased.
|
||||
#[derive(Default)]
|
||||
pub struct Imports<'a> {
|
||||
/// Resolved addresses for imported functions.
|
||||
pub functions: &'a [VMFunctionImport],
|
||||
@@ -19,4 +28,15 @@ 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>>,
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ 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, MemoryIndex, SignatureIndex,
|
||||
TableElementType, TableIndex, WasmType,
|
||||
ElemIndex, EntityIndex, FuncIndex, GlobalIndex, GlobalInit, InstanceIndex, MemoryIndex,
|
||||
ModuleIndex, SignatureIndex, TableElementType, TableIndex, WasmType,
|
||||
};
|
||||
use wasmtime_environ::{ir, DataInitializer, Module, TableElements, VMOffsets};
|
||||
use wasmtime_environ::{ir, DataInitializer, Module, ModuleType, TableElements, VMOffsets};
|
||||
|
||||
/// A WebAssembly instance.
|
||||
///
|
||||
@@ -50,6 +50,15 @@ 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.
|
||||
@@ -78,12 +87,6 @@ impl Instance {
|
||||
.cast()
|
||||
}
|
||||
|
||||
/// Return the indexed `VMSharedSignatureIndex`.
|
||||
fn signature_id(&self, index: SignatureIndex) -> VMSharedSignatureIndex {
|
||||
let index = usize::try_from(index.as_u32()).unwrap();
|
||||
unsafe { *self.signature_ids_ptr().add(index) }
|
||||
}
|
||||
|
||||
pub(crate) fn module(&self) -> &Module {
|
||||
&self.module
|
||||
}
|
||||
@@ -274,7 +277,7 @@ impl Instance {
|
||||
}
|
||||
|
||||
/// 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();
|
||||
@@ -323,9 +326,8 @@ impl Instance {
|
||||
}
|
||||
.into(),
|
||||
|
||||
// FIXME(#2094)
|
||||
EntityIndex::Instance(_index) => unimplemented!(),
|
||||
EntityIndex::Module(_index) => unimplemented!(),
|
||||
EntityIndex::Instance(index) => Export::Instance(&self.instances[*index]),
|
||||
EntityIndex::Module(index) => Export::Module(&*self.modules[*index]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,6 +855,8 @@ impl InstanceHandle {
|
||||
passive_elements: Default::default(),
|
||||
passive_data,
|
||||
host_state,
|
||||
instances: imports.instances,
|
||||
modules: imports.modules,
|
||||
vmctx: VMContext {},
|
||||
};
|
||||
let layout = instance.alloc_layout();
|
||||
@@ -868,8 +872,11 @@ impl InstanceHandle {
|
||||
let instance = handle.instance();
|
||||
|
||||
let mut ptr = instance.signature_ids_ptr();
|
||||
for (signature, _) in handle.module().signatures.iter() {
|
||||
*ptr = lookup_shared_signature(signature);
|
||||
for sig in handle.module().types.values() {
|
||||
*ptr = match sig {
|
||||
ModuleType::Function(sig) => lookup_shared_signature(*sig),
|
||||
_ => VMSharedSignatureIndex::new(u32::max_value()),
|
||||
};
|
||||
ptr = ptr.add(1);
|
||||
}
|
||||
|
||||
@@ -924,7 +931,7 @@ impl InstanceHandle {
|
||||
*instance.stack_map_registry() = stack_map_registry;
|
||||
|
||||
for (index, sig) in instance.module.functions.iter() {
|
||||
let type_index = instance.signature_id(*sig);
|
||||
let type_index = lookup_shared_signature(*sig);
|
||||
|
||||
let (func_ptr, vmctx) =
|
||||
if let Some(def_index) = instance.module.defined_func_index(index) {
|
||||
|
||||
@@ -26,15 +26,20 @@ impl<'a> Iterator for ReadDir<'a> {
|
||||
|
||||
fn next(&mut self) -> Option<DirEntry> {
|
||||
unsafe {
|
||||
if self.buf.is_empty() {
|
||||
if self.buf.len() < mem::size_of::<wasi::Dirent>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Read the data
|
||||
let dirent_ptr = self.buf.as_ptr() as *const wasi::Dirent;
|
||||
let dirent = dirent_ptr.read_unaligned();
|
||||
|
||||
if self.buf.len() < mem::size_of::<wasi::Dirent>() + dirent.d_namlen as usize {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name_ptr = dirent_ptr.offset(1) as *const u8;
|
||||
// NOTE Linux syscall returns a NULL-terminated name, but WASI doesn't
|
||||
// NOTE Linux syscall returns a NUL-terminated name, but WASI doesn't
|
||||
let namelen = dirent.d_namlen as usize;
|
||||
let slice = slice::from_raw_parts(name_ptr, namelen);
|
||||
let name = str::from_utf8(slice).expect("invalid utf8").to_owned();
|
||||
@@ -48,21 +53,24 @@ impl<'a> Iterator for ReadDir<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn exec_fd_readdir(fd: wasi::Fd, cookie: wasi::Dircookie) -> Vec<DirEntry> {
|
||||
/// Return the entries plus a bool indicating EOF.
|
||||
unsafe fn exec_fd_readdir(fd: wasi::Fd, cookie: wasi::Dircookie) -> (Vec<DirEntry>, bool) {
|
||||
let mut buf: [u8; BUF_LEN] = [0; BUF_LEN];
|
||||
let bufused =
|
||||
wasi::fd_readdir(fd, buf.as_mut_ptr(), BUF_LEN, cookie).expect("failed fd_readdir");
|
||||
|
||||
let sl = slice::from_raw_parts(buf.as_ptr(), min(BUF_LEN, bufused));
|
||||
let dirs: Vec<_> = ReadDir::from_slice(sl).collect();
|
||||
dirs
|
||||
let eof = bufused < BUF_LEN;
|
||||
(dirs, eof)
|
||||
}
|
||||
|
||||
unsafe fn test_fd_readdir(dir_fd: wasi::Fd) {
|
||||
let stat = wasi::fd_filestat_get(dir_fd).expect("failed filestat");
|
||||
|
||||
// Check the behavior in an empty directory
|
||||
let mut dirs = exec_fd_readdir(dir_fd, 0);
|
||||
let (mut dirs, eof) = exec_fd_readdir(dir_fd, 0);
|
||||
assert!(eof, "expected to read the entire directory");
|
||||
dirs.sort_by_key(|d| d.name.clone());
|
||||
assert_eq!(dirs.len(), 2, "expected two entries in an empty directory");
|
||||
let mut dirs = dirs.into_iter();
|
||||
@@ -105,9 +113,11 @@ unsafe fn test_fd_readdir(dir_fd: wasi::Fd) {
|
||||
);
|
||||
|
||||
let stat = wasi::fd_filestat_get(file_fd).expect("failed filestat");
|
||||
wasi::fd_close(file_fd).expect("closing a file");
|
||||
|
||||
// Execute another readdir
|
||||
let mut dirs = exec_fd_readdir(dir_fd, 0);
|
||||
let (mut dirs, eof) = exec_fd_readdir(dir_fd, 0);
|
||||
assert!(eof, "expected to read the entire directory");
|
||||
assert_eq!(dirs.len(), 3, "expected three entries");
|
||||
// Save the data about the last entry. We need to do it before sorting.
|
||||
let lastfile_cookie = dirs[1].dirent.d_next;
|
||||
@@ -130,9 +140,54 @@ unsafe fn test_fd_readdir(dir_fd: wasi::Fd) {
|
||||
assert_eq!(dir.dirent.d_ino, stat.ino);
|
||||
|
||||
// check if cookie works as expected
|
||||
let dirs = exec_fd_readdir(dir_fd, lastfile_cookie);
|
||||
let (dirs, eof) = exec_fd_readdir(dir_fd, lastfile_cookie);
|
||||
assert!(eof, "expected to read the entire directory");
|
||||
assert_eq!(dirs.len(), 1, "expected one entry");
|
||||
assert_eq!(dirs[0].name, lastfile_name, "name of the only entry");
|
||||
|
||||
wasi::path_unlink_file(dir_fd, "file").expect("removing a file");
|
||||
}
|
||||
|
||||
unsafe fn test_fd_readdir_lots(dir_fd: wasi::Fd) {
|
||||
// Add a file and check the behavior
|
||||
for count in 0..1000 {
|
||||
let file_fd = wasi::path_open(
|
||||
dir_fd,
|
||||
0,
|
||||
&format!("file.{}", count),
|
||||
wasi::OFLAGS_CREAT,
|
||||
wasi::RIGHTS_FD_READ
|
||||
| wasi::RIGHTS_FD_WRITE
|
||||
| wasi::RIGHTS_FD_READDIR
|
||||
| wasi::RIGHTS_FD_FILESTAT_GET,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
.expect("failed to create file");
|
||||
assert_gt!(
|
||||
file_fd,
|
||||
libc::STDERR_FILENO as wasi::Fd,
|
||||
"file descriptor range check",
|
||||
);
|
||||
wasi::fd_close(file_fd).expect("closing a file");
|
||||
}
|
||||
|
||||
// Count the entries to ensure that we see the correct number.
|
||||
let mut total = 0;
|
||||
let mut cookie = 0;
|
||||
loop {
|
||||
let (dirs, eof) = exec_fd_readdir(dir_fd, cookie);
|
||||
total += dirs.len();
|
||||
if eof {
|
||||
break;
|
||||
}
|
||||
cookie = dirs[dirs.len()-1].dirent.d_next;
|
||||
}
|
||||
assert_eq!(total, 1002, "expected 1000 entries plus . and ..");
|
||||
|
||||
for count in 0..1000 {
|
||||
wasi::path_unlink_file(dir_fd, &format!("file.{}", count)).expect("removing a file");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -156,4 +211,5 @@ fn main() {
|
||||
|
||||
// Run the tests.
|
||||
unsafe { test_fd_readdir(dir_fd) }
|
||||
unsafe { test_fd_readdir_lots(dir_fd) }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::sys::{clock, poll};
|
||||
use crate::wasi::types;
|
||||
use crate::wasi::wasi_snapshot_preview1::WasiSnapshotPreview1;
|
||||
use crate::{path, sched, Error, Result, WasiCtx};
|
||||
use std::cmp::min;
|
||||
use std::convert::TryInto;
|
||||
use std::io::{self, SeekFrom};
|
||||
use std::ops::Deref;
|
||||
@@ -304,15 +305,34 @@ impl<'a> WasiSnapshotPreview1 for WasiCtx {
|
||||
let name_raw = name.as_bytes();
|
||||
let name_len = name_raw.len().try_into()?;
|
||||
let offset = dirent_len.checked_add(name_len).ok_or(Error::Overflow)?;
|
||||
if (buf_len - bufused) < offset {
|
||||
break;
|
||||
} else {
|
||||
buf.as_array(dirent_len).copy_from_slice(&dirent_raw)?;
|
||||
buf = buf.add(dirent_len)?;
|
||||
buf.as_array(name_len).copy_from_slice(name_raw)?;
|
||||
buf = buf.add(name_len)?;
|
||||
bufused += offset;
|
||||
|
||||
// Copy as many bytes of the dirent as we can, up to the end of the buffer.
|
||||
let dirent_copy_len = min(dirent_len, buf_len - bufused);
|
||||
buf.as_array(dirent_copy_len)
|
||||
.copy_from_slice(&dirent_raw[..dirent_copy_len as usize])?;
|
||||
|
||||
// If the dirent struct wasn't copied entirely, return that we
|
||||
// filled the buffer, which tells libc that we're not at EOF.
|
||||
if dirent_copy_len < dirent_len {
|
||||
return Ok(buf_len);
|
||||
}
|
||||
|
||||
buf = buf.add(dirent_copy_len)?;
|
||||
|
||||
// Copy as many bytes of the name as we can, up to the end of the buffer.
|
||||
let name_copy_len = min(name_len, buf_len - bufused);
|
||||
buf.as_array(name_copy_len)
|
||||
.copy_from_slice(&name_raw[..name_copy_len as usize])?;
|
||||
|
||||
// If the dirent struct wasn't copied entirely, return that we
|
||||
// filled the buffer, which tells libc that we're not at EOF.
|
||||
if name_copy_len < name_len {
|
||||
return Ok(buf_len);
|
||||
}
|
||||
|
||||
buf = buf.add(name_copy_len)?;
|
||||
|
||||
bufused += offset;
|
||||
}
|
||||
|
||||
Ok(bufused)
|
||||
|
||||
@@ -3,11 +3,11 @@ use std::fs;
|
||||
use wasi_nn;
|
||||
|
||||
pub fn main() {
|
||||
let xml = fs::read_to_string("fixture/frozen_inference_graph.xml").unwrap();
|
||||
println!("First 50 characters of graph: {}", &xml[..50]);
|
||||
let xml = fs::read_to_string("fixture/alexnet.xml").unwrap();
|
||||
println!("Read graph XML, first 50 characters: {}", &xml[..50]);
|
||||
|
||||
let weights = fs::read("fixture/frozen_inference_graph.bin").unwrap();
|
||||
println!("Size of weights: {}", weights.len());
|
||||
let weights = fs::read("fixture/alexnet.bin").unwrap();
|
||||
println!("Read graph weights, size in bytes: {}", weights.len());
|
||||
|
||||
let graph = unsafe {
|
||||
wasi_nn::load(
|
||||
@@ -17,17 +17,17 @@ pub fn main() {
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
println!("Graph handle ID: {}", graph);
|
||||
println!("Loaded graph into wasi-nn with ID: {}", graph);
|
||||
|
||||
let context = unsafe { wasi_nn::init_execution_context(graph).unwrap() };
|
||||
println!("Execution context ID: {}", context);
|
||||
println!("Created wasi-nn execution context with ID: {}", context);
|
||||
|
||||
// Load a tensor that precisely matches the graph input tensor (see
|
||||
// `fixture/frozen_inference_graph.xml`).
|
||||
let tensor_data = fs::read("fixture/tensor-1x3x300x300-f32.bgr").unwrap();
|
||||
println!("Tensor bytes: {}", tensor_data.len());
|
||||
let tensor_data = fs::read("fixture/tensor-1x3x227x227-f32.bgr").unwrap();
|
||||
println!("Read input tensor, size in bytes: {}", tensor_data.len());
|
||||
let tensor = wasi_nn::Tensor {
|
||||
dimensions: &[1, 3, 300, 300],
|
||||
dimensions: &[1, 3, 227, 227],
|
||||
r#type: wasi_nn::TENSOR_TYPE_F32,
|
||||
data: &tensor_data,
|
||||
};
|
||||
@@ -39,9 +39,10 @@ pub fn main() {
|
||||
unsafe {
|
||||
wasi_nn::compute(context).unwrap();
|
||||
}
|
||||
println!("Executed graph inference");
|
||||
|
||||
// Retrieve the output (TODO output looks incorrect).
|
||||
let mut output_buffer = vec![0f32; 1 << 20];
|
||||
// Retrieve the output.
|
||||
let mut output_buffer = vec![0f32; 1000];
|
||||
unsafe {
|
||||
wasi_nn::get_output(
|
||||
context,
|
||||
@@ -50,5 +51,25 @@ pub fn main() {
|
||||
(output_buffer.len() * 4).try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
println!("output tensor: {:?}", &output_buffer[..1000])
|
||||
println!(
|
||||
"Found results, sorted top 5: {:?}",
|
||||
&sort_results(&output_buffer)[..5]
|
||||
)
|
||||
}
|
||||
|
||||
// Sort the buffer of probabilities. The graph places the match probability for each class at the
|
||||
// index for that class (e.g. the probability of class 42 is placed at buffer[42]). Here we convert
|
||||
// to a wrapping InferenceResult and sort the results.
|
||||
fn sort_results(buffer: &[f32]) -> Vec<InferenceResult> {
|
||||
let mut results: Vec<InferenceResult> = buffer
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(c, p)| InferenceResult(c, *p))
|
||||
.collect();
|
||||
results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||||
results
|
||||
}
|
||||
|
||||
// A wrapper for class ID and match probabilities.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct InferenceResult(usize, f32);
|
||||
|
||||
@@ -16,18 +16,20 @@ wasmtime-jit = { path = "../jit", version = "0.21.0" }
|
||||
wasmtime-cache = { path = "../cache", version = "0.21.0", optional = true }
|
||||
wasmtime-profiling = { path = "../profiling", version = "0.21.0" }
|
||||
target-lexicon = { version = "0.11.0", default-features = false }
|
||||
wasmparser = "0.68.0"
|
||||
wasmparser = "0.70"
|
||||
anyhow = "1.0.19"
|
||||
region = "2.2.0"
|
||||
libc = "0.2"
|
||||
cfg-if = "1.0"
|
||||
backtrace = "0.3.42"
|
||||
rustc-demangle = "0.1.16"
|
||||
cpp_demangle = "0.3.2"
|
||||
log = "0.4.8"
|
||||
wat = { version = "1.0.18", optional = true }
|
||||
smallvec = "1.4.0"
|
||||
serde = { version = "1.0.94", features = ["derive"] }
|
||||
bincode = "1.2.1"
|
||||
indexmap = "1.6"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
@@ -58,3 +60,6 @@ parallel-compilation = ["wasmtime-jit/parallel-compilation"]
|
||||
|
||||
# Enables support for automatic cache configuration to be enabled in `Config`.
|
||||
cache = ["wasmtime-cache"]
|
||||
|
||||
# Enables support for new x64 backend.
|
||||
experimental_x64 = ["wasmtime-jit/experimental_x64"]
|
||||
|
||||
@@ -32,6 +32,7 @@ pub struct Config {
|
||||
pub(crate) memory_creator: Option<MemoryCreatorProxy>,
|
||||
pub(crate) max_wasm_stack: usize,
|
||||
pub(crate) features: WasmFeatures,
|
||||
pub(crate) wasm_backtrace_details_env_used: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -61,7 +62,7 @@ impl Config {
|
||||
.set("enable_probestack", "false")
|
||||
.expect("should be valid flag");
|
||||
|
||||
Config {
|
||||
let mut ret = Config {
|
||||
tunables: Tunables::default(),
|
||||
flags,
|
||||
isa_flags: native::builder(),
|
||||
@@ -71,13 +72,16 @@ impl Config {
|
||||
profiler: Arc::new(NullProfilerAgent),
|
||||
memory_creator: None,
|
||||
max_wasm_stack: 1 << 20,
|
||||
wasm_backtrace_details_env_used: false,
|
||||
features: WasmFeatures {
|
||||
reference_types: true,
|
||||
bulk_memory: true,
|
||||
multi_value: true,
|
||||
..WasmFeatures::default()
|
||||
},
|
||||
}
|
||||
};
|
||||
ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Configures whether DWARF debug information will be emitted during
|
||||
@@ -85,7 +89,33 @@ impl Config {
|
||||
///
|
||||
/// By default this option is `false`.
|
||||
pub fn debug_info(&mut self, enable: bool) -> &mut Self {
|
||||
self.tunables.debug_info = enable;
|
||||
self.tunables.generate_native_debuginfo = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures backtraces in `Trap` will parse debuginfo in the wasm file to
|
||||
/// have filename/line number information.
|
||||
///
|
||||
/// When enabled this will causes modules to retain debugging information
|
||||
/// found in wasm binaries. This debug information will be used when a trap
|
||||
/// happens to symbolicate each stack frame and attempt to print a
|
||||
/// filename/line number for each wasm frame in the stack trace.
|
||||
///
|
||||
/// By default this option is `WasmBacktraceDetails::Environment`, meaning
|
||||
/// that wasm will read `WASMTIME_BACKTRACE_DETAILS` to indicate whether details
|
||||
/// should be parsed.
|
||||
pub fn wasm_backtrace_details(&mut self, enable: WasmBacktraceDetails) -> &mut Self {
|
||||
self.wasm_backtrace_details_env_used = false;
|
||||
self.tunables.parse_wasm_debuginfo = match enable {
|
||||
WasmBacktraceDetails::Enable => true,
|
||||
WasmBacktraceDetails::Disable => false,
|
||||
WasmBacktraceDetails::Environment => {
|
||||
self.wasm_backtrace_details_env_used = true;
|
||||
std::env::var("WASMTIME_BACKTRACE_DETAILS")
|
||||
.map(|s| s == "1")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
@@ -640,7 +670,8 @@ impl Default for Config {
|
||||
impl fmt::Debug for Config {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Config")
|
||||
.field("debug_info", &self.tunables.debug_info)
|
||||
.field("debug_info", &self.tunables.generate_native_debuginfo)
|
||||
.field("parse_wasm_debuginfo", &self.tunables.parse_wasm_debuginfo)
|
||||
.field("strategy", &self.strategy)
|
||||
.field("wasm_threads", &self.features.threads)
|
||||
.field("wasm_reference_types", &self.features.reference_types)
|
||||
@@ -712,3 +743,19 @@ pub enum ProfilingStrategy {
|
||||
/// Collect profiling info using the "ittapi", used with `VTune` on Linux.
|
||||
VTune,
|
||||
}
|
||||
|
||||
/// Select how wasm backtrace detailed information is handled.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum WasmBacktraceDetails {
|
||||
/// Support is unconditionally enabled and wasmtime will parse and read
|
||||
/// debug information.
|
||||
Enable,
|
||||
|
||||
/// Support is disabled, and wasmtime will not parse debug information for
|
||||
/// backtrace details.
|
||||
Disable,
|
||||
|
||||
/// Support for backtrace details is conditional on the
|
||||
/// `WASMTIME_BACKTRACE_DETAILS` environment variable.
|
||||
Environment,
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::trampoline::{
|
||||
};
|
||||
use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val};
|
||||
use crate::{
|
||||
ExternRef, ExternType, Func, GlobalType, MemoryType, Mutability, Store, TableType, Trap,
|
||||
ValType,
|
||||
ExternRef, ExternType, Func, GlobalType, Instance, MemoryType, Module, Mutability, Store,
|
||||
TableType, Trap, ValType,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::mem;
|
||||
@@ -33,6 +33,10 @@ pub enum Extern {
|
||||
Table(Table),
|
||||
/// A WebAssembly linear memory.
|
||||
Memory(Memory),
|
||||
/// A WebAssembly instance.
|
||||
Instance(Instance),
|
||||
/// A WebAssembly module.
|
||||
Module(Module),
|
||||
}
|
||||
|
||||
impl Extern {
|
||||
@@ -76,6 +80,26 @@ impl Extern {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying `Instance`, if this external is a instance.
|
||||
///
|
||||
/// Returns `None` if this is not a instance.
|
||||
pub fn into_instance(self) -> Option<Instance> {
|
||||
match self {
|
||||
Extern::Instance(instance) => Some(instance),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying `Module`, if this external is a module.
|
||||
///
|
||||
/// Returns `None` if this is not a module.
|
||||
pub fn into_module(self) -> Option<Module> {
|
||||
match self {
|
||||
Extern::Module(module) => Some(module),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type associated with this `Extern`.
|
||||
pub fn ty(&self) -> ExternType {
|
||||
match self {
|
||||
@@ -83,6 +107,8 @@ impl Extern {
|
||||
Extern::Memory(ft) => ExternType::Memory(ft.ty()),
|
||||
Extern::Table(tt) => ExternType::Table(tt.ty()),
|
||||
Extern::Global(gt) => ExternType::Global(gt.ty()),
|
||||
Extern::Instance(i) => ExternType::Instance(i.ty()),
|
||||
Extern::Module(m) => ExternType::Module(m.ty()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +129,13 @@ impl Extern {
|
||||
wasmtime_runtime::Export::Table(t) => {
|
||||
Extern::Table(Table::from_wasmtime_table(t, instance))
|
||||
}
|
||||
wasmtime_runtime::Export::Instance(i) => {
|
||||
let handle = unsafe { instance.store.existing_instance_handle(i.clone()) };
|
||||
Extern::Instance(Instance::from_wasmtime(handle))
|
||||
}
|
||||
wasmtime_runtime::Export::Module(m) => {
|
||||
Extern::Module(m.downcast_ref::<Module>().unwrap().clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +145,10 @@ impl Extern {
|
||||
Extern::Global(g) => &g.instance.store,
|
||||
Extern::Memory(m) => &m.instance.store,
|
||||
Extern::Table(t) => &t.instance.store,
|
||||
Extern::Instance(i) => i.store(),
|
||||
// Modules don't live in stores right now, so they're compatible
|
||||
// with all stores.
|
||||
Extern::Module(_) => return true,
|
||||
};
|
||||
Store::same(my_store, store)
|
||||
}
|
||||
@@ -122,6 +159,8 @@ impl Extern {
|
||||
Extern::Table(_) => "table",
|
||||
Extern::Memory(_) => "memory",
|
||||
Extern::Global(_) => "global",
|
||||
Extern::Instance(_) => "instance",
|
||||
Extern::Module(_) => "module",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,6 +189,18 @@ impl From<Table> for Extern {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Instance> for Extern {
|
||||
fn from(r: Instance) -> Self {
|
||||
Extern::Instance(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Module> for Extern {
|
||||
fn from(r: Module) -> Self {
|
||||
Extern::Module(r)
|
||||
}
|
||||
}
|
||||
|
||||
/// A WebAssembly `global` value which can be read and written to.
|
||||
///
|
||||
/// A `global` in WebAssembly is sort of like a global variable within an
|
||||
@@ -294,11 +345,8 @@ impl Global {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, expected: &wasmtime_environ::wasm::Global) -> bool {
|
||||
let actual = &self.wasmtime_export.global;
|
||||
expected.ty == actual.ty
|
||||
&& expected.wasm_ty == actual.wasm_ty
|
||||
&& expected.mutability == actual.mutability
|
||||
pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Global {
|
||||
&self.wasmtime_export.global
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMGlobalImport {
|
||||
@@ -538,19 +586,8 @@ impl Table {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, ty: &wasmtime_environ::TablePlan) -> bool {
|
||||
let expected = &ty.table;
|
||||
let actual = &self.wasmtime_export.table.table;
|
||||
expected.wasm_ty == actual.wasm_ty
|
||||
&& expected.ty == actual.ty
|
||||
&& expected.minimum <= actual.minimum
|
||||
&& match expected.maximum {
|
||||
Some(expected) => match actual.maximum {
|
||||
Some(actual) => expected >= actual,
|
||||
None => false,
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Table {
|
||||
&self.wasmtime_export.table.table
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMTableImport {
|
||||
@@ -960,18 +997,8 @@ impl Memory {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, ty: &wasmtime_environ::MemoryPlan) -> bool {
|
||||
let expected = &ty.memory;
|
||||
let actual = &self.wasmtime_export.memory.memory;
|
||||
expected.shared == actual.shared
|
||||
&& expected.minimum <= actual.minimum
|
||||
&& match expected.maximum {
|
||||
Some(expected) => match actual.maximum {
|
||||
Some(actual) => expected >= actual,
|
||||
None => false,
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
pub(crate) fn wasmtime_ty(&self) -> &wasmtime_environ::wasm::Memory {
|
||||
&self.wasmtime_export.memory.memory
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMMemoryImport {
|
||||
|
||||
@@ -5,7 +5,7 @@ use wasmtime_environ::entity::EntityRef;
|
||||
use wasmtime_environ::ir;
|
||||
use wasmtime_environ::wasm::FuncIndex;
|
||||
use wasmtime_environ::{FunctionAddressMap, Module, TrapInformation};
|
||||
use wasmtime_jit::CompiledModule;
|
||||
use wasmtime_jit::{CompiledModule, SymbolizeContext};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StoreFrameInfo {
|
||||
@@ -25,6 +25,8 @@ struct ModuleFrameInfo {
|
||||
start: usize,
|
||||
functions: BTreeMap<usize, FunctionInfo>,
|
||||
module: Arc<Module>,
|
||||
symbolize: Option<SymbolizeContext>,
|
||||
has_unparsed_debuginfo: bool,
|
||||
}
|
||||
|
||||
struct FunctionInfo {
|
||||
@@ -38,8 +40,10 @@ impl StoreFrameInfo {
|
||||
/// Fetches frame information about a program counter in a backtrace.
|
||||
///
|
||||
/// Returns an object if this `pc` is known to some previously registered
|
||||
/// module, or returns `None` if no information can be found.
|
||||
pub fn lookup_frame_info(&self, pc: usize) -> Option<FrameInfo> {
|
||||
/// module, or returns `None` if no information can be found. The boolean
|
||||
/// returned indicates whether the original module has unparsed debug
|
||||
/// information due to the compiler's configuration.
|
||||
pub fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, bool)> {
|
||||
let (module, func) = self.func(pc)?;
|
||||
|
||||
// Use our relative position from the start of the function to find the
|
||||
@@ -72,13 +76,49 @@ impl StoreFrameInfo {
|
||||
Some(pos) => func.instr_map.instructions[pos].srcloc,
|
||||
None => func.instr_map.start_srcloc,
|
||||
};
|
||||
Some(FrameInfo {
|
||||
module_name: module.module.name.clone(),
|
||||
func_index: func.index.index() as u32,
|
||||
func_name: module.module.func_names.get(&func.index).cloned(),
|
||||
instr,
|
||||
func_start: func.instr_map.start_srcloc,
|
||||
})
|
||||
|
||||
// Use our wasm-relative pc to symbolize this frame. If there's a
|
||||
// symbolication context (dwarf debug info) available then we can try to
|
||||
// look this up there.
|
||||
//
|
||||
// Note that dwarf pcs are code-section-relative, hence the subtraction
|
||||
// from the location of `instr`. Also note that all errors are ignored
|
||||
// here for now since technically wasm modules can always have any
|
||||
// custom section contents.
|
||||
let mut symbols = Vec::new();
|
||||
if let Some(s) = &module.symbolize {
|
||||
let to_lookup = (instr.bits() as u64) - s.code_section_offset();
|
||||
if let Ok(mut frames) = s.addr2line().find_frames(to_lookup) {
|
||||
while let Ok(Some(frame)) = frames.next() {
|
||||
symbols.push(FrameSymbol {
|
||||
name: frame
|
||||
.function
|
||||
.as_ref()
|
||||
.and_then(|l| l.raw_name().ok())
|
||||
.map(|s| s.to_string()),
|
||||
file: frame
|
||||
.location
|
||||
.as_ref()
|
||||
.and_then(|l| l.file)
|
||||
.map(|s| s.to_string()),
|
||||
line: frame.location.as_ref().and_then(|l| l.line),
|
||||
column: frame.location.as_ref().and_then(|l| l.column),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some((
|
||||
FrameInfo {
|
||||
module_name: module.module.name.clone(),
|
||||
func_index: func.index.index() as u32,
|
||||
func_name: module.module.func_names.get(&func.index).cloned(),
|
||||
instr,
|
||||
func_start: func.instr_map.start_srcloc,
|
||||
symbols,
|
||||
},
|
||||
module.has_unparsed_debuginfo,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns whether the `pc` specified is contaained within some module's
|
||||
@@ -160,6 +200,8 @@ impl StoreFrameInfo {
|
||||
start: min,
|
||||
functions,
|
||||
module: module.module().clone(),
|
||||
symbolize: module.symbolize_context().ok().and_then(|c| c),
|
||||
has_unparsed_debuginfo: module.has_unparsed_debuginfo(),
|
||||
},
|
||||
);
|
||||
assert!(prev.is_none());
|
||||
@@ -180,6 +222,20 @@ pub struct FrameInfo {
|
||||
func_name: Option<String>,
|
||||
func_start: ir::SourceLoc,
|
||||
instr: ir::SourceLoc,
|
||||
symbols: Vec<FrameSymbol>,
|
||||
}
|
||||
|
||||
/// Debug information for a symbol that is attached to a [`FrameInfo`].
|
||||
///
|
||||
/// When DWARF debug information is present in a wasm file then this structure
|
||||
/// can be found on a [`FrameInfo`] and can be used to learn about filenames,
|
||||
/// line numbers, etc, which are the origin of a function in a stack trace.
|
||||
#[derive(Debug)]
|
||||
pub struct FrameSymbol {
|
||||
name: Option<String>,
|
||||
file: Option<String>,
|
||||
line: Option<u32>,
|
||||
column: Option<u32>,
|
||||
}
|
||||
|
||||
impl FrameInfo {
|
||||
@@ -240,6 +296,55 @@ impl FrameInfo {
|
||||
pub fn func_offset(&self) -> usize {
|
||||
(self.instr.bits() - self.func_start.bits()) as usize
|
||||
}
|
||||
|
||||
/// Returns the debug symbols found, if any, for this function frame.
|
||||
///
|
||||
/// When a wasm program is compiled with DWARF debug information then this
|
||||
/// function may be populated to return symbols which contain extra debug
|
||||
/// information about a frame including the filename and line number. If no
|
||||
/// debug information was found or if it was malformed then this will return
|
||||
/// an empty array.
|
||||
pub fn symbols(&self) -> &[FrameSymbol] {
|
||||
&self.symbols
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameSymbol {
|
||||
/// Returns the function name associated with this symbol.
|
||||
///
|
||||
/// Note that this may not be present with malformed debug information, or
|
||||
/// the debug information may not include it. Also note that the symbol is
|
||||
/// frequently mangled, so you might need to run some form of demangling
|
||||
/// over it.
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the source code filename this symbol was defined in.
|
||||
///
|
||||
/// Note that this may not be present with malformed debug information, or
|
||||
/// the debug information may not include it.
|
||||
pub fn file(&self) -> Option<&str> {
|
||||
self.file.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the 1-indexed source code line number this symbol was defined
|
||||
/// on.
|
||||
///
|
||||
/// Note that this may not be present with malformed debug information, or
|
||||
/// the debug information may not include it.
|
||||
pub fn line(&self) -> Option<u32> {
|
||||
self.line
|
||||
}
|
||||
|
||||
/// Returns the 1-indexed source code column number this symbol was defined
|
||||
/// on.
|
||||
///
|
||||
/// Note that this may not be present with malformed debug information, or
|
||||
/// the debug information may not include it.
|
||||
pub fn column(&self) -> Option<u32> {
|
||||
self.column
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -270,7 +375,7 @@ fn test_frame_info() -> Result<(), anyhow::Error> {
|
||||
(ptr as usize, ptr as usize + len)
|
||||
};
|
||||
for pc in start..end {
|
||||
let frame = info.lookup_frame_info(pc).unwrap();
|
||||
let (frame, _) = info.lookup_frame_info(pc).unwrap();
|
||||
assert!(frame.func_index() == i.as_u32());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,10 +798,6 @@ impl Func {
|
||||
&self.instance.store
|
||||
}
|
||||
|
||||
pub(crate) fn matches_expected(&self, expected: VMSharedSignatureIndex) -> bool {
|
||||
self.sig_index() == expected
|
||||
}
|
||||
|
||||
pub(crate) fn vmimport(&self) -> wasmtime_runtime::VMFunctionImport {
|
||||
unsafe {
|
||||
let f = self.caller_checked_anyfunc();
|
||||
@@ -1503,7 +1499,6 @@ impl Caller<'_> {
|
||||
return None;
|
||||
}
|
||||
let instance = InstanceHandle::from_vmctx(self.caller_vmctx);
|
||||
let export = instance.lookup(name)?;
|
||||
// Our `Weak` pointer is used only to break a cycle where `Store`
|
||||
// stores instance handles which have this weak pointer as their
|
||||
// custom host data. This function should only be invoke-able while
|
||||
@@ -1511,6 +1506,7 @@ 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))),
|
||||
|
||||
@@ -1,34 +1,186 @@
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::{Engine, Export, Extern, Func, Global, Memory, Module, Store, Table, Trap};
|
||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
||||
use std::any::Any;
|
||||
use crate::types::matching;
|
||||
use crate::{
|
||||
Engine, Export, Extern, ExternType, Func, Global, InstanceType, Memory, Module, Store, Table,
|
||||
Trap,
|
||||
};
|
||||
use anyhow::{bail, Context, Error, Result};
|
||||
use std::mem;
|
||||
use wasmtime_environ::wasm::EntityIndex;
|
||||
use wasmtime_jit::CompiledModule;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::{
|
||||
EntityIndex, EntityType, FuncIndex, GlobalIndex, InstanceIndex, MemoryIndex, ModuleIndex,
|
||||
TableIndex,
|
||||
};
|
||||
use wasmtime_environ::Initializer;
|
||||
use wasmtime_jit::TypeTables;
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstantiationError, StackMapRegistry, VMContext, VMExternRefActivationsTable,
|
||||
VMFunctionBody,
|
||||
Imports, InstanceHandle, InstantiationError, 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,
|
||||
compiled_module: &CompiledModule,
|
||||
imports: Imports<'_>,
|
||||
host: Box<dyn Any>,
|
||||
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(compiled_module);
|
||||
store.register_module(module);
|
||||
|
||||
let config = store.engine().config();
|
||||
let instance = unsafe {
|
||||
let instance = compiled_module.instantiate(
|
||||
imports,
|
||||
&store.lookup_shared_signature(compiled_module.module()),
|
||||
&store.lookup_shared_signature(module.types()),
|
||||
config.memory_creator.as_ref().map(|a| a as _),
|
||||
store.interrupts(),
|
||||
host,
|
||||
Box::new(module.types().clone()),
|
||||
store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
|
||||
store.stack_map_registry() as *const StackMapRegistry as *mut _,
|
||||
)?;
|
||||
@@ -103,7 +255,6 @@ fn instantiate(
|
||||
#[derive(Clone)]
|
||||
pub struct Instance {
|
||||
pub(crate) handle: StoreInstanceHandle,
|
||||
module: Module,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
@@ -165,14 +316,47 @@ impl Instance {
|
||||
bail!("cross-`Engine` instantiation is not currently supported");
|
||||
}
|
||||
|
||||
let handle = with_imports(store, module.compiled_module(), imports, |imports| {
|
||||
instantiate(store, module.compiled_module(), imports, Box::new(()))
|
||||
// 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 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,
|
||||
module: module.clone(),
|
||||
})
|
||||
Ok(Instance { handle })
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime(handle: StoreInstanceHandle) -> Instance {
|
||||
Instance { handle }
|
||||
}
|
||||
|
||||
/// 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)),
|
||||
);
|
||||
}
|
||||
ty
|
||||
}
|
||||
|
||||
/// Returns the associated [`Store`] that this `Instance` is compiled into.
|
||||
@@ -238,87 +422,105 @@ impl Instance {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_imports<R>(
|
||||
store: &Store,
|
||||
module: &CompiledModule,
|
||||
externs: &[Extern],
|
||||
f: impl FnOnce(Imports<'_>) -> Result<R>,
|
||||
) -> Result<R> {
|
||||
let m = module.module();
|
||||
if externs.len() != m.imports.len() {
|
||||
bail!(
|
||||
"wrong number of imports provided, {} != {}",
|
||||
externs.len(),
|
||||
m.imports.len()
|
||||
);
|
||||
}
|
||||
struct ImportsBuilder<'a> {
|
||||
functions: PrimaryMap<FuncIndex, VMFunctionImport>,
|
||||
tables: PrimaryMap<TableIndex, VMTableImport>,
|
||||
memories: PrimaryMap<MemoryIndex, VMMemoryImport>,
|
||||
globals: PrimaryMap<GlobalIndex, VMGlobalImport>,
|
||||
instances: PrimaryMap<InstanceIndex, InstanceHandle>,
|
||||
modules: PrimaryMap<ModuleIndex, Module>,
|
||||
|
||||
let mut tables = Vec::new();
|
||||
let mut functions = Vec::new();
|
||||
let mut globals = Vec::new();
|
||||
let mut memories = Vec::new();
|
||||
|
||||
let mut process = |expected: &EntityIndex, actual: &Extern| {
|
||||
// For now we have a restriction that the `Store` that we're working
|
||||
// with is the same for everything involved here.
|
||||
if !actual.comes_from_same_store(store) {
|
||||
bail!("cross-`Store` instantiation is not currently supported");
|
||||
}
|
||||
|
||||
match *expected {
|
||||
EntityIndex::Table(i) => tables.push(match actual {
|
||||
Extern::Table(e) if e.matches_expected(&m.table_plans[i]) => e.vmimport(),
|
||||
Extern::Table(_) => bail!("table types incompatible"),
|
||||
_ => bail!("expected table, but found {}", actual.desc()),
|
||||
}),
|
||||
EntityIndex::Memory(i) => memories.push(match actual {
|
||||
Extern::Memory(e) if e.matches_expected(&m.memory_plans[i]) => e.vmimport(),
|
||||
Extern::Memory(_) => bail!("memory types incompatible"),
|
||||
_ => bail!("expected memory, but found {}", actual.desc()),
|
||||
}),
|
||||
EntityIndex::Global(i) => globals.push(match actual {
|
||||
Extern::Global(e) if e.matches_expected(&m.globals[i]) => e.vmimport(),
|
||||
Extern::Global(_) => bail!("global types incompatible"),
|
||||
_ => bail!("expected global, but found {}", actual.desc()),
|
||||
}),
|
||||
EntityIndex::Function(i) => {
|
||||
let func = match actual {
|
||||
Extern::Func(e) => e,
|
||||
_ => bail!("expected function, but found {}", actual.desc()),
|
||||
};
|
||||
// Look up the `i`th function's type from the module in our
|
||||
// signature registry. If it's not present then we have no
|
||||
// functions registered with that type, so `func` is guaranteed
|
||||
// to not match.
|
||||
let ty = store
|
||||
.signatures()
|
||||
.borrow()
|
||||
.lookup(&m.signatures[m.functions[i]])
|
||||
.ok_or_else(|| anyhow!("function types incompatible"))?;
|
||||
if !func.matches_expected(ty) {
|
||||
bail!("function types incompatible");
|
||||
}
|
||||
functions.push(func.vmimport());
|
||||
}
|
||||
|
||||
// FIXME(#2094)
|
||||
EntityIndex::Module(_i) => unimplemented!(),
|
||||
EntityIndex::Instance(_i) => unimplemented!(),
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (expected, actual) in m.imports.iter().zip(externs) {
|
||||
process(&expected.2, actual).with_context(|| match &expected.1 {
|
||||
Some(name) => format!("incompatible import type for {}/{}", expected.0, name),
|
||||
None => format!("incompatible import type for {}", expected.0),
|
||||
})?;
|
||||
}
|
||||
|
||||
return f(Imports {
|
||||
tables: &tables,
|
||||
functions: &functions,
|
||||
globals: &globals,
|
||||
memories: &memories,
|
||||
});
|
||||
module: &'a wasmtime_environ::Module,
|
||||
matcher: matching::MatchCx<'a>,
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
self.push_extern(actual);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_extern(&mut self, item: &Extern) {
|
||||
match item {
|
||||
Extern::Func(i) => {
|
||||
self.functions.push(i.vmimport());
|
||||
}
|
||||
Extern::Global(i) => {
|
||||
self.globals.push(i.vmimport());
|
||||
}
|
||||
Extern::Table(i) => {
|
||||
self.tables.push(i.vmimport());
|
||||
}
|
||||
Extern::Memory(i) => {
|
||||
self.memories.push(i.vmimport());
|
||||
}
|
||||
Extern::Instance(i) => {
|
||||
debug_assert!(Store::same(i.store(), self.matcher.store));
|
||||
self.instances.push(unsafe { (*i.handle).clone() });
|
||||
}
|
||||
Extern::Module(m) => {
|
||||
self.modules.push(m.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build(&mut 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ mod values;
|
||||
pub use crate::config::*;
|
||||
pub use crate::engine::*;
|
||||
pub use crate::externals::*;
|
||||
pub use crate::frame_info::FrameInfo;
|
||||
pub use crate::frame_info::{FrameInfo, FrameSymbol};
|
||||
pub use crate::func::*;
|
||||
pub use crate::instance::Instance;
|
||||
pub use crate::linker::*;
|
||||
|
||||
@@ -58,6 +58,8 @@ enum ImportKind {
|
||||
Global(GlobalType),
|
||||
Memory,
|
||||
Table,
|
||||
Module,
|
||||
Instance,
|
||||
}
|
||||
|
||||
impl Linker {
|
||||
@@ -516,10 +518,8 @@ impl Linker {
|
||||
ExternType::Global(f) => ImportKind::Global(f),
|
||||
ExternType::Memory(_) => ImportKind::Memory,
|
||||
ExternType::Table(_) => ImportKind::Table,
|
||||
|
||||
// FIXME(#2094)
|
||||
ExternType::Module(_) => unimplemented!(),
|
||||
ExternType::Instance(_) => unimplemented!(),
|
||||
ExternType::Module(_) => ImportKind::Module,
|
||||
ExternType::Instance(_) => ImportKind::Instance,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::types::{EntityType, ExportType, ExternType, ImportType};
|
||||
use crate::Engine;
|
||||
use crate::types::{ExportType, ExternType, ImportType};
|
||||
use crate::{Engine, ModuleType};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bincode::Options;
|
||||
use std::hash::Hash;
|
||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use wasmparser::Validator;
|
||||
#[cfg(feature = "cache")]
|
||||
use wasmtime_cache::ModuleCacheEntry;
|
||||
use wasmtime_jit::{CompilationArtifacts, CompiledModule};
|
||||
use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables};
|
||||
|
||||
/// A compiled WebAssembly module, ready to be instantiated.
|
||||
///
|
||||
@@ -81,10 +81,15 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule};
|
||||
#[derive(Clone)]
|
||||
pub struct Module {
|
||||
engine: Engine,
|
||||
compiled: Arc<[CompiledModule]>,
|
||||
data: Arc<ModuleData>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct ModuleData {
|
||||
pub(crate) types: Arc<TypeTables>,
|
||||
pub(crate) modules: Vec<CompiledModule>,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `bytes`.
|
||||
///
|
||||
@@ -164,7 +169,7 @@ 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.compiled).unwrap()[module.index]
|
||||
Arc::get_mut(&mut module.data).unwrap().modules[module.index]
|
||||
.module_mut()
|
||||
.expect("mutable module")
|
||||
.name = Some(name.to_string());
|
||||
@@ -240,23 +245,24 @@ impl Module {
|
||||
/// ```
|
||||
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
|
||||
#[cfg(feature = "cache")]
|
||||
let artifacts = ModuleCacheEntry::new("wasmtime", engine.cache_config())
|
||||
let (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 = CompilationArtifacts::build(engine.compiler(), binary)?;
|
||||
let (artifacts, types) = CompilationArtifacts::build(engine.compiler(), binary)?;
|
||||
|
||||
let compiled = CompiledModule::from_artifacts_list(
|
||||
let modules = CompiledModule::from_artifacts_list(
|
||||
artifacts,
|
||||
engine.compiler().isa(),
|
||||
&*engine.config().profiler,
|
||||
)?;
|
||||
|
||||
let types = Arc::new(types);
|
||||
Ok(Module {
|
||||
engine: engine.clone(),
|
||||
index: compiled.len() - 1,
|
||||
compiled: compiled.into(),
|
||||
index: 0,
|
||||
data: Arc::new(ModuleData { types, modules }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -286,14 +292,33 @@ impl Module {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the type signature of this module.
|
||||
pub fn ty(&self) -> ModuleType {
|
||||
let mut sig = ModuleType::new();
|
||||
let env_module = self.compiled_module().module();
|
||||
let types = self.types();
|
||||
for (module, field, ty) in env_module.imports() {
|
||||
sig.add_named_import(module, field, ExternType::from_wasmtime(types, &ty));
|
||||
}
|
||||
for (name, index) in env_module.exports.iter() {
|
||||
sig.add_named_export(
|
||||
name,
|
||||
ExternType::from_wasmtime(types, &env_module.type_of(*index)),
|
||||
);
|
||||
}
|
||||
sig
|
||||
}
|
||||
|
||||
/// Serialize compilation artifacts to the buffer. See also `deseriaize`.
|
||||
pub fn serialize(&self) -> Result<Vec<u8>> {
|
||||
let artifacts = (
|
||||
compiler_fingerprint(&self.engine),
|
||||
self.compiled
|
||||
self.data
|
||||
.modules
|
||||
.iter()
|
||||
.map(|i| i.compilation_artifacts())
|
||||
.collect::<Vec<_>>(),
|
||||
&*self.data.types,
|
||||
self.index,
|
||||
);
|
||||
|
||||
@@ -313,28 +338,42 @@ impl Module {
|
||||
pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result<Module> {
|
||||
let expected_fingerprint = compiler_fingerprint(engine);
|
||||
|
||||
let (fingerprint, artifacts, index) = bincode_options()
|
||||
.deserialize::<(u64, _, _)>(serialized)
|
||||
let (fingerprint, artifacts, types, index) = bincode_options()
|
||||
.deserialize::<(u64, _, _, _)>(serialized)
|
||||
.context("Deserialize compilation artifacts")?;
|
||||
if fingerprint != expected_fingerprint {
|
||||
bail!("Incompatible compilation artifact");
|
||||
}
|
||||
|
||||
let compiled = CompiledModule::from_artifacts_list(
|
||||
let modules = CompiledModule::from_artifacts_list(
|
||||
artifacts,
|
||||
engine.compiler().isa(),
|
||||
&*engine.config().profiler,
|
||||
)?;
|
||||
|
||||
let types = Arc::new(types);
|
||||
Ok(Module {
|
||||
engine: engine.clone(),
|
||||
index,
|
||||
compiled: compiled.into(),
|
||||
data: Arc::new(ModuleData { modules, types }),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compiled_module(&self) -> &CompiledModule {
|
||||
&self.compiled[self.index]
|
||||
&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,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn types(&self) -> &Arc<TypeTables> {
|
||||
&self.data.types
|
||||
}
|
||||
|
||||
/// Returns identifier/name that this [`Module`] has. This name
|
||||
@@ -418,13 +457,12 @@ impl Module {
|
||||
&'module self,
|
||||
) -> impl ExactSizeIterator<Item = ImportType<'module>> + 'module {
|
||||
let module = self.compiled_module().module();
|
||||
let types = self.types();
|
||||
module
|
||||
.imports
|
||||
.iter()
|
||||
.map(move |(module_name, name, entity_index)| {
|
||||
let r#type = EntityType::new(entity_index, module);
|
||||
ImportType::new(module_name, name.as_deref(), r#type)
|
||||
})
|
||||
.imports()
|
||||
.map(move |(module, field, ty)| ImportType::new(module, field, ty, types))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
/// Returns the list of exports that this [`Module`] has and will be
|
||||
@@ -485,9 +523,9 @@ impl Module {
|
||||
&'module self,
|
||||
) -> impl ExactSizeIterator<Item = ExportType<'module>> + 'module {
|
||||
let module = self.compiled_module().module();
|
||||
let types = self.types();
|
||||
module.exports.iter().map(move |(name, entity_index)| {
|
||||
let r#type = EntityType::new(entity_index, module);
|
||||
ExportType::new(name, r#type)
|
||||
ExportType::new(name, module.type_of(*entity_index), types)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -537,7 +575,10 @@ impl Module {
|
||||
pub fn get_export<'module>(&'module self, name: &'module str) -> Option<ExternType> {
|
||||
let module = self.compiled_module().module();
|
||||
let entity_index = module.exports.get(name)?;
|
||||
Some(EntityType::new(entity_index, module).extern_type())
|
||||
Some(ExternType::from_wasmtime(
|
||||
self.types(),
|
||||
&module.type_of(*entity_index),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the [`Engine`] that this [`Module`] was compiled by.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::frame_info::StoreFrameInfo;
|
||||
use crate::sig_registry::SignatureRegistry;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::Engine;
|
||||
use crate::{Engine, Module};
|
||||
use anyhow::{bail, Result};
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
@@ -11,7 +11,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::wasm;
|
||||
use wasmtime_jit::{CompiledModule, ModuleCode};
|
||||
use wasmtime_jit::{CompiledModule, ModuleCode, TypeTables};
|
||||
use wasmtime_runtime::{
|
||||
InstanceHandle, RuntimeMemoryCreator, SignalHandler, StackMapRegistry, TrapInfo, VMExternRef,
|
||||
VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex,
|
||||
@@ -137,17 +137,17 @@ impl Store {
|
||||
|
||||
pub(crate) fn lookup_shared_signature<'a>(
|
||||
&'a self,
|
||||
module: &'a wasmtime_environ::Module,
|
||||
types: &'a TypeTables,
|
||||
) -> impl Fn(wasm::SignatureIndex) -> VMSharedSignatureIndex + 'a {
|
||||
move |index| {
|
||||
self.signatures()
|
||||
.borrow()
|
||||
.lookup(&module.signatures[index])
|
||||
.lookup(&types.wasm_signatures[index])
|
||||
.expect("signature not previously registered")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn register_module(&self, module: &CompiledModule) {
|
||||
pub(crate) fn register_module(&self, module: &Module) {
|
||||
// All modules register their JIT code in a store for two reasons
|
||||
// currently:
|
||||
//
|
||||
@@ -158,12 +158,12 @@ impl Store {
|
||||
// * Second when generating a backtrace we'll use this mapping to
|
||||
// only generate wasm frames for instruction pointers that fall
|
||||
// within jit code.
|
||||
self.register_jit_code(module);
|
||||
self.register_jit_code(module.compiled_module());
|
||||
|
||||
// We need to know about all the stack maps of all instantiated modules
|
||||
// so when performing a GC we know about all wasm frames that we find
|
||||
// on the stack.
|
||||
self.register_stack_maps(module);
|
||||
self.register_stack_maps(module.compiled_module());
|
||||
|
||||
// Signatures are loaded into our `SignatureRegistry` here
|
||||
// once-per-module (and once-per-signature). This allows us to create
|
||||
@@ -178,7 +178,7 @@ impl Store {
|
||||
self.inner
|
||||
.modules
|
||||
.borrow_mut()
|
||||
.insert(ArcModuleCode(module.code().clone()));
|
||||
.insert(ArcModuleCode(module.compiled_module().code().clone()));
|
||||
}
|
||||
|
||||
fn register_jit_code(&self, module: &CompiledModule) {
|
||||
@@ -205,11 +205,10 @@ impl Store {
|
||||
}));
|
||||
}
|
||||
|
||||
fn register_signatures(&self, module: &CompiledModule) {
|
||||
let trampolines = module.trampolines();
|
||||
let module = module.module();
|
||||
fn register_signatures(&self, module: &Module) {
|
||||
let trampolines = module.compiled_module().trampolines();
|
||||
let mut signatures = self.signatures().borrow_mut();
|
||||
for (index, wasm) in module.signatures.iter() {
|
||||
for (index, wasm) in module.types().wasm_signatures.iter() {
|
||||
signatures.register(wasm, trampolines[index]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
use wasmtime_environ::Module;
|
||||
use wasmtime_runtime::{
|
||||
Imports, InstanceHandle, StackMapRegistry, VMExternRefActivationsTable, VMFunctionBody,
|
||||
VMFunctionImport,
|
||||
VMFunctionImport, VMSharedSignatureIndex,
|
||||
};
|
||||
|
||||
pub(crate) fn create_handle(
|
||||
@@ -19,11 +19,11 @@ pub(crate) fn create_handle(
|
||||
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
state: Box<dyn Any>,
|
||||
func_imports: &[VMFunctionImport],
|
||||
shared_signature_id: Option<VMSharedSignatureIndex>,
|
||||
) -> Result<StoreInstanceHandle> {
|
||||
let mut imports = Imports::default();
|
||||
imports.functions = func_imports;
|
||||
let module = Arc::new(module);
|
||||
let module2 = module.clone();
|
||||
|
||||
unsafe {
|
||||
let handle = InstanceHandle::new(
|
||||
@@ -31,7 +31,7 @@ pub(crate) fn create_handle(
|
||||
&finished_functions,
|
||||
imports,
|
||||
store.memory_creator(),
|
||||
&store.lookup_shared_signature(&module2),
|
||||
&|_| shared_signature_id.unwrap(),
|
||||
state,
|
||||
store.interrupts(),
|
||||
store.externref_activations_table() as *const VMExternRefActivationsTable as *mut _,
|
||||
|
||||
@@ -10,7 +10,8 @@ use std::mem;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::isa::TargetIsa;
|
||||
use wasmtime_environ::{ir, wasm, CompiledFunction, Module};
|
||||
use wasmtime_environ::wasm::SignatureIndex;
|
||||
use wasmtime_environ::{ir, wasm, CompiledFunction, Module, ModuleType};
|
||||
use wasmtime_jit::trampoline::ir::{
|
||||
ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind,
|
||||
};
|
||||
@@ -223,7 +224,8 @@ pub fn create_handle_with_function(
|
||||
|
||||
// First up we manufacture a trampoline which has the ABI specified by `ft`
|
||||
// and calls into `stub_fn`...
|
||||
let sig_id = module.signatures.push(wft.clone());
|
||||
let sig_id = SignatureIndex::from_u32(u32::max_value() - 1);
|
||||
module.types.push(ModuleType::Function(sig_id));
|
||||
let func_id = module.functions.push(sig_id);
|
||||
module
|
||||
.exports
|
||||
@@ -241,7 +243,7 @@ pub fn create_handle_with_function(
|
||||
&sig,
|
||||
mem::size_of::<u128>(),
|
||||
)?;
|
||||
store.signatures().borrow_mut().register(wft, trampoline);
|
||||
let shared_signature_id = store.signatures().borrow_mut().register(wft, trampoline);
|
||||
|
||||
// Next up we wrap everything up into an `InstanceHandle` by publishing our
|
||||
// code memory (makes it executable) and ensuring all our various bits of
|
||||
@@ -254,6 +256,7 @@ pub fn create_handle_with_function(
|
||||
finished_functions,
|
||||
Box::new(trampoline_state),
|
||||
&[],
|
||||
Some(shared_signature_id),
|
||||
)
|
||||
.map(|instance| (instance, trampoline))
|
||||
}
|
||||
@@ -270,13 +273,21 @@ pub unsafe fn create_handle_with_raw_function(
|
||||
let mut module = Module::new();
|
||||
let mut finished_functions = PrimaryMap::new();
|
||||
|
||||
let sig_id = module.signatures.push(wft.clone());
|
||||
let sig_id = SignatureIndex::from_u32(u32::max_value() - 1);
|
||||
module.types.push(ModuleType::Function(sig_id));
|
||||
let func_id = module.functions.push(sig_id);
|
||||
module
|
||||
.exports
|
||||
.insert(String::new(), wasm::EntityIndex::Function(func_id));
|
||||
finished_functions.push(func);
|
||||
store.signatures().borrow_mut().register(wft, trampoline);
|
||||
let shared_signature_id = store.signatures().borrow_mut().register(wft, trampoline);
|
||||
|
||||
create_handle(module, store, finished_functions, state, &[])
|
||||
create_handle(
|
||||
module,
|
||||
store,
|
||||
finished_functions,
|
||||
state,
|
||||
&[],
|
||||
Some(shared_signature_id),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,13 +3,17 @@ use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::{GlobalType, Mutability, Store, Val};
|
||||
use anyhow::Result;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, Module};
|
||||
use wasmtime_environ::{
|
||||
wasm::{self, SignatureIndex},
|
||||
Module, ModuleType,
|
||||
};
|
||||
use wasmtime_runtime::VMFunctionImport;
|
||||
|
||||
pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreInstanceHandle> {
|
||||
let mut module = Module::new();
|
||||
let mut func_imports = Vec::new();
|
||||
let mut externref_init = None;
|
||||
let mut shared_signature_id = None;
|
||||
|
||||
let global = wasm::Global {
|
||||
wasm_ty: gt.content().to_wasm_type(),
|
||||
@@ -35,17 +39,19 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
|
||||
Val::FuncRef(Some(f)) => {
|
||||
// Add a function import to the stub module, and then initialize
|
||||
// our global with a `ref.func` to grab that imported function.
|
||||
let signatures = store.signatures().borrow();
|
||||
let shared_sig_index = f.sig_index();
|
||||
let (wasm, _) = signatures
|
||||
.lookup_shared(shared_sig_index)
|
||||
.expect("signature not registered");
|
||||
let local_sig_index = module.signatures.push(wasm.clone());
|
||||
let func_index = module.functions.push(local_sig_index);
|
||||
shared_signature_id = Some(shared_sig_index);
|
||||
let sig_id = SignatureIndex::from_u32(u32::max_value() - 1);
|
||||
module.types.push(ModuleType::Function(sig_id));
|
||||
let func_index = module.functions.push(sig_id);
|
||||
module.num_imported_funcs = 1;
|
||||
module
|
||||
.imports
|
||||
.push(("".into(), None, wasm::EntityIndex::Function(func_index)));
|
||||
.initializers
|
||||
.push(wasmtime_environ::Initializer::Import {
|
||||
module: "".into(),
|
||||
field: None,
|
||||
index: wasm::EntityIndex::Function(func_index),
|
||||
});
|
||||
|
||||
let f = f.caller_checked_anyfunc();
|
||||
let f = unsafe { f.as_ref() };
|
||||
@@ -70,6 +76,7 @@ pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreIn
|
||||
PrimaryMap::new(),
|
||||
Box::new(()),
|
||||
&func_imports,
|
||||
shared_signature_id,
|
||||
)?;
|
||||
|
||||
if let Some(x) = externref_init {
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn create_handle_with_memory(
|
||||
.exports
|
||||
.insert(String::new(), wasm::EntityIndex::Memory(memory_id));
|
||||
|
||||
create_handle(module, store, PrimaryMap::new(), Box::new(()), &[])
|
||||
create_handle(module, store, PrimaryMap::new(), Box::new(()), &[], None)
|
||||
}
|
||||
|
||||
struct LinearMemoryProxy {
|
||||
|
||||
@@ -27,5 +27,5 @@ pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<Stor
|
||||
.exports
|
||||
.insert(String::new(), wasm::EntityIndex::Table(table_id));
|
||||
|
||||
create_handle(module, store, PrimaryMap::new(), Box::new(()), &[])
|
||||
create_handle(module, store, PrimaryMap::new(), Box::new(()), &[], None)
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ struct TrapInner {
|
||||
reason: TrapReason,
|
||||
wasm_trace: Vec<FrameInfo>,
|
||||
native_trace: Backtrace,
|
||||
hint_wasm_backtrace_details_env: bool,
|
||||
}
|
||||
|
||||
fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) {
|
||||
@@ -214,6 +215,7 @@ impl Trap {
|
||||
native_trace: Backtrace,
|
||||
) -> Self {
|
||||
let mut wasm_trace = Vec::new();
|
||||
let mut hint_wasm_backtrace_details_env = false;
|
||||
wasmtime_runtime::with_last_info(|last| {
|
||||
// If the `store` passed in is `None` then we look at the `last`
|
||||
// store configured to call wasm, and if that's a `Store` we use
|
||||
@@ -236,9 +238,22 @@ impl Trap {
|
||||
// want to lookup information for the previous instruction
|
||||
// (the call instruction) so we subtract one as the lookup.
|
||||
let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 };
|
||||
if let Some(info) = store.frame_info().borrow().lookup_frame_info(pc_to_lookup)
|
||||
if let Some((info, has_unparsed_debuginfo)) =
|
||||
store.frame_info().borrow().lookup_frame_info(pc_to_lookup)
|
||||
{
|
||||
wasm_trace.push(info);
|
||||
|
||||
// If this frame has unparsed debug information and the
|
||||
// store's configuration indicates that we were
|
||||
// respecting the environment variable of whether to
|
||||
// do this then we will print out a helpful note in
|
||||
// `Display` to indicate that more detailed information
|
||||
// in a trap may be available.
|
||||
if has_unparsed_debuginfo
|
||||
&& store.engine().config().wasm_backtrace_details_env_used
|
||||
{
|
||||
hint_wasm_backtrace_details_env = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,6 +263,7 @@ impl Trap {
|
||||
reason,
|
||||
wasm_trace,
|
||||
native_trace,
|
||||
hint_wasm_backtrace_details_env,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -297,15 +313,52 @@ impl fmt::Display for Trap {
|
||||
writeln!(f, "\nwasm backtrace:")?;
|
||||
for (i, frame) in self.trace().iter().enumerate() {
|
||||
let name = frame.module_name().unwrap_or("<unknown>");
|
||||
write!(f, " {}: {:#6x} - {}!", i, frame.module_offset(), name)?;
|
||||
match frame.func_name() {
|
||||
Some(name) => match rustc_demangle::try_demangle(name) {
|
||||
Ok(name) => write!(f, "{}", name)?,
|
||||
Err(_) => write!(f, "{}", name)?,
|
||||
},
|
||||
None => write!(f, "<wasm function {}>", frame.func_index())?,
|
||||
write!(f, " {:>3}: {:#6x} - ", i, frame.module_offset())?;
|
||||
|
||||
let demangle =
|
||||
|f: &mut fmt::Formatter<'_>, name: &str| match rustc_demangle::try_demangle(name) {
|
||||
Ok(name) => write!(f, "{}", name),
|
||||
Err(_) => match cpp_demangle::Symbol::new(name) {
|
||||
Ok(name) => write!(f, "{}", name),
|
||||
Err(_) => write!(f, "{}", name),
|
||||
},
|
||||
};
|
||||
let write_raw_func_name = |f: &mut fmt::Formatter<'_>| match frame.func_name() {
|
||||
Some(name) => demangle(f, name),
|
||||
None => write!(f, "<wasm function {}>", frame.func_index()),
|
||||
};
|
||||
if frame.symbols().is_empty() {
|
||||
write!(f, "{}!", name)?;
|
||||
write_raw_func_name(f)?;
|
||||
writeln!(f, "")?;
|
||||
} else {
|
||||
for (i, symbol) in frame.symbols().iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(f, " - ")?;
|
||||
} else {
|
||||
// ...
|
||||
}
|
||||
match symbol.name() {
|
||||
Some(name) => demangle(f, name)?,
|
||||
None if i == 0 => write_raw_func_name(f)?,
|
||||
None => write!(f, "<inlined function>")?,
|
||||
}
|
||||
writeln!(f, "")?;
|
||||
if let Some(file) = symbol.file() {
|
||||
write!(f, " at {}", file)?;
|
||||
if let Some(line) = symbol.line() {
|
||||
write!(f, ":{}", line)?;
|
||||
if let Some(col) = symbol.column() {
|
||||
write!(f, ":{}", col)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
writeln!(f, "")?;
|
||||
}
|
||||
}
|
||||
writeln!(f, "")?;
|
||||
}
|
||||
if self.inner.hint_wasm_backtrace_details_env {
|
||||
writeln!(f, "note: run with `WASMTIME_BACKTRACE_DETAILS=1` environment variable to display more information")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use std::fmt;
|
||||
use wasmtime_environ::wasm::WasmFuncType;
|
||||
use wasmtime_environ::wasm::{EntityType, WasmFuncType};
|
||||
use wasmtime_environ::{ir, wasm};
|
||||
use wasmtime_jit::TypeTables;
|
||||
|
||||
pub(crate) mod matching;
|
||||
|
||||
// Type Representations
|
||||
|
||||
@@ -195,33 +198,25 @@ impl ExternType {
|
||||
(Instance(InstanceType) instance unwrap_instance)
|
||||
}
|
||||
|
||||
fn from_wasmtime(
|
||||
module: &wasmtime_environ::Module,
|
||||
pub(crate) fn from_wasmtime(
|
||||
types: &TypeTables,
|
||||
ty: &wasmtime_environ::wasm::EntityType,
|
||||
) -> ExternType {
|
||||
use wasmtime_environ::wasm::EntityType;
|
||||
match ty {
|
||||
EntityType::Function(idx) => {
|
||||
let sig = module.types[*idx].unwrap_function();
|
||||
let sig = &module.signatures[sig];
|
||||
let sig = &types.wasm_signatures[*idx];
|
||||
FuncType::from_wasm_func_type(sig).into()
|
||||
}
|
||||
EntityType::Global(ty) => GlobalType::from_wasmtime_global(ty).into(),
|
||||
EntityType::Memory(ty) => MemoryType::from_wasmtime_memory(ty).into(),
|
||||
EntityType::Table(ty) => TableType::from_wasmtime_table(ty).into(),
|
||||
EntityType::Module(ty) => {
|
||||
let (imports, exports) = match &module.types[*ty] {
|
||||
wasmtime_environ::ModuleType::Module { imports, exports } => (imports, exports),
|
||||
_ => unreachable!("not possible in valid wasm modules"),
|
||||
};
|
||||
ModuleType::from_wasmtime(module, imports, exports).into()
|
||||
let ty = &types.module_signatures[*ty];
|
||||
ModuleType::from_wasmtime(types, ty).into()
|
||||
}
|
||||
EntityType::Instance(ty) => {
|
||||
let exports = match &module.types[*ty] {
|
||||
wasmtime_environ::ModuleType::Instance { exports } => exports,
|
||||
_ => unreachable!("not possible in valid wasm modules"),
|
||||
};
|
||||
InstanceType::from_wasmtime(module, exports).into()
|
||||
let ty = &types.instance_signatures[*ty];
|
||||
InstanceType::from_wasmtime(types, ty).into()
|
||||
}
|
||||
EntityType::Event(_) => unimplemented!("wasm event support"),
|
||||
}
|
||||
@@ -499,22 +494,23 @@ impl ModuleType {
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime(
|
||||
module: &wasmtime_environ::Module,
|
||||
imports: &[(String, Option<String>, wasmtime_environ::wasm::EntityType)],
|
||||
exports: &[(String, wasmtime_environ::wasm::EntityType)],
|
||||
types: &TypeTables,
|
||||
ty: &wasmtime_environ::ModuleSignature,
|
||||
) -> ModuleType {
|
||||
let exports = &types.instance_signatures[ty.exports].exports;
|
||||
ModuleType {
|
||||
exports: exports
|
||||
.iter()
|
||||
.map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(module, ty)))
|
||||
.map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(types, ty)))
|
||||
.collect(),
|
||||
imports: imports
|
||||
imports: ty
|
||||
.imports
|
||||
.iter()
|
||||
.map(|(m, name, ty)| {
|
||||
(
|
||||
m.to_string(),
|
||||
name.as_ref().map(|n| n.to_string()),
|
||||
ExternType::from_wasmtime(module, ty),
|
||||
ExternType::from_wasmtime(types, ty),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
@@ -556,109 +552,19 @@ impl InstanceType {
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime(
|
||||
module: &wasmtime_environ::Module,
|
||||
exports: &[(String, wasmtime_environ::wasm::EntityType)],
|
||||
types: &TypeTables,
|
||||
ty: &wasmtime_environ::InstanceSignature,
|
||||
) -> InstanceType {
|
||||
InstanceType {
|
||||
exports: exports
|
||||
exports: ty
|
||||
.exports
|
||||
.iter()
|
||||
.map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(module, ty)))
|
||||
.map(|(name, ty)| (name.to_string(), ExternType::from_wasmtime(types, ty)))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entity Types
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum EntityType<'module> {
|
||||
Function(&'module wasm::WasmFuncType),
|
||||
Table(&'module wasm::Table),
|
||||
Memory(&'module wasm::Memory),
|
||||
Global(&'module wasm::Global),
|
||||
Module {
|
||||
imports: &'module [(String, Option<String>, wasmtime_environ::wasm::EntityType)],
|
||||
exports: &'module [(String, wasmtime_environ::wasm::EntityType)],
|
||||
module: &'module wasmtime_environ::Module,
|
||||
},
|
||||
Instance {
|
||||
exports: &'module [(String, wasmtime_environ::wasm::EntityType)],
|
||||
module: &'module wasmtime_environ::Module,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'module> EntityType<'module> {
|
||||
/// Translate from a `EntityIndex` into an `ExternType`.
|
||||
pub(crate) fn new(
|
||||
entity_index: &wasm::EntityIndex,
|
||||
module: &'module wasmtime_environ::Module,
|
||||
) -> EntityType<'module> {
|
||||
match entity_index {
|
||||
wasm::EntityIndex::Function(func_index) => {
|
||||
let sig = module.wasm_func_type(*func_index);
|
||||
EntityType::Function(&sig)
|
||||
}
|
||||
wasm::EntityIndex::Table(table_index) => {
|
||||
EntityType::Table(&module.table_plans[*table_index].table)
|
||||
}
|
||||
wasm::EntityIndex::Memory(memory_index) => {
|
||||
EntityType::Memory(&module.memory_plans[*memory_index].memory)
|
||||
}
|
||||
wasm::EntityIndex::Global(global_index) => {
|
||||
EntityType::Global(&module.globals[*global_index])
|
||||
}
|
||||
wasm::EntityIndex::Module(idx) => {
|
||||
let (imports, exports) = match &module.types[module.modules[*idx]] {
|
||||
wasmtime_environ::ModuleType::Module { imports, exports } => (imports, exports),
|
||||
_ => unreachable!("valid modules should never hit this"),
|
||||
};
|
||||
EntityType::Module {
|
||||
imports,
|
||||
exports,
|
||||
module,
|
||||
}
|
||||
}
|
||||
wasm::EntityIndex::Instance(idx) => {
|
||||
// Get the type, either a pointer to an instance for an import
|
||||
// or a module for an instantiation.
|
||||
let ty = match module.instances[*idx] {
|
||||
wasmtime_environ::Instance::Import(ty) => ty,
|
||||
wasmtime_environ::Instance::Instantiate { module: idx, .. } => {
|
||||
module.modules[idx]
|
||||
}
|
||||
};
|
||||
// Get the exports of whatever our type specifies, ignoring
|
||||
// imports in the module case since we're instantiating the
|
||||
// module.
|
||||
let exports = match &module.types[ty] {
|
||||
wasmtime_environ::ModuleType::Instance { exports } => exports,
|
||||
wasmtime_environ::ModuleType::Module { exports, .. } => exports,
|
||||
_ => unreachable!("valid modules should never hit this"),
|
||||
};
|
||||
EntityType::Instance { exports, module }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this `EntityType` to an `ExternType`.
|
||||
pub(crate) fn extern_type(&self) -> ExternType {
|
||||
match self {
|
||||
EntityType::Function(sig) => FuncType::from_wasm_func_type(sig).into(),
|
||||
EntityType::Table(table) => TableType::from_wasmtime_table(table).into(),
|
||||
EntityType::Memory(memory) => MemoryType::from_wasmtime_memory(memory).into(),
|
||||
EntityType::Global(global) => GlobalType::from_wasmtime_global(global).into(),
|
||||
EntityType::Instance { exports, module } => {
|
||||
InstanceType::from_wasmtime(module, exports).into()
|
||||
}
|
||||
EntityType::Module {
|
||||
imports,
|
||||
exports,
|
||||
module,
|
||||
} => ModuleType::from_wasmtime(module, imports, exports).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import Types
|
||||
|
||||
/// A descriptor for an imported value into a wasm module.
|
||||
@@ -681,7 +587,7 @@ pub struct ImportType<'module> {
|
||||
|
||||
#[derive(Clone)]
|
||||
enum EntityOrExtern<'a> {
|
||||
Entity(EntityType<'a>),
|
||||
Entity(EntityType, &'a TypeTables),
|
||||
Extern(&'a ExternType),
|
||||
}
|
||||
|
||||
@@ -691,12 +597,13 @@ impl<'module> ImportType<'module> {
|
||||
pub(crate) fn new(
|
||||
module: &'module str,
|
||||
name: Option<&'module str>,
|
||||
ty: EntityType<'module>,
|
||||
ty: EntityType,
|
||||
types: &'module TypeTables,
|
||||
) -> ImportType<'module> {
|
||||
ImportType {
|
||||
module,
|
||||
name,
|
||||
ty: EntityOrExtern::Entity(ty),
|
||||
ty: EntityOrExtern::Entity(ty, types),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,7 +624,7 @@ impl<'module> ImportType<'module> {
|
||||
/// Returns the expected type of this import.
|
||||
pub fn ty(&self) -> ExternType {
|
||||
match &self.ty {
|
||||
EntityOrExtern::Entity(e) => e.extern_type(),
|
||||
EntityOrExtern::Entity(e, types) => ExternType::from_wasmtime(types, e),
|
||||
EntityOrExtern::Extern(e) => (*e).clone(),
|
||||
}
|
||||
}
|
||||
@@ -753,10 +660,14 @@ pub struct ExportType<'module> {
|
||||
impl<'module> ExportType<'module> {
|
||||
/// Creates a new export which is exported with the given `name` and has the
|
||||
/// given `ty`.
|
||||
pub(crate) fn new(name: &'module str, ty: EntityType<'module>) -> ExportType<'module> {
|
||||
pub(crate) fn new(
|
||||
name: &'module str,
|
||||
ty: EntityType,
|
||||
types: &'module TypeTables,
|
||||
) -> ExportType<'module> {
|
||||
ExportType {
|
||||
name,
|
||||
ty: EntityOrExtern::Entity(ty),
|
||||
ty: EntityOrExtern::Entity(ty, types),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,7 +679,7 @@ impl<'module> ExportType<'module> {
|
||||
/// Returns the type of this export.
|
||||
pub fn ty(&self) -> ExternType {
|
||||
match &self.ty {
|
||||
EntityOrExtern::Entity(e) => e.extern_type(),
|
||||
EntityOrExtern::Entity(e, types) => ExternType::from_wasmtime(types, e),
|
||||
EntityOrExtern::Extern(e) => (*e).clone(),
|
||||
}
|
||||
}
|
||||
|
||||
195
crates/wasmtime/src/types/matching.rs
Normal file
195
crates/wasmtime/src/types/matching.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use crate::Store;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::wasm::{
|
||||
EntityType, Global, InstanceTypeIndex, Memory, ModuleTypeIndex, SignatureIndex, Table,
|
||||
};
|
||||
use wasmtime_jit::TypeTables;
|
||||
|
||||
pub struct MatchCx<'a> {
|
||||
pub types: &'a TypeTables,
|
||||
pub store: &'a Store,
|
||||
}
|
||||
|
||||
impl MatchCx<'_> {
|
||||
pub fn global(&self, expected: &Global, actual: &crate::Global) -> bool {
|
||||
self.global_ty(expected, actual.wasmtime_ty())
|
||||
}
|
||||
|
||||
fn global_ty(&self, expected: &Global, actual: &Global) -> bool {
|
||||
expected.ty == actual.ty
|
||||
&& expected.wasm_ty == actual.wasm_ty
|
||||
&& expected.mutability == actual.mutability
|
||||
}
|
||||
|
||||
pub fn table(&self, expected: &Table, actual: &crate::Table) -> bool {
|
||||
self.table_ty(expected, actual.wasmtime_ty())
|
||||
}
|
||||
|
||||
fn table_ty(&self, expected: &Table, actual: &Table) -> bool {
|
||||
expected.wasm_ty == actual.wasm_ty
|
||||
&& expected.ty == actual.ty
|
||||
&& expected.minimum <= actual.minimum
|
||||
&& match expected.maximum {
|
||||
Some(expected) => match actual.maximum {
|
||||
Some(actual) => expected >= actual,
|
||||
None => false,
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory(&self, expected: &Memory, actual: &crate::Memory) -> bool {
|
||||
self.memory_ty(expected, actual.wasmtime_ty())
|
||||
}
|
||||
|
||||
fn memory_ty(&self, expected: &Memory, actual: &Memory) -> bool {
|
||||
expected.shared == actual.shared
|
||||
&& expected.minimum <= actual.minimum
|
||||
&& match expected.maximum {
|
||||
Some(expected) => match actual.maximum {
|
||||
Some(actual) => expected >= actual,
|
||||
None => false,
|
||||
},
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn func(&self, expected: SignatureIndex, actual: &crate::Func) -> bool {
|
||||
match self
|
||||
.store
|
||||
.signatures()
|
||||
.borrow()
|
||||
.lookup(&self.types.wasm_signatures[expected])
|
||||
{
|
||||
Some(idx) => actual.sig_index() == idx,
|
||||
// If our expected signature isn't registered, then there's no way
|
||||
// that `actual` can match it.
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
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)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Validates that the type signature of `actual` matches the `expected`
|
||||
/// module type signature.
|
||||
pub fn module(&self, expected: ModuleTypeIndex, actual: &crate::Module) -> bool {
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
/// Validates that the `actual_imports` list of module imports matches the
|
||||
/// `expected` module type signature.
|
||||
///
|
||||
/// Types specified in `actual_imports` are relative to `actual_types`.
|
||||
fn imports_match<'a>(
|
||||
&self,
|
||||
expected: ModuleTypeIndex,
|
||||
actual_types: &TypeTables,
|
||||
mut actual_imports: impl Iterator<Item = (&'a str, Option<&'a str>, EntityType)>,
|
||||
) -> bool {
|
||||
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,
|
||||
};
|
||||
if !self.extern_ty_matches(expected, &ty, actual_types) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
actual_imports.next().is_none()
|
||||
}
|
||||
|
||||
/// Validates that all exports in `expected` are defined by `lookup` within
|
||||
/// `actual_types`.
|
||||
fn exports_match(
|
||||
&self,
|
||||
expected: InstanceTypeIndex,
|
||||
actual_types: &TypeTables,
|
||||
lookup: impl Fn(&str) -> Option<EntityType>,
|
||||
) -> bool {
|
||||
// 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,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Validates that the `expected` entity matches the `actual_ty` defined
|
||||
/// within `actual_types`.
|
||||
fn extern_ty_matches(
|
||||
&self,
|
||||
expected: &EntityType,
|
||||
actual_ty: &EntityType,
|
||||
actual_types: &TypeTables,
|
||||
) -> bool {
|
||||
match expected {
|
||||
EntityType::Global(expected) => match actual_ty {
|
||||
EntityType::Global(actual) => self.global_ty(expected, actual),
|
||||
_ => false,
|
||||
},
|
||||
EntityType::Table(expected) => match actual_ty {
|
||||
EntityType::Table(actual) => self.table_ty(expected, actual),
|
||||
_ => false,
|
||||
},
|
||||
EntityType::Memory(expected) => match actual_ty {
|
||||
EntityType::Memory(actual) => self.memory_ty(expected, actual),
|
||||
_ => false,
|
||||
},
|
||||
EntityType::Function(expected) => match *actual_ty {
|
||||
EntityType::Function(actual) => {
|
||||
self.types.wasm_signatures[*expected] == actual_types.wasm_signatures[actual]
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
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()
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
EntityType::Module(expected) => match actual_ty {
|
||||
EntityType::Module(actual) => {
|
||||
let expected_module_sig = &self.types.module_signatures[*expected];
|
||||
let actual_module_sig = &actual_types.module_signatures[*actual];
|
||||
let actual_instance_sig =
|
||||
&actual_types.instance_signatures[actual_module_sig.exports];
|
||||
|
||||
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_instance_sig.exports.get(name).cloned()
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
EntityType::Event(_) => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
anyhow = "1.0.19"
|
||||
wasmtime = { path = "../wasmtime", version = "0.21.0", default-features = false }
|
||||
wast = "27.0.0"
|
||||
wast = "29.0.0"
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::spectest::link_spectest;
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use std::path::Path;
|
||||
use core::fmt;
|
||||
use std::str;
|
||||
use std::{mem::size_of_val, path::Path};
|
||||
use wasmtime::*;
|
||||
use wast::Wat;
|
||||
use wast::{
|
||||
@@ -185,7 +186,13 @@ impl WastContext {
|
||||
if val_matches(v, e)? {
|
||||
continue;
|
||||
}
|
||||
bail!("expected {:?}, got {:?}", e, v)
|
||||
bail!(
|
||||
"expected {:?} ({}), got {:?} ({})",
|
||||
e,
|
||||
e.as_hex_pattern(),
|
||||
v,
|
||||
v.as_hex_pattern()
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -226,20 +233,25 @@ impl WastContext {
|
||||
|
||||
for directive in ast.directives {
|
||||
let sp = directive.span();
|
||||
self.run_directive(directive).with_context(|| {
|
||||
let (line, col) = sp.linecol_in(wast);
|
||||
format!("failed directive on {}:{}:{}", filename, line + 1, col)
|
||||
})?;
|
||||
self.run_directive(directive, &adjust_wast)
|
||||
.with_context(|| {
|
||||
let (line, col) = sp.linecol_in(wast);
|
||||
format!("failed directive on {}:{}:{}", filename, line + 1, col)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_directive(&mut self, directive: wast::WastDirective) -> Result<()> {
|
||||
fn run_directive(
|
||||
&mut self,
|
||||
directive: wast::WastDirective,
|
||||
adjust: impl Fn(wast::Error) -> wast::Error,
|
||||
) -> Result<()> {
|
||||
use wast::WastDirective::*;
|
||||
|
||||
match directive {
|
||||
Module(mut module) => {
|
||||
let binary = module.encode()?;
|
||||
let binary = module.encode().map_err(adjust)?;
|
||||
self.module(module.id.map(|s| s.name()), &binary)?;
|
||||
}
|
||||
QuoteModule { span: _, source } => {
|
||||
@@ -249,7 +261,10 @@ impl WastContext {
|
||||
module.push_str(" ");
|
||||
}
|
||||
let buf = ParseBuffer::new(&module)?;
|
||||
let mut wat = parser::parse::<Wat>(&buf)?;
|
||||
let mut wat = parser::parse::<Wat>(&buf).map_err(|mut e| {
|
||||
e.set_text(&module);
|
||||
e
|
||||
})?;
|
||||
let binary = wat.module.encode()?;
|
||||
self.module(wat.module.id.map(|s| s.name()), &binary)?;
|
||||
}
|
||||
@@ -317,7 +332,7 @@ impl WastContext {
|
||||
// interested in.
|
||||
wast::QuoteModule::Quote(_) => return Ok(()),
|
||||
};
|
||||
let bytes = module.encode()?;
|
||||
let bytes = module.encode().map_err(adjust)?;
|
||||
if let Ok(_) = self.module(None, &bytes) {
|
||||
bail!("expected malformed module to fail to instantiate");
|
||||
}
|
||||
@@ -327,7 +342,7 @@ impl WastContext {
|
||||
mut module,
|
||||
message,
|
||||
} => {
|
||||
let bytes = module.encode()?;
|
||||
let bytes = module.encode().map_err(adjust)?;
|
||||
let err = match self.module(None, &bytes) {
|
||||
Ok(()) => bail!("expected module to fail to link"),
|
||||
Err(e) => e,
|
||||
@@ -356,9 +371,6 @@ impl WastContext {
|
||||
|
||||
fn is_matching_assert_invalid_error_message(expected: &str, actual: &str) -> bool {
|
||||
actual.contains(expected)
|
||||
// Waiting on https://github.com/WebAssembly/bulk-memory-operations/pull/137
|
||||
// to propagate to WebAssembly/testsuite.
|
||||
|| (expected.contains("unknown table") && actual.contains("unknown elem"))
|
||||
// `elem.wast` and `proposals/bulk-memory-operations/elem.wast` disagree
|
||||
// on the expected error message for the same error.
|
||||
|| (expected.contains("out of bounds") && actual.contains("does not fit"))
|
||||
@@ -382,22 +394,50 @@ fn extract_lane_as_i64(bytes: u128, lane: usize) -> i64 {
|
||||
(bytes >> (lane * 64)) as i64
|
||||
}
|
||||
|
||||
/// Check if an f32 (as u32 bits to avoid possible quieting when moving values in registers, e.g.
|
||||
/// https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
|
||||
/// is a canonical NaN:
|
||||
/// - the sign bit is unspecified,
|
||||
/// - the 8-bit exponent is set to all 1s
|
||||
/// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
|
||||
/// See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||
fn is_canonical_f32_nan(bits: u32) -> bool {
|
||||
(bits & 0x7fff_ffff) == 0x7fc0_0000
|
||||
}
|
||||
|
||||
/// Check if an f64 (as u64 bits to avoid possible quieting when moving values in registers, e.g.
|
||||
/// https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
|
||||
/// is a canonical NaN:
|
||||
/// - the sign bit is unspecified,
|
||||
/// - the 11-bit exponent is set to all 1s
|
||||
/// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
|
||||
/// See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||
fn is_canonical_f64_nan(bits: u64) -> bool {
|
||||
(bits & 0x7fff_ffff_ffff_ffff) == 0x7ff8_0000_0000_0000
|
||||
}
|
||||
|
||||
/// Check if an f32 (as u32, see comments above) is an arithmetic NaN. This is the same as a
|
||||
/// canonical NaN including that the payload MSB is set to 1, but one or more of the remaining
|
||||
/// payload bits MAY BE set to 1 (a canonical NaN specifies all 0s). See
|
||||
/// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||
fn is_arithmetic_f32_nan(bits: u32) -> bool {
|
||||
const AF32_NAN: u32 = 0x0040_0000;
|
||||
(bits & AF32_NAN) == AF32_NAN
|
||||
const AF32_NAN: u32 = 0x7f80_0000;
|
||||
let is_nan = bits & AF32_NAN == AF32_NAN;
|
||||
const AF32_PAYLOAD_MSB: u32 = 0x0040_0000;
|
||||
let is_msb_set = bits & AF32_PAYLOAD_MSB == AF32_PAYLOAD_MSB;
|
||||
is_nan && is_msb_set
|
||||
}
|
||||
|
||||
/// Check if an f64 (as u64, see comments above) is an arithmetic NaN. This is the same as a
|
||||
/// canonical NaN including that the payload MSB is set to 1, but one or more of the remaining
|
||||
/// payload bits MAY BE set to 1 (a canonical NaN specifies all 0s). See
|
||||
/// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||
fn is_arithmetic_f64_nan(bits: u64) -> bool {
|
||||
const AF64_NAN: u64 = 0x0008_0000_0000_0000;
|
||||
(bits & AF64_NAN) == AF64_NAN
|
||||
const AF64_NAN: u64 = 0x7ff0_0000_0000_0000;
|
||||
let is_nan = bits & AF64_NAN == AF64_NAN;
|
||||
const AF64_PAYLOAD_MSB: u64 = 0x0008_0000_0000_0000;
|
||||
let is_msb_set = bits & AF64_PAYLOAD_MSB == AF64_PAYLOAD_MSB;
|
||||
is_nan && is_msb_set
|
||||
}
|
||||
|
||||
fn val_matches(actual: &Val, expected: &wast::AssertExpression) -> Result<bool> {
|
||||
@@ -474,3 +514,172 @@ fn v128_matches(actual: u128, expected: &wast::V128Pattern) -> bool {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// When troubleshooting a failure in a spec test, it is valuable to understand the bit-by-bit
|
||||
/// difference. To do this, we print a hex-encoded version of Wasm values and assertion expressions
|
||||
/// using this helper.
|
||||
fn as_hex_pattern<T>(bits: T) -> String
|
||||
where
|
||||
T: fmt::LowerHex,
|
||||
{
|
||||
format!("{1:#00$x}", size_of_val(&bits) * 2 + 2, bits)
|
||||
}
|
||||
|
||||
/// The [AsHexPattern] allows us to extend `as_hex_pattern` to various structures.
|
||||
trait AsHexPattern {
|
||||
fn as_hex_pattern(&self) -> String;
|
||||
}
|
||||
|
||||
impl AsHexPattern for wast::AssertExpression<'_> {
|
||||
fn as_hex_pattern(&self) -> String {
|
||||
match self {
|
||||
wast::AssertExpression::I32(i) => as_hex_pattern(*i),
|
||||
wast::AssertExpression::I64(i) => as_hex_pattern(*i),
|
||||
wast::AssertExpression::F32(f) => f.as_hex_pattern(),
|
||||
wast::AssertExpression::F64(f) => f.as_hex_pattern(),
|
||||
wast::AssertExpression::V128(v) => v.as_hex_pattern(),
|
||||
wast::AssertExpression::RefNull(_)
|
||||
| wast::AssertExpression::RefExtern(_)
|
||||
| wast::AssertExpression::RefFunc(_)
|
||||
| wast::AssertExpression::LegacyArithmeticNaN
|
||||
| wast::AssertExpression::LegacyCanonicalNaN => "no hex representation".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsHexPattern for wast::NanPattern<wast::Float32> {
|
||||
fn as_hex_pattern(&self) -> String {
|
||||
match self {
|
||||
wast::NanPattern::CanonicalNan => "0x7fc00000".to_string(),
|
||||
// Note that NaN patterns can have varying sign bits and payloads. Technically the first
|
||||
// bit should be a `*` but it is impossible to show that in hex.
|
||||
wast::NanPattern::ArithmeticNan => "0x7fc*****".to_string(),
|
||||
wast::NanPattern::Value(wast::Float32 { bits }) => as_hex_pattern(*bits),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsHexPattern for wast::NanPattern<wast::Float64> {
|
||||
fn as_hex_pattern(&self) -> String {
|
||||
match self {
|
||||
wast::NanPattern::CanonicalNan => "0x7ff8000000000000".to_string(),
|
||||
// Note that NaN patterns can have varying sign bits and payloads. Technically the first
|
||||
// bit should be a `*` but it is impossible to show that in hex.
|
||||
wast::NanPattern::ArithmeticNan => "0x7ff8************".to_string(),
|
||||
wast::NanPattern::Value(wast::Float64 { bits }) => as_hex_pattern(*bits),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This implementation reverses both the lanes and the lane bytes in order to match the Wasm SIMD
|
||||
// little-endian order. This implementation must include special behavior for this reversal; other
|
||||
// implementations do not because they deal with raw values (`u128`) or use big-endian order for
|
||||
// display (scalars).
|
||||
impl AsHexPattern for wast::V128Pattern {
|
||||
fn as_hex_pattern(&self) -> String {
|
||||
fn reverse_pattern(pattern: String) -> String {
|
||||
let chars: Vec<char> = pattern[2..].chars().collect();
|
||||
let reversed: Vec<&[char]> = chars.chunks(2).rev().collect();
|
||||
reversed.concat().iter().collect()
|
||||
}
|
||||
|
||||
fn as_hex_pattern(bits: &[u8]) -> String {
|
||||
bits.iter()
|
||||
.map(|b| format!("{:02x}", b))
|
||||
.collect::<Vec<_>>()
|
||||
.concat()
|
||||
}
|
||||
|
||||
fn reverse_lanes<T, F>(
|
||||
lanes: impl DoubleEndedIterator<Item = T>,
|
||||
as_hex_pattern: F,
|
||||
) -> String
|
||||
where
|
||||
F: Fn(T) -> String,
|
||||
{
|
||||
lanes
|
||||
.rev()
|
||||
.map(|f| as_hex_pattern(f))
|
||||
.collect::<Vec<_>>()
|
||||
.concat()
|
||||
}
|
||||
|
||||
let lanes_as_hex = match self {
|
||||
wast::V128Pattern::I8x16(v) => {
|
||||
reverse_lanes(v.iter(), |b| as_hex_pattern(&b.to_le_bytes()))
|
||||
}
|
||||
wast::V128Pattern::I16x8(v) => {
|
||||
reverse_lanes(v.iter(), |b| as_hex_pattern(&b.to_le_bytes()))
|
||||
}
|
||||
wast::V128Pattern::I32x4(v) => {
|
||||
reverse_lanes(v.iter(), |b| as_hex_pattern(&b.to_le_bytes()))
|
||||
}
|
||||
wast::V128Pattern::I64x2(v) => {
|
||||
reverse_lanes(v.iter(), |b| as_hex_pattern(&b.to_le_bytes()))
|
||||
}
|
||||
wast::V128Pattern::F32x4(v) => {
|
||||
reverse_lanes(v.iter(), |b| reverse_pattern(b.as_hex_pattern()))
|
||||
}
|
||||
wast::V128Pattern::F64x2(v) => {
|
||||
reverse_lanes(v.iter(), |b| reverse_pattern(b.as_hex_pattern()))
|
||||
}
|
||||
};
|
||||
|
||||
String::from("0x") + &lanes_as_hex
|
||||
}
|
||||
}
|
||||
|
||||
impl AsHexPattern for Val {
|
||||
fn as_hex_pattern(&self) -> String {
|
||||
match self {
|
||||
Val::I32(i) => as_hex_pattern(*i),
|
||||
Val::I64(i) => as_hex_pattern(*i),
|
||||
Val::F32(f) => as_hex_pattern(*f),
|
||||
Val::F64(f) => as_hex_pattern(*f),
|
||||
Val::V128(v) => as_hex_pattern(*v),
|
||||
Val::ExternRef(_) | Val::FuncRef(_) => "no hex representation".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn val_to_hex() {
|
||||
assert_eq!(Val::I32(0x42).as_hex_pattern(), "0x00000042");
|
||||
assert_eq!(Val::F64(0x0).as_hex_pattern(), "0x0000000000000000");
|
||||
assert_eq!(
|
||||
Val::V128(u128::from_le_bytes([
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
|
||||
]))
|
||||
.as_hex_pattern(),
|
||||
"0x0f0e0d0c0b0a09080706050403020100"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_expression_to_hex() {
|
||||
assert_eq!(
|
||||
wast::AssertExpression::F32(wast::NanPattern::ArithmeticNan).as_hex_pattern(),
|
||||
"0x7fc*****"
|
||||
);
|
||||
assert_eq!(
|
||||
wast::AssertExpression::F64(wast::NanPattern::Value(wast::Float64 { bits: 0x42 }))
|
||||
.as_hex_pattern(),
|
||||
"0x0000000000000042"
|
||||
);
|
||||
assert_eq!(
|
||||
wast::AssertExpression::V128(wast::V128Pattern::I32x4([0, 1, 2, 3])).as_hex_pattern(),
|
||||
"0x03000000020000000100000000000000"
|
||||
);
|
||||
assert_eq!(
|
||||
wast::AssertExpression::V128(wast::V128Pattern::F64x2([
|
||||
wast::NanPattern::CanonicalNan,
|
||||
wast::NanPattern::ArithmeticNan
|
||||
]))
|
||||
.as_hex_pattern(),
|
||||
"0x************f87f000000000000f87f"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user