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

This commit is contained in:
Pat Hickey
2020-12-14 16:17:10 -08:00
160 changed files with 7328 additions and 1466 deletions

View File

@@ -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.
*/
/**

View File

@@ -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

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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()))
}

View File

@@ -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]

View File

@@ -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()))
}

View File

@@ -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,
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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>>,

View File

@@ -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();

View File

@@ -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))
})?;

View File

@@ -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 }

View File

@@ -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"] }

View File

@@ -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>;
}

View File

@@ -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 {

View File

@@ -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(())
}
}

View File

@@ -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,
}
}

View File

@@ -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(

View File

@@ -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"]

View File

@@ -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)
}

View File

@@ -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(&params).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(&params);
}
}
@@ -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();
}
}
}

View File

@@ -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",
}
}

View File

@@ -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"] }

View File

@@ -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,

View File

@@ -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,
}
}
}

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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" }

View File

@@ -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

View File

@@ -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;

View File

@@ -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)
}
}

View File

@@ -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>>,
}

View File

@@ -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) {

View File

@@ -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) }
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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"]

View File

@@ -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,
}

View File

@@ -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 {

View File

@@ -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());
}
}

View File

@@ -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))),

View File

@@ -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(),
}
}
}

View File

@@ -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::*;

View File

@@ -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,
}
}

View File

@@ -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.

View File

@@ -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]);
}
}

View File

@@ -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 _,

View File

@@ -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),
)
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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(())
}

View File

@@ -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(),
}
}

View 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!(),
}
}
}

View File

@@ -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" }

View File

@@ -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"
);
}
}