Update the *.wast runner to use the wasmtime API (#690)
* Update the `*.wast` runner to use the `wasmtime` API This commit migrates the `wasmtime-wast` crate, which executes `*.wast` test suites, to use the `wasmtime` crate exclusively instead of the raw support provided by the `wasmtime-*` family of crates. The primary motivation for this change is to use `*.wast` test to test the support for interface types, but interface types is only being added in the `wasmtime` crate for now rather than all throughout the core crates. This means that without this transition it's much more difficult to write tests for wasm interface types! A secondary motivation for this is that it's testing the support we provide to users through the `wasmtime` crate, since that's the expectation of what most users would use rather than the raw `wasmtime-*` crates. * Run rustfmt * Fix the multi example * Handle v128 values in the `wasmtime` crate Ensure that we allocate 128-bit stack slots instead of 64-bit stack slots. * Update to master * Add comment
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -2188,6 +2188,7 @@ dependencies = [
|
|||||||
"wasmtime-environ",
|
"wasmtime-environ",
|
||||||
"wasmtime-jit",
|
"wasmtime-jit",
|
||||||
"wasmtime-runtime",
|
"wasmtime-runtime",
|
||||||
|
"wig",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2213,9 +2214,7 @@ name = "wasmtime-wast"
|
|||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"target-lexicon",
|
"wasmtime",
|
||||||
"wasmtime-environ",
|
|
||||||
"wasmtime-jit",
|
|
||||||
"wasmtime-runtime",
|
"wasmtime-runtime",
|
||||||
"wast 4.0.0",
|
"wast 4.0.0",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -45,7 +45,12 @@ const WAT: &str = r#"
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// Initialize.
|
// Initialize.
|
||||||
println!("Initializing...");
|
println!("Initializing...");
|
||||||
let engine = HostRef::new(Engine::default());
|
let mut cfg = Config::new();
|
||||||
|
cfg.features(wasmtime_jit::Features {
|
||||||
|
multi_value: true,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let engine = HostRef::new(Engine::new(&cfg));
|
||||||
let store = HostRef::new(Store::new(&engine));
|
let store = HostRef::new(Store::new(&engine));
|
||||||
|
|
||||||
// Load binary.
|
// Load binary.
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ impl WasmtimeFn {
|
|||||||
impl WrappedCallable for WasmtimeFn {
|
impl WrappedCallable for WasmtimeFn {
|
||||||
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), HostRef<Trap>> {
|
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), HostRef<Trap>> {
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::{mem, ptr};
|
use std::mem;
|
||||||
|
|
||||||
let (vmctx, body, signature) = match self.wasmtime_export() {
|
let (vmctx, body, signature) = match self.wasmtime_export() {
|
||||||
Export::Function {
|
Export::Function {
|
||||||
@@ -132,21 +132,14 @@ impl WrappedCallable for WasmtimeFn {
|
|||||||
_ => panic!("unexpected export type in Callable"),
|
_ => panic!("unexpected export type in Callable"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let value_size = mem::size_of::<u64>();
|
let value_size = mem::size_of::<u128>();
|
||||||
let mut values_vec: Vec<u64> = vec![0; max(params.len(), results.len())];
|
let mut values_vec = vec![0; max(params.len(), results.len())];
|
||||||
|
|
||||||
// Store the argument values into `values_vec`.
|
// Store the argument values into `values_vec`.
|
||||||
for (index, arg) in params.iter().enumerate() {
|
for (index, arg) in params.iter().enumerate() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = values_vec.as_mut_ptr().add(index);
|
let ptr = values_vec.as_mut_ptr().add(index);
|
||||||
|
arg.write_value_to(ptr);
|
||||||
match arg {
|
|
||||||
Val::I32(x) => ptr::write(ptr as *mut i32, *x),
|
|
||||||
Val::I64(x) => ptr::write(ptr as *mut i64, *x),
|
|
||||||
Val::F32(x) => ptr::write(ptr as *mut u32, *x),
|
|
||||||
Val::F64(x) => ptr::write(ptr as *mut u64, *x),
|
|
||||||
_ => unimplemented!("WasmtimeFn arg"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,13 +169,7 @@ impl WrappedCallable for WasmtimeFn {
|
|||||||
unsafe {
|
unsafe {
|
||||||
let ptr = values_vec.as_ptr().add(index);
|
let ptr = values_vec.as_ptr().add(index);
|
||||||
|
|
||||||
results[index] = match abi_param.value_type {
|
results[index] = Val::read_value_from(ptr, abi_param.value_type);
|
||||||
ir::types::I32 => Val::I32(ptr::read(ptr as *const i32)),
|
|
||||||
ir::types::I64 => Val::I64(ptr::read(ptr as *const i64)),
|
|
||||||
ir::types::F32 => Val::F32(ptr::read(ptr as *const u32)),
|
|
||||||
ir::types::F64 => Val::F64(ptr::read(ptr as *const u64)),
|
|
||||||
other => panic!("unsupported value type {:?}", other),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -201,14 +201,15 @@ impl Module {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn validate(_store: &HostRef<Store>, binary: &[u8]) -> Result<()> {
|
pub fn validate(store: &HostRef<Store>, binary: &[u8]) -> Result<()> {
|
||||||
|
let features = store.borrow().engine().borrow().config.features.clone();
|
||||||
let config = ValidatingParserConfig {
|
let config = ValidatingParserConfig {
|
||||||
operator_config: OperatorValidatorConfig {
|
operator_config: OperatorValidatorConfig {
|
||||||
enable_threads: false,
|
enable_threads: features.threads,
|
||||||
enable_reference_types: false,
|
enable_reference_types: features.reference_types,
|
||||||
enable_bulk_memory: false,
|
enable_bulk_memory: features.bulk_memory,
|
||||||
enable_simd: false,
|
enable_simd: features.simd,
|
||||||
enable_multi_value: true,
|
enable_multi_value: features.multi_value,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
validate(binary, Some(config)).map_err(Error::new)
|
validate(binary, Some(config)).map_err(Error::new)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ impl Default for Config {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
config: Config,
|
pub(crate) config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl Drop for TrampolineState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, call_id: u32, values_vec: *mut i64) -> u32 {
|
unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, call_id: u32, values_vec: *mut i128) -> u32 {
|
||||||
let mut instance = InstanceHandle::from_vmctx(vmctx);
|
let mut instance = InstanceHandle::from_vmctx(vmctx);
|
||||||
|
|
||||||
let (args, returns_len) = {
|
let (args, returns_len) = {
|
||||||
@@ -130,7 +130,10 @@ fn make_trampoline(
|
|||||||
// Add error/trap return.
|
// Add error/trap return.
|
||||||
stub_sig.returns.push(ir::AbiParam::new(types::I32));
|
stub_sig.returns.push(ir::AbiParam::new(types::I32));
|
||||||
|
|
||||||
let values_vec_len = 8 * cmp::max(signature.params.len() - 1, signature.returns.len()) as u32;
|
let value_size = 16;
|
||||||
|
let values_vec_len = ((value_size as usize)
|
||||||
|
* cmp::max(signature.params.len() - 1, signature.returns.len()))
|
||||||
|
as u32;
|
||||||
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone());
|
context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone());
|
||||||
@@ -139,7 +142,6 @@ fn make_trampoline(
|
|||||||
StackSlotKind::ExplicitSlot,
|
StackSlotKind::ExplicitSlot,
|
||||||
values_vec_len,
|
values_vec_len,
|
||||||
));
|
));
|
||||||
let value_size = 8;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
|
||||||
|
|||||||
@@ -83,22 +83,24 @@ impl Val {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn write_value_to(&self, p: *mut i64) {
|
pub(crate) unsafe fn write_value_to(&self, p: *mut i128) {
|
||||||
match self {
|
match self {
|
||||||
Val::I32(i) => ptr::write(p as *mut i32, *i),
|
Val::I32(i) => ptr::write(p as *mut i32, *i),
|
||||||
Val::I64(i) => ptr::write(p as *mut i64, *i),
|
Val::I64(i) => ptr::write(p as *mut i64, *i),
|
||||||
Val::F32(u) => ptr::write(p as *mut u32, *u),
|
Val::F32(u) => ptr::write(p as *mut u32, *u),
|
||||||
Val::F64(u) => ptr::write(p as *mut u64, *u),
|
Val::F64(u) => ptr::write(p as *mut u64, *u),
|
||||||
|
Val::V128(b) => ptr::write(p as *mut u128, *b),
|
||||||
_ => unimplemented!("Val::write_value_to"),
|
_ => unimplemented!("Val::write_value_to"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn read_value_from(p: *const i64, ty: ir::Type) -> Val {
|
pub(crate) unsafe fn read_value_from(p: *const i128, ty: ir::Type) -> Val {
|
||||||
match ty {
|
match ty {
|
||||||
ir::types::I32 => Val::I32(ptr::read(p as *const i32)),
|
ir::types::I32 => Val::I32(ptr::read(p as *const i32)),
|
||||||
ir::types::I64 => Val::I64(ptr::read(p as *const i64)),
|
ir::types::I64 => Val::I64(ptr::read(p as *const i64)),
|
||||||
ir::types::F32 => Val::F32(ptr::read(p as *const u32)),
|
ir::types::F32 => Val::F32(ptr::read(p as *const u32)),
|
||||||
ir::types::F64 => Val::F64(ptr::read(p as *const u64)),
|
ir::types::F64 => Val::F64(ptr::read(p as *const u64)),
|
||||||
|
ir::types::I8X16 => Val::V128(ptr::read(p as *const u128)),
|
||||||
_ => unimplemented!("Val::read_value_from"),
|
_ => unimplemented!("Val::read_value_from"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,10 @@ readme = "README.md"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime-jit = { path = "../jit" }
|
|
||||||
wasmtime-runtime = { path = "../runtime" }
|
|
||||||
wasmtime-environ = { path = "../environ" }
|
|
||||||
wast = "4.0.0"
|
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
target-lexicon = "0.9.0"
|
wasmtime = { path = "../api" }
|
||||||
|
wasmtime-runtime = { path = "../runtime" }
|
||||||
|
wast = "4.0.0"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|||||||
@@ -1,233 +1,105 @@
|
|||||||
#![allow(improper_ctypes)]
|
#![allow(improper_ctypes)]
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use anyhow::Result;
|
||||||
use std::collections::hash_map::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use target_lexicon::HOST;
|
use wasmtime::*;
|
||||||
use wasmtime_environ::entity::PrimaryMap;
|
|
||||||
use wasmtime_environ::ir::types;
|
|
||||||
use wasmtime_environ::wasm::{
|
|
||||||
DefinedFuncIndex, Global, GlobalInit, Memory, Table, TableElementType,
|
|
||||||
};
|
|
||||||
use wasmtime_environ::{ir, isa};
|
|
||||||
use wasmtime_environ::{translate_signature, Export, MemoryPlan, Module, TablePlan};
|
|
||||||
use wasmtime_jit::target_tunables;
|
|
||||||
use wasmtime_runtime::{Imports, InstanceHandle, InstantiationError, VMContext, VMFunctionBody};
|
|
||||||
|
|
||||||
extern "C" fn spectest_print() {}
|
struct MyCall<F>(F);
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
impl<F> Callable for MyCall<F>
|
||||||
extern "C" fn spectest_print_i32(_vmctx: &mut VMContext, x: i32) {
|
where
|
||||||
println!("{}: i32", x);
|
F: Fn(&[Val], &mut [Val]) -> Result<(), HostRef<Trap>>,
|
||||||
|
{
|
||||||
|
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), HostRef<Trap>> {
|
||||||
|
(self.0)(params, results)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
fn wrap(
|
||||||
extern "C" fn spectest_print_i64(_vmctx: &mut VMContext, x: i64) {
|
store: &HostRef<Store>,
|
||||||
println!("{}: i64", x);
|
ty: FuncType,
|
||||||
}
|
callable: impl Fn(&[Val], &mut [Val]) -> Result<(), HostRef<Trap>> + 'static,
|
||||||
|
) -> Func {
|
||||||
#[allow(clippy::print_stdout)]
|
Func::new(store, ty, Rc::new(MyCall(callable)))
|
||||||
extern "C" fn spectest_print_f32(_vmctx: &mut VMContext, x: f32) {
|
|
||||||
println!("{}: f32", x);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
|
||||||
extern "C" fn spectest_print_f64(_vmctx: &mut VMContext, x: f64) {
|
|
||||||
println!("{}: f64", x);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
|
||||||
extern "C" fn spectest_print_i32_f32(_vmctx: &mut VMContext, x: i32, y: f32) {
|
|
||||||
println!("{}: i32", x);
|
|
||||||
println!("{}: f32", y);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
|
||||||
extern "C" fn spectest_print_f64_f64(_vmctx: &mut VMContext, x: f64, y: f64) {
|
|
||||||
println!("{}: f64", x);
|
|
||||||
println!("{}: f64", y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an instance implementing the "spectest" interface used in the
|
/// Return an instance implementing the "spectest" interface used in the
|
||||||
/// spec testsuite.
|
/// spec testsuite.
|
||||||
pub fn instantiate_spectest() -> Result<InstanceHandle, InstantiationError> {
|
pub fn instantiate_spectest(store: &HostRef<Store>) -> HashMap<&'static str, Extern> {
|
||||||
let call_conv = isa::CallConv::triple_default(&HOST);
|
let mut ret = HashMap::new();
|
||||||
let pointer_type = types::Type::triple_pointer_type(&HOST);
|
|
||||||
let mut module = Module::new();
|
|
||||||
let mut finished_functions: PrimaryMap<DefinedFuncIndex, *const VMFunctionBody> =
|
|
||||||
PrimaryMap::new();
|
|
||||||
|
|
||||||
let sig = module.signatures.push(translate_signature(
|
let ty = FuncType::new(Box::new([]), Box::new([]));
|
||||||
ir::Signature {
|
let func = wrap(store, ty, |_params, _results| Ok(()));
|
||||||
params: vec![],
|
ret.insert("print", Extern::Func(HostRef::new(func)));
|
||||||
returns: vec![],
|
|
||||||
call_conv,
|
|
||||||
},
|
|
||||||
pointer_type,
|
|
||||||
));
|
|
||||||
let func = module.functions.push(sig);
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("print".to_owned(), Export::Function(func));
|
|
||||||
finished_functions.push(spectest_print as *const VMFunctionBody);
|
|
||||||
|
|
||||||
let sig = module.signatures.push(translate_signature(
|
let ty = FuncType::new(Box::new([ValType::I32]), Box::new([]));
|
||||||
ir::Signature {
|
let func = wrap(store, ty, |params, _results| {
|
||||||
params: vec![ir::AbiParam::new(types::I32)],
|
println!("{}: i32", params[0].unwrap_i32());
|
||||||
returns: vec![],
|
Ok(())
|
||||||
call_conv,
|
|
||||||
},
|
|
||||||
pointer_type,
|
|
||||||
));
|
|
||||||
let func = module.functions.push(sig);
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("print_i32".to_owned(), Export::Function(func));
|
|
||||||
finished_functions.push(spectest_print_i32 as *const VMFunctionBody);
|
|
||||||
|
|
||||||
let sig = module.signatures.push(translate_signature(
|
|
||||||
ir::Signature {
|
|
||||||
params: vec![ir::AbiParam::new(types::I64)],
|
|
||||||
returns: vec![],
|
|
||||||
call_conv,
|
|
||||||
},
|
|
||||||
pointer_type,
|
|
||||||
));
|
|
||||||
let func = module.functions.push(sig);
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("print_i64".to_owned(), Export::Function(func));
|
|
||||||
finished_functions.push(spectest_print_i64 as *const VMFunctionBody);
|
|
||||||
|
|
||||||
let sig = module.signatures.push(translate_signature(
|
|
||||||
ir::Signature {
|
|
||||||
params: vec![ir::AbiParam::new(types::F32)],
|
|
||||||
returns: vec![],
|
|
||||||
call_conv,
|
|
||||||
},
|
|
||||||
pointer_type,
|
|
||||||
));
|
|
||||||
let func = module.functions.push(sig);
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("print_f32".to_owned(), Export::Function(func));
|
|
||||||
finished_functions.push(spectest_print_f32 as *const VMFunctionBody);
|
|
||||||
|
|
||||||
let sig = module.signatures.push(translate_signature(
|
|
||||||
ir::Signature {
|
|
||||||
params: vec![ir::AbiParam::new(types::F64)],
|
|
||||||
returns: vec![],
|
|
||||||
call_conv,
|
|
||||||
},
|
|
||||||
pointer_type,
|
|
||||||
));
|
|
||||||
let func = module.functions.push(sig);
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("print_f64".to_owned(), Export::Function(func));
|
|
||||||
finished_functions.push(spectest_print_f64 as *const VMFunctionBody);
|
|
||||||
|
|
||||||
let sig = module.signatures.push(translate_signature(
|
|
||||||
ir::Signature {
|
|
||||||
params: vec![ir::AbiParam::new(types::I32), ir::AbiParam::new(types::F32)],
|
|
||||||
returns: vec![],
|
|
||||||
call_conv,
|
|
||||||
},
|
|
||||||
pointer_type,
|
|
||||||
));
|
|
||||||
let func = module.functions.push(sig);
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("print_i32_f32".to_owned(), Export::Function(func));
|
|
||||||
finished_functions.push(spectest_print_i32_f32 as *const VMFunctionBody);
|
|
||||||
|
|
||||||
let sig = module.signatures.push(translate_signature(
|
|
||||||
ir::Signature {
|
|
||||||
params: vec![ir::AbiParam::new(types::F64), ir::AbiParam::new(types::F64)],
|
|
||||||
returns: vec![],
|
|
||||||
call_conv,
|
|
||||||
},
|
|
||||||
pointer_type,
|
|
||||||
));
|
|
||||||
let func = module.functions.push(sig);
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("print_f64_f64".to_owned(), Export::Function(func));
|
|
||||||
finished_functions.push(spectest_print_f64_f64 as *const VMFunctionBody);
|
|
||||||
|
|
||||||
let global = module.globals.push(Global {
|
|
||||||
ty: types::I32,
|
|
||||||
mutability: false,
|
|
||||||
initializer: GlobalInit::I32Const(666),
|
|
||||||
});
|
});
|
||||||
module
|
ret.insert("print_i32", Extern::Func(HostRef::new(func)));
|
||||||
.exports
|
|
||||||
.insert("global_i32".to_owned(), Export::Global(global));
|
|
||||||
|
|
||||||
let global = module.globals.push(Global {
|
let ty = FuncType::new(Box::new([ValType::I64]), Box::new([]));
|
||||||
ty: types::I64,
|
let func = wrap(store, ty, |params, _results| {
|
||||||
mutability: false,
|
println!("{}: i64", params[0].unwrap_i64());
|
||||||
initializer: GlobalInit::I64Const(666),
|
Ok(())
|
||||||
});
|
});
|
||||||
module
|
ret.insert("print_i64", Extern::Func(HostRef::new(func)));
|
||||||
.exports
|
|
||||||
.insert("global_i64".to_owned(), Export::Global(global));
|
|
||||||
|
|
||||||
let global = module.globals.push(Global {
|
let ty = FuncType::new(Box::new([ValType::F32]), Box::new([]));
|
||||||
ty: types::F32,
|
let func = wrap(store, ty, |params, _results| {
|
||||||
mutability: false,
|
println!("{}: f32", params[0].unwrap_f32());
|
||||||
initializer: GlobalInit::F32Const(0x44268000),
|
Ok(())
|
||||||
});
|
});
|
||||||
module
|
ret.insert("print_f32", Extern::Func(HostRef::new(func)));
|
||||||
.exports
|
|
||||||
.insert("global_f32".to_owned(), Export::Global(global));
|
|
||||||
|
|
||||||
let global = module.globals.push(Global {
|
let ty = FuncType::new(Box::new([ValType::F64]), Box::new([]));
|
||||||
ty: types::F64,
|
let func = wrap(store, ty, |params, _results| {
|
||||||
mutability: false,
|
println!("{}: f64", params[0].unwrap_f64());
|
||||||
initializer: GlobalInit::F64Const(0x4084d00000000000),
|
Ok(())
|
||||||
});
|
});
|
||||||
module
|
ret.insert("print_f64", Extern::Func(HostRef::new(func)));
|
||||||
.exports
|
|
||||||
.insert("global_f64".to_owned(), Export::Global(global));
|
|
||||||
|
|
||||||
let tunables = target_tunables(&HOST);
|
let ty = FuncType::new(Box::new([ValType::I32, ValType::F32]), Box::new([]));
|
||||||
let table = module.table_plans.push(TablePlan::for_table(
|
let func = wrap(store, ty, |params, _results| {
|
||||||
Table {
|
println!("{}: i32", params[0].unwrap_i32());
|
||||||
ty: TableElementType::Func,
|
println!("{}: f32", params[1].unwrap_f32());
|
||||||
minimum: 10,
|
Ok(())
|
||||||
maximum: Some(20),
|
});
|
||||||
},
|
ret.insert("print_i32_f32", Extern::Func(HostRef::new(func)));
|
||||||
&tunables,
|
|
||||||
));
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("table".to_owned(), Export::Table(table));
|
|
||||||
|
|
||||||
let memory = module.memory_plans.push(MemoryPlan::for_memory(
|
let ty = FuncType::new(Box::new([ValType::F64, ValType::F64]), Box::new([]));
|
||||||
Memory {
|
let func = wrap(store, ty, |params, _results| {
|
||||||
minimum: 1,
|
println!("{}: f64", params[0].unwrap_f64());
|
||||||
maximum: Some(2),
|
println!("{}: f64", params[1].unwrap_f64());
|
||||||
shared: false,
|
Ok(())
|
||||||
},
|
});
|
||||||
&tunables,
|
ret.insert("print_f64_f64", Extern::Func(HostRef::new(func)));
|
||||||
));
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.insert("memory".to_owned(), Export::Memory(memory));
|
|
||||||
|
|
||||||
let imports = Imports::none();
|
let ty = GlobalType::new(ValType::I32, Mutability::Const);
|
||||||
let data_initializers = Vec::new();
|
let g = Global::new(store, ty, Val::I32(666));
|
||||||
let signatures = PrimaryMap::new();
|
ret.insert("global_i32", Extern::Global(HostRef::new(g)));
|
||||||
|
|
||||||
InstanceHandle::new(
|
let ty = GlobalType::new(ValType::I64, Mutability::Const);
|
||||||
Rc::new(module),
|
let g = Global::new(store, ty, Val::I64(666));
|
||||||
Rc::new(RefCell::new(HashMap::new())),
|
ret.insert("global_i64", Extern::Global(HostRef::new(g)));
|
||||||
finished_functions.into_boxed_slice(),
|
|
||||||
imports,
|
let ty = GlobalType::new(ValType::F32, Mutability::Const);
|
||||||
&data_initializers,
|
let g = Global::new(store, ty, Val::F32(0x44268000));
|
||||||
signatures.into_boxed_slice(),
|
ret.insert("global_f32", Extern::Global(HostRef::new(g)));
|
||||||
None,
|
|
||||||
Box::new(()),
|
let ty = GlobalType::new(ValType::F64, Mutability::Const);
|
||||||
)
|
let g = Global::new(store, ty, Val::F64(0x4084d00000000000));
|
||||||
|
ret.insert("global_f64", Extern::Global(HostRef::new(g)));
|
||||||
|
|
||||||
|
let ty = TableType::new(ValType::FuncRef, Limits::new(10, Some(20)));
|
||||||
|
let table = Table::new(store, ty, Val::AnyRef(AnyRef::Null));
|
||||||
|
ret.insert("table", Extern::Table(HostRef::new(table)));
|
||||||
|
|
||||||
|
let ty = MemoryType::new(Limits::new(1, Some(2)));
|
||||||
|
let memory = Memory::new(store, ty);
|
||||||
|
ret.insert("memory", Extern::Memory(HostRef::new(memory)));
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
use crate::spectest::instantiate_spectest;
|
use crate::spectest::instantiate_spectest;
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{anyhow, bail, Context as _, Result};
|
||||||
use std::convert::TryInto;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str;
|
use std::str;
|
||||||
use wasmtime_jit::{
|
use wasmtime::*;
|
||||||
ActionError, ActionOutcome, Compiler, Context, Features, InstanceHandle, InstantiationError,
|
|
||||||
RuntimeValue, SetupError,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Translate from a `script::Value` to a `RuntimeValue`.
|
/// Translate from a `script::Value` to a `RuntimeValue`.
|
||||||
fn runtime_value(v: &wast::Expression<'_>) -> RuntimeValue {
|
fn runtime_value(v: &wast::Expression<'_>) -> Result<Val> {
|
||||||
use wast::Instruction::*;
|
use wast::Instruction::*;
|
||||||
|
|
||||||
if v.instrs.len() != 1 {
|
if v.instrs.len() != 1 {
|
||||||
panic!("too many instructions in {:?}", v);
|
bail!("too many instructions in {:?}", v);
|
||||||
}
|
|
||||||
match &v.instrs[0] {
|
|
||||||
I32Const(x) => RuntimeValue::I32(*x),
|
|
||||||
I64Const(x) => RuntimeValue::I64(*x),
|
|
||||||
F32Const(x) => RuntimeValue::F32(x.bits),
|
|
||||||
F64Const(x) => RuntimeValue::F64(x.bits),
|
|
||||||
V128Const(x) => RuntimeValue::V128(x.to_le_bytes()),
|
|
||||||
other => panic!("couldn't convert {:?} to a runtime value", other),
|
|
||||||
}
|
}
|
||||||
|
Ok(match &v.instrs[0] {
|
||||||
|
I32Const(x) => Val::I32(*x),
|
||||||
|
I64Const(x) => Val::I64(*x),
|
||||||
|
F32Const(x) => Val::F32(x.bits),
|
||||||
|
F64Const(x) => Val::F64(x.bits),
|
||||||
|
V128Const(x) => Val::V128(u128::from_le_bytes(x.to_le_bytes())),
|
||||||
|
other => bail!("couldn't convert {:?} to a runtime value", other),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wast test script language allows modules to be defined and actions
|
/// The wast test script language allows modules to be defined and actions
|
||||||
@@ -30,85 +27,131 @@ fn runtime_value(v: &wast::Expression<'_>) -> RuntimeValue {
|
|||||||
pub struct WastContext {
|
pub struct WastContext {
|
||||||
/// Wast files have a concept of a "current" module, which is the most
|
/// Wast files have a concept of a "current" module, which is the most
|
||||||
/// recently defined.
|
/// recently defined.
|
||||||
current: Option<InstanceHandle>,
|
current: Option<HostRef<Instance>>,
|
||||||
|
|
||||||
context: Context,
|
instances: HashMap<String, HostRef<Instance>>,
|
||||||
|
store: HostRef<Store>,
|
||||||
|
spectest: Option<HashMap<&'static str, Extern>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Outcome<T = Vec<Val>> {
|
||||||
|
Ok(T),
|
||||||
|
Trap(HostRef<Trap>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WastContext {
|
impl WastContext {
|
||||||
/// Construct a new instance of `WastContext`.
|
/// Construct a new instance of `WastContext`.
|
||||||
pub fn new(compiler: Box<Compiler>) -> Self {
|
pub fn new(store: HostRef<Store>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: None,
|
current: None,
|
||||||
context: Context::new(compiler),
|
store,
|
||||||
|
spectest: None,
|
||||||
|
instances: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a new instance with the given features using the current `Context`
|
fn get_instance(&self, instance_name: Option<&str>) -> Result<HostRef<Instance>> {
|
||||||
pub fn with_features(self, features: Features) -> Self {
|
match instance_name {
|
||||||
Self {
|
Some(name) => self
|
||||||
context: self.context.with_features(features),
|
.instances
|
||||||
..self
|
.get(name)
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| anyhow!("failed to find instance named `{}`", name)),
|
||||||
|
None => self
|
||||||
|
.current
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| anyhow!("no previous instance found")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_instance(&mut self, instance_name: Option<&str>) -> Result<&mut InstanceHandle> {
|
fn instantiate(&self, module: &[u8]) -> Result<Outcome<HostRef<Instance>>> {
|
||||||
let instance = if let Some(instance_name) = instance_name {
|
let module = HostRef::new(Module::new(&self.store, module)?);
|
||||||
self.context
|
let mut imports = Vec::new();
|
||||||
.get_instance(instance_name)
|
for import in module.borrow().imports() {
|
||||||
.context("failed to fetch instance")?
|
if import.module() == "spectest" {
|
||||||
} else {
|
let spectest = self
|
||||||
self.current
|
.spectest
|
||||||
.as_mut()
|
.as_ref()
|
||||||
.ok_or_else(|| anyhow::format_err!("no current instance"))?
|
.ok_or_else(|| anyhow!("spectest module isn't instantiated"))?;
|
||||||
|
let export = spectest
|
||||||
|
.get(import.name())
|
||||||
|
.ok_or_else(|| anyhow!("unknown import `spectest::{}`", import.name()))?;
|
||||||
|
imports.push(export.clone());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = self
|
||||||
|
.instances
|
||||||
|
.get(import.module())
|
||||||
|
.ok_or_else(|| anyhow!("no module named `{}`", import.module()))?;
|
||||||
|
let export = instance
|
||||||
|
.borrow()
|
||||||
|
.find_export_by_name(import.name())
|
||||||
|
.ok_or_else(|| anyhow!("unknown import `{}::{}`", import.name(), import.module()))?
|
||||||
|
.clone();
|
||||||
|
imports.push(export);
|
||||||
|
}
|
||||||
|
let instance = match Instance::new(&self.store, &module, &imports) {
|
||||||
|
Ok(i) => i,
|
||||||
|
// FIXME(#683) shouldn't have to reach into runtime crate
|
||||||
|
Err(e) => {
|
||||||
|
use wasmtime_runtime::InstantiationError;
|
||||||
|
let err = e
|
||||||
|
.chain()
|
||||||
|
.filter_map(|e| e.downcast_ref::<InstantiationError>())
|
||||||
|
.next();
|
||||||
|
if let Some(InstantiationError::StartTrap(msg)) = err {
|
||||||
|
return Ok(Outcome::Trap(HostRef::new(Trap::new(msg.clone()))));
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
Ok(Outcome::Ok(HostRef::new(instance)))
|
||||||
Ok(instance)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register "spectest" which is used by the spec testsuite.
|
/// Register "spectest" which is used by the spec testsuite.
|
||||||
pub fn register_spectest(&mut self) -> Result<()> {
|
pub fn register_spectest(&mut self) -> Result<()> {
|
||||||
let instance = instantiate_spectest()?;
|
self.spectest = Some(instantiate_spectest(&self.store));
|
||||||
self.context.name_instance("spectest".to_owned(), instance);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the action portion of a command.
|
/// Perform the action portion of a command.
|
||||||
fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result<ActionOutcome> {
|
fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result<Outcome> {
|
||||||
match exec {
|
match exec {
|
||||||
wast::WastExecute::Invoke(invoke) => self.perform_invoke(invoke),
|
wast::WastExecute::Invoke(invoke) => self.perform_invoke(invoke),
|
||||||
wast::WastExecute::Module(mut module) => {
|
wast::WastExecute::Module(mut module) => {
|
||||||
let binary = module.encode()?;
|
let binary = module.encode()?;
|
||||||
let result = self.context.instantiate_module(None, &binary);
|
let result = self.instantiate(&binary)?;
|
||||||
match result {
|
Ok(match result {
|
||||||
Ok(_) => Ok(ActionOutcome::Returned { values: Vec::new() }),
|
Outcome::Ok(_) => Outcome::Ok(Vec::new()),
|
||||||
Err(ActionError::Setup(SetupError::Instantiate(
|
Outcome::Trap(e) => Outcome::Trap(e),
|
||||||
InstantiationError::StartTrap(message),
|
})
|
||||||
))) => Ok(ActionOutcome::Trapped { message }),
|
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
wast::WastExecute::Get { module, global } => self.get(module.map(|s| s.name()), global),
|
wast::WastExecute::Get { module, global } => self.get(module.map(|s| s.name()), global),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_invoke(&mut self, exec: wast::WastInvoke<'_>) -> Result<ActionOutcome> {
|
fn perform_invoke(&mut self, exec: wast::WastInvoke<'_>) -> Result<Outcome> {
|
||||||
self.invoke(exec.module.map(|i| i.name()), exec.name, &exec.args)
|
self.invoke(exec.module.map(|i| i.name()), exec.name, &exec.args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a module and register it.
|
/// Define a module and register it.
|
||||||
fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<()> {
|
fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<()> {
|
||||||
let index = self
|
let instance = match self.instantiate(module)? {
|
||||||
.context
|
Outcome::Ok(i) => i,
|
||||||
.instantiate_module(instance_name.map(|s| s.to_string()), module)?;
|
Outcome::Trap(e) => bail!("instantiation failed with: {}", e.borrow().message()),
|
||||||
self.current = Some(index);
|
};
|
||||||
|
if let Some(name) = instance_name {
|
||||||
|
self.instances.insert(name.to_string(), instance.clone());
|
||||||
|
}
|
||||||
|
self.current = Some(instance);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register an instance to make it available for performing actions.
|
/// Register an instance to make it available for performing actions.
|
||||||
fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
|
fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> {
|
||||||
let instance = self.get_instance(name)?.clone();
|
let instance = self.get_instance(name)?.clone();
|
||||||
self.context.name_instance(as_name.to_string(), instance);
|
self.instances.insert(as_name.to_string(), instance);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,26 +161,35 @@ impl WastContext {
|
|||||||
instance_name: Option<&str>,
|
instance_name: Option<&str>,
|
||||||
field: &str,
|
field: &str,
|
||||||
args: &[wast::Expression],
|
args: &[wast::Expression],
|
||||||
) -> Result<ActionOutcome> {
|
) -> Result<Outcome> {
|
||||||
let value_args = args.iter().map(runtime_value).collect::<Vec<_>>();
|
let values = args.iter().map(runtime_value).collect::<Result<Vec<_>>>()?;
|
||||||
let mut instance = self.get_instance(instance_name)?.clone();
|
let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?;
|
||||||
let result = self
|
let instance = instance.borrow();
|
||||||
.context
|
let export = instance
|
||||||
.invoke(&mut instance, field, &value_args)
|
.find_export_by_name(field)
|
||||||
.with_context(|| format!("failed to invoke `{}`", field))?;
|
.ok_or_else(|| anyhow!("no global named `{}`", field))?;
|
||||||
Ok(result)
|
let func = match export {
|
||||||
|
Extern::Func(f) => f.borrow(),
|
||||||
|
_ => bail!("export of `{}` wasn't a global", field),
|
||||||
|
};
|
||||||
|
Ok(match func.call(&values) {
|
||||||
|
Ok(result) => Outcome::Ok(result.into()),
|
||||||
|
Err(e) => Outcome::Trap(e),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of an exported global from an instance.
|
/// Get the value of an exported global from an instance.
|
||||||
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<ActionOutcome> {
|
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
|
||||||
let instance = self
|
let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?;
|
||||||
.get_instance(instance_name.as_ref().map(|x| &**x))?
|
let instance = instance.borrow();
|
||||||
.clone();
|
let export = instance
|
||||||
let result = self
|
.find_export_by_name(field)
|
||||||
.context
|
.ok_or_else(|| anyhow!("no global named `{}`", field))?;
|
||||||
.get(&instance, field)
|
let global = match export {
|
||||||
.with_context(|| format!("failed to get field `{}`", field))?;
|
Extern::Global(g) => g.borrow(),
|
||||||
Ok(result)
|
_ => bail!("export of `{}` wasn't a global", field),
|
||||||
|
};
|
||||||
|
Ok(Outcome::Ok(vec![global.get()]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a wast script from a byte buffer.
|
/// Run a wast script from a byte buffer.
|
||||||
@@ -179,16 +231,18 @@ impl WastContext {
|
|||||||
exec,
|
exec,
|
||||||
results,
|
results,
|
||||||
} => match self.perform_execute(exec).with_context(|| context(span))? {
|
} => match self.perform_execute(exec).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
for (v, e) in values.iter().zip(results.iter().map(runtime_value)) {
|
for (v, e) in values.iter().zip(results.iter().map(runtime_value)) {
|
||||||
if *v == e {
|
let e = e?;
|
||||||
|
if values_equal(v, &e)? {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bail!("{}\nexpected {}, got {}", context(span), e, v)
|
bail!("{}\nexpected {:?}, got {:?}", context(span), e, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActionOutcome::Trapped { message } => {
|
Outcome::Trap(t) => {
|
||||||
bail!("{}\nunexpected trap: {}", context(span), message)
|
let t = t.borrow();
|
||||||
|
bail!("{}\nunexpected trap: {}", context(span), t.message())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AssertTrap {
|
AssertTrap {
|
||||||
@@ -196,13 +250,12 @@ impl WastContext {
|
|||||||
exec,
|
exec,
|
||||||
message,
|
message,
|
||||||
} => match self.perform_execute(exec).with_context(|| context(span))? {
|
} => match self.perform_execute(exec).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
bail!("{}\nexpected trap, got {:?}", context(span), values)
|
bail!("{}\nexpected trap, got {:?}", context(span), values)
|
||||||
}
|
}
|
||||||
ActionOutcome::Trapped {
|
Outcome::Trap(t) => {
|
||||||
message: trap_message,
|
let t = t.borrow();
|
||||||
} => {
|
if t.message().contains(message) {
|
||||||
if trap_message.contains(message) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if cfg!(feature = "lightbeam") {
|
if cfg!(feature = "lightbeam") {
|
||||||
@@ -217,7 +270,7 @@ impl WastContext {
|
|||||||
"{}\nexpected {}, got {}",
|
"{}\nexpected {}, got {}",
|
||||||
context(span),
|
context(span),
|
||||||
message,
|
message,
|
||||||
trap_message
|
t.message(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -226,67 +279,57 @@ impl WastContext {
|
|||||||
call,
|
call,
|
||||||
message,
|
message,
|
||||||
} => match self.perform_invoke(call).with_context(|| context(span))? {
|
} => match self.perform_invoke(call).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
bail!("{}\nexpected trap, got {:?}", context(span), values)
|
bail!("{}\nexpected trap, got {:?}", context(span), values)
|
||||||
}
|
}
|
||||||
ActionOutcome::Trapped {
|
Outcome::Trap(t) => {
|
||||||
message: trap_message,
|
let t = t.borrow();
|
||||||
} => {
|
if t.message().contains(message) {
|
||||||
if trap_message.contains(message) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bail!(
|
bail!(
|
||||||
"{}\nexpected exhaustion with {}, got {}",
|
"{}\nexpected exhaustion with {}, got {}",
|
||||||
context(span),
|
context(span),
|
||||||
message,
|
message,
|
||||||
trap_message
|
t.message(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AssertReturnCanonicalNan { span, invoke } => {
|
AssertReturnCanonicalNan { span, invoke } => {
|
||||||
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
Val::F32(x) => {
|
||||||
bail!("{}\nunexpected integer in NaN test", context(span))
|
|
||||||
}
|
|
||||||
RuntimeValue::V128(_) => {
|
|
||||||
bail!("{}\nunexpected vector in NaN test", context(span))
|
|
||||||
}
|
|
||||||
RuntimeValue::F32(x) => {
|
|
||||||
if !is_canonical_f32_nan(x) {
|
if !is_canonical_f32_nan(x) {
|
||||||
bail!("{}\nexpected canonical NaN", context(span))
|
bail!("{}\nexpected canonical NaN", context(span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeValue::F64(x) => {
|
Val::F64(x) => {
|
||||||
if !is_canonical_f64_nan(x) {
|
if !is_canonical_f64_nan(x) {
|
||||||
bail!("{}\nexpected canonical NaN", context(span))
|
bail!("{}\nexpected canonical NaN", context(span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
other => bail!("expected float, got {:?}", other),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActionOutcome::Trapped { message } => {
|
Outcome::Trap(t) => {
|
||||||
bail!("{}\nunexpected trap: {}", context(span), message)
|
let t = t.borrow();
|
||||||
|
bail!("{}\nunexpected trap: {}", context(span), t.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssertReturnCanonicalNanF32x4 { span, invoke } => {
|
AssertReturnCanonicalNanF32x4 { span, invoke } => {
|
||||||
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
let val = match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
Val::V128(x) => x,
|
||||||
bail!("{}\nunexpected integer in NaN test", context(span))
|
other => bail!("expected v128, got {:?}", other),
|
||||||
}
|
};
|
||||||
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
|
|
||||||
"{}\nunexpected scalar float in vector NaN test",
|
|
||||||
context(span)
|
|
||||||
),
|
|
||||||
RuntimeValue::V128(x) => {
|
|
||||||
for l in 0..4 {
|
for l in 0..4 {
|
||||||
if !is_canonical_f32_nan(&extract_lane_as_u32(x, l)?) {
|
if !is_canonical_f32_nan(&extract_lane_as_u32(val, l)?) {
|
||||||
bail!(
|
bail!(
|
||||||
"{}\nexpected f32x4 canonical NaN in lane {}",
|
"{}\nexpected f32x4 canonical NaN in lane {}",
|
||||||
context(span),
|
context(span),
|
||||||
@@ -295,29 +338,23 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
Outcome::Trap(t) => {
|
||||||
ActionOutcome::Trapped { message } => {
|
let t = t.borrow();
|
||||||
bail!("{}\nunexpected trap: {}", context(span), message)
|
bail!("{}\nunexpected trap: {}", context(span), t.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssertReturnCanonicalNanF64x2 { span, invoke } => {
|
AssertReturnCanonicalNanF64x2 { span, invoke } => {
|
||||||
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
let val = match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
Val::V128(x) => x,
|
||||||
bail!("{}\nunexpected integer in NaN test", context(span))
|
other => bail!("expected v128, got {:?}", other),
|
||||||
}
|
};
|
||||||
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
|
|
||||||
"{}\nunexpected scalar float in vector NaN test",
|
|
||||||
context(span)
|
|
||||||
),
|
|
||||||
RuntimeValue::V128(x) => {
|
|
||||||
for l in 0..2 {
|
for l in 0..2 {
|
||||||
if !is_canonical_f64_nan(&extract_lane_as_u64(x, l)?) {
|
if !is_canonical_f64_nan(&extract_lane_as_u64(val, l)?) {
|
||||||
bail!(
|
bail!(
|
||||||
"{}\nexpected f64x2 canonical NaN in lane {}",
|
"{}\nexpected f64x2 canonical NaN in lane {}",
|
||||||
context(span),
|
context(span),
|
||||||
@@ -326,58 +363,48 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
Outcome::Trap(t) => {
|
||||||
ActionOutcome::Trapped { message } => {
|
let t = t.borrow();
|
||||||
bail!("{}\nunexpected trap: {}", context(span), message)
|
bail!("{}\nunexpected trap: {}", context(span), t.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssertReturnArithmeticNan { span, invoke } => {
|
AssertReturnArithmeticNan { span, invoke } => {
|
||||||
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
Val::F32(x) => {
|
||||||
bail!("{}\nunexpected integer in NaN test", context(span))
|
|
||||||
}
|
|
||||||
RuntimeValue::V128(_) => {
|
|
||||||
bail!("{}\nunexpected vector in NaN test", context(span))
|
|
||||||
}
|
|
||||||
RuntimeValue::F32(x) => {
|
|
||||||
if !is_arithmetic_f32_nan(x) {
|
if !is_arithmetic_f32_nan(x) {
|
||||||
bail!("{}\nexpected arithmetic NaN", context(span))
|
bail!("{}\nexpected arithmetic NaN", context(span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeValue::F64(x) => {
|
Val::F64(x) => {
|
||||||
if !is_arithmetic_f64_nan(x) {
|
if !is_arithmetic_f64_nan(x) {
|
||||||
bail!("{}\nexpected arithmetic NaN", context(span))
|
bail!("{}\nexpected arithmetic NaN", context(span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
other => bail!("expected float, got {:?}", other),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActionOutcome::Trapped { message } => {
|
Outcome::Trap(t) => {
|
||||||
bail!("{}\nunexpected trap: {}", context(span), message)
|
let t = t.borrow();
|
||||||
|
bail!("{}\nunexpected trap: {}", context(span), t.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssertReturnArithmeticNanF32x4 { span, invoke } => {
|
AssertReturnArithmeticNanF32x4 { span, invoke } => {
|
||||||
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
let val = match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
Val::V128(x) => x,
|
||||||
bail!("{}\nunexpected integer in NaN test", context(span))
|
other => bail!("expected v128, got {:?}", other),
|
||||||
}
|
};
|
||||||
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
|
|
||||||
"{}\nunexpected scalar float in vector NaN test",
|
|
||||||
context(span)
|
|
||||||
),
|
|
||||||
RuntimeValue::V128(x) => {
|
|
||||||
for l in 0..4 {
|
for l in 0..4 {
|
||||||
if !is_arithmetic_f32_nan(&extract_lane_as_u32(x, l)?) {
|
if !is_arithmetic_f32_nan(&extract_lane_as_u32(val, l)?) {
|
||||||
bail!(
|
bail!(
|
||||||
"{}\nexpected f32x4 arithmetic NaN in lane {}",
|
"{}\nexpected f32x4 arithmetic NaN in lane {}",
|
||||||
context(span),
|
context(span),
|
||||||
@@ -386,29 +413,23 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
Outcome::Trap(t) => {
|
||||||
ActionOutcome::Trapped { message } => {
|
let t = t.borrow();
|
||||||
bail!("{}\nunexpected trap: {}", context(span), message)
|
bail!("{}\nunexpected trap: {}", context(span), t.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssertReturnArithmeticNanF64x2 { span, invoke } => {
|
AssertReturnArithmeticNanF64x2 { span, invoke } => {
|
||||||
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
match self.perform_invoke(invoke).with_context(|| context(span))? {
|
||||||
ActionOutcome::Returned { values } => {
|
Outcome::Ok(values) => {
|
||||||
for v in values.iter() {
|
for v in values.iter() {
|
||||||
match v {
|
let val = match v {
|
||||||
RuntimeValue::I32(_) | RuntimeValue::I64(_) => {
|
Val::V128(x) => x,
|
||||||
bail!("{}\nunexpected integer in NaN test", context(span))
|
other => bail!("expected v128, got {:?}", other),
|
||||||
}
|
};
|
||||||
RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!(
|
|
||||||
"{}\nunexpected scalar float in vector NaN test",
|
|
||||||
context(span)
|
|
||||||
),
|
|
||||||
RuntimeValue::V128(x) => {
|
|
||||||
for l in 0..2 {
|
for l in 0..2 {
|
||||||
if !is_arithmetic_f64_nan(&extract_lane_as_u64(x, l)?) {
|
if !is_arithmetic_f64_nan(&extract_lane_as_u64(val, l)?) {
|
||||||
bail!(
|
bail!(
|
||||||
"{}\nexpected f64x2 arithmetic NaN in lane {}",
|
"{}\nexpected f64x2 arithmetic NaN in lane {}",
|
||||||
context(span),
|
context(span),
|
||||||
@@ -417,11 +438,10 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
Outcome::Trap(t) => {
|
||||||
ActionOutcome::Trapped { message } => {
|
let t = t.borrow();
|
||||||
bail!("{}\nunexpected trap: {}", context(span), message)
|
bail!("{}\nunexpected trap: {}", context(span), t.message())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,7 +515,7 @@ impl WastContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssertReturnFunc { .. } => panic!("need to implement assert_return_func"),
|
AssertReturnFunc { .. } => bail!("need to implement assert_return_func"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,14 +530,12 @@ impl WastContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_lane_as_u32(bytes: &[u8; 16], lane: usize) -> Result<u32> {
|
fn extract_lane_as_u32(bytes: &u128, lane: usize) -> Result<u32> {
|
||||||
let i = lane * 4;
|
Ok((*bytes >> (lane * 32)) as u32)
|
||||||
Ok(u32::from_le_bytes(bytes[i..i + 4].try_into()?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_lane_as_u64(bytes: &[u8; 16], lane: usize) -> Result<u64> {
|
fn extract_lane_as_u64(bytes: &u128, lane: usize) -> Result<u64> {
|
||||||
let i = lane * 8;
|
Ok((*bytes >> (lane * 64)) as u64)
|
||||||
Ok(u64::from_le_bytes(bytes[i..i + 8].try_into()?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_canonical_f32_nan(bits: &u32) -> bool {
|
fn is_canonical_f32_nan(bits: &u32) -> bool {
|
||||||
@@ -535,3 +553,16 @@ fn is_arithmetic_f32_nan(bits: &u32) -> bool {
|
|||||||
fn is_arithmetic_f64_nan(bits: &u64) -> bool {
|
fn is_arithmetic_f64_nan(bits: &u64) -> bool {
|
||||||
return (bits & 0x0008000000000000) == 0x0008000000000000;
|
return (bits & 0x0008000000000000) == 0x0008000000000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn values_equal(v1: &Val, v2: &Val) -> Result<bool> {
|
||||||
|
Ok(match (v1, v2) {
|
||||||
|
(Val::I32(a), Val::I32(b)) => a == b,
|
||||||
|
(Val::I64(a), Val::I64(b)) => a == b,
|
||||||
|
// Note that these float comparisons are comparing bits, not float
|
||||||
|
// values, so we're testing for bit-for-bit equivalence
|
||||||
|
(Val::F32(a), Val::F32(b)) => a == b,
|
||||||
|
(Val::F64(a), Val::F64(b)) => a == b,
|
||||||
|
(Val::V128(a), Val::V128(b)) => a == b,
|
||||||
|
_ => bail!("don't know how to compare {:?} and {:?} yet", v1, v2),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ use wasmtime_wasi::create_wasi_instance;
|
|||||||
use wasmtime_wasi::old::snapshot_0::create_wasi_instance as create_wasi_instance_snapshot_0;
|
use wasmtime_wasi::old::snapshot_0::create_wasi_instance as create_wasi_instance_snapshot_0;
|
||||||
#[cfg(feature = "wasi-c")]
|
#[cfg(feature = "wasi-c")]
|
||||||
use wasmtime_wasi_c::instantiate_wasi_c;
|
use wasmtime_wasi_c::instantiate_wasi_c;
|
||||||
use wasmtime_wast::instantiate_spectest;
|
|
||||||
|
|
||||||
const USAGE: &str = "
|
const USAGE: &str = "
|
||||||
Wasm runner.
|
Wasm runner.
|
||||||
@@ -275,12 +274,6 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let mut module_registry = HashMap::new();
|
let mut module_registry = HashMap::new();
|
||||||
|
|
||||||
// Make spectest available by default.
|
|
||||||
module_registry.insert(
|
|
||||||
"spectest".to_owned(),
|
|
||||||
HostRef::new(Instance::from_handle(&store, instantiate_spectest()?)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make wasi available by default.
|
// Make wasi available by default.
|
||||||
let preopen_dirs = compute_preopen_dirs(&args.flag_dir, &args.flag_mapdir);
|
let preopen_dirs = compute_preopen_dirs(&args.flag_dir, &args.flag_mapdir);
|
||||||
let argv = compute_argv(&args.arg_file, &args.arg_arg);
|
let argv = compute_argv(&args.arg_file, &args.arg_arg);
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ use pretty_env_logger;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
use wasmtime::{Config, Engine, HostRef, Store};
|
||||||
use wasmtime_cli::pick_compilation_strategy;
|
use wasmtime_cli::pick_compilation_strategy;
|
||||||
use wasmtime_environ::settings;
|
use wasmtime_environ::settings;
|
||||||
use wasmtime_environ::settings::Configurable;
|
use wasmtime_environ::settings::Configurable;
|
||||||
use wasmtime_environ::{cache_create_new_config, cache_init};
|
use wasmtime_environ::{cache_create_new_config, cache_init};
|
||||||
use wasmtime_jit::native;
|
use wasmtime_jit::Features;
|
||||||
use wasmtime_jit::{Compiler, Features};
|
|
||||||
use wasmtime_wast::WastContext;
|
use wasmtime_wast::WastContext;
|
||||||
|
|
||||||
const USAGE: &str = "
|
const USAGE: &str = "
|
||||||
@@ -128,7 +128,6 @@ fn main() {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let isa_builder = native::builder();
|
|
||||||
let mut flag_builder = settings::builder();
|
let mut flag_builder = settings::builder();
|
||||||
let mut features: Features = Default::default();
|
let mut features: Features = Default::default();
|
||||||
|
|
||||||
@@ -154,10 +153,12 @@ fn main() {
|
|||||||
|
|
||||||
// Decide how to compile.
|
// Decide how to compile.
|
||||||
let strategy = pick_compilation_strategy(args.flag_cranelift, args.flag_lightbeam);
|
let strategy = pick_compilation_strategy(args.flag_cranelift, args.flag_lightbeam);
|
||||||
|
let mut cfg = Config::new();
|
||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
cfg.strategy(strategy)
|
||||||
let engine = Compiler::new(isa, strategy);
|
.flags(settings::Flags::new(flag_builder))
|
||||||
let mut wast_context = WastContext::new(Box::new(engine)).with_features(features);
|
.features(features);
|
||||||
|
let store = HostRef::new(Store::new(&HostRef::new(Engine::new(&cfg))));
|
||||||
|
let mut wast_context = WastContext::new(store);
|
||||||
|
|
||||||
wast_context
|
wast_context
|
||||||
.register_spectest()
|
.register_spectest()
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use wasmtime::{Config, Engine, HostRef, Store};
|
||||||
|
use wasmtime_environ::settings;
|
||||||
use wasmtime_environ::settings::Configurable;
|
use wasmtime_environ::settings::Configurable;
|
||||||
use wasmtime_environ::{isa, settings};
|
use wasmtime_jit::{CompilationStrategy, Features};
|
||||||
use wasmtime_jit::{native, CompilationStrategy, Compiler, Features};
|
|
||||||
use wasmtime_wast::WastContext;
|
use wasmtime_wast::WastContext;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
||||||
@@ -11,26 +12,24 @@ include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
|||||||
// to compile it.
|
// to compile it.
|
||||||
fn run_wast(wast: &str, strategy: CompilationStrategy) -> anyhow::Result<()> {
|
fn run_wast(wast: &str, strategy: CompilationStrategy) -> anyhow::Result<()> {
|
||||||
let wast = Path::new(wast);
|
let wast = Path::new(wast);
|
||||||
let isa = native_isa();
|
|
||||||
let compiler = Compiler::new(isa, strategy);
|
|
||||||
let features = Features {
|
let features = Features {
|
||||||
simd: wast.iter().any(|s| s == "simd"),
|
simd: wast.iter().any(|s| s == "simd"),
|
||||||
multi_value: wast.iter().any(|s| s == "multi-value"),
|
multi_value: wast.iter().any(|s| s == "multi-value"),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features);
|
|
||||||
wast_context.register_spectest()?;
|
|
||||||
wast_context.run_file(wast)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_isa() -> Box<dyn isa::TargetIsa> {
|
|
||||||
let mut flag_builder = settings::builder();
|
let mut flag_builder = settings::builder();
|
||||||
flag_builder.enable("enable_verifier").unwrap();
|
flag_builder.enable("enable_verifier").unwrap();
|
||||||
flag_builder.enable("avoid_div_traps").unwrap();
|
flag_builder.enable("avoid_div_traps").unwrap();
|
||||||
flag_builder.enable("enable_simd").unwrap();
|
flag_builder.enable("enable_simd").unwrap();
|
||||||
|
|
||||||
let isa_builder = native::builder();
|
let mut cfg = Config::new();
|
||||||
|
cfg.strategy(strategy)
|
||||||
isa_builder.finish(settings::Flags::new(flag_builder))
|
.flags(settings::Flags::new(flag_builder))
|
||||||
|
.features(features);
|
||||||
|
let store = HostRef::new(Store::new(&HostRef::new(Engine::new(&cfg))));
|
||||||
|
let mut wast_context = WastContext::new(store);
|
||||||
|
wast_context.register_spectest()?;
|
||||||
|
wast_context.run_file(wast)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user