From d5a2eb397c6760f7df9c4150822ab6d5c3d9504b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Dec 2019 13:30:50 -0600 Subject: [PATCH] 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 --- Cargo.lock | 5 +- crates/api/examples/multi.rs | 7 +- crates/api/src/callable.rs | 23 +- crates/api/src/module.rs | 13 +- crates/api/src/runtime.rs | 2 +- crates/api/src/trampoline/func.rs | 8 +- crates/api/src/values.rs | 6 +- crates/wast/Cargo.toml | 8 +- crates/wast/src/spectest.rs | 288 ++++++-------------- crates/wast/src/wast.rs | 439 ++++++++++++++++-------------- src/bin/wasmtime.rs | 7 - src/bin/wast.rs | 15 +- tests/wast_testsuites.rs | 25 +- 13 files changed, 368 insertions(+), 478 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6497401b08..60df42fbb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2188,6 +2188,7 @@ dependencies = [ "wasmtime-environ", "wasmtime-jit", "wasmtime-runtime", + "wig", ] [[package]] @@ -2213,9 +2214,7 @@ name = "wasmtime-wast" version = "0.7.0" dependencies = [ "anyhow", - "target-lexicon", - "wasmtime-environ", - "wasmtime-jit", + "wasmtime", "wasmtime-runtime", "wast 4.0.0", ] diff --git a/crates/api/examples/multi.rs b/crates/api/examples/multi.rs index d02e56cbc6..7f980f581b 100644 --- a/crates/api/examples/multi.rs +++ b/crates/api/examples/multi.rs @@ -45,7 +45,12 @@ const WAT: &str = r#" fn main() -> Result<()> { // Initialize. 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)); // Load binary. diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index c06b76d62b..374bbdc95b 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -121,7 +121,7 @@ impl WasmtimeFn { impl WrappedCallable for WasmtimeFn { fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), HostRef> { use std::cmp::max; - use std::{mem, ptr}; + use std::mem; let (vmctx, body, signature) = match self.wasmtime_export() { Export::Function { @@ -132,21 +132,14 @@ impl WrappedCallable for WasmtimeFn { _ => panic!("unexpected export type in Callable"), }; - let value_size = mem::size_of::(); - let mut values_vec: Vec = vec![0; max(params.len(), results.len())]; + let value_size = mem::size_of::(); + let mut values_vec = vec![0; max(params.len(), results.len())]; // Store the argument values into `values_vec`. for (index, arg) in params.iter().enumerate() { unsafe { let ptr = values_vec.as_mut_ptr().add(index); - - 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"), - } + arg.write_value_to(ptr); } } @@ -176,13 +169,7 @@ impl WrappedCallable for WasmtimeFn { unsafe { let ptr = values_vec.as_ptr().add(index); - results[index] = match 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), - } + results[index] = Val::read_value_from(ptr, abi_param.value_type); } } diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index f92aee937b..1e36db8038 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -201,14 +201,15 @@ impl Module { _ => None, } } - pub fn validate(_store: &HostRef, binary: &[u8]) -> Result<()> { + pub fn validate(store: &HostRef, binary: &[u8]) -> Result<()> { + let features = store.borrow().engine().borrow().config.features.clone(); let config = ValidatingParserConfig { operator_config: OperatorValidatorConfig { - enable_threads: false, - enable_reference_types: false, - enable_bulk_memory: false, - enable_simd: false, - enable_multi_value: true, + enable_threads: features.threads, + enable_reference_types: features.reference_types, + enable_bulk_memory: features.bulk_memory, + enable_simd: features.simd, + enable_multi_value: features.multi_value, }, }; validate(binary, Some(config)).map_err(Error::new) diff --git a/crates/api/src/runtime.rs b/crates/api/src/runtime.rs index 26c827036e..3ed7ea6499 100644 --- a/crates/api/src/runtime.rs +++ b/crates/api/src/runtime.rs @@ -94,7 +94,7 @@ impl Default for Config { #[derive(Default)] pub struct Engine { - config: Config, + pub(crate) config: Config, } impl Engine { diff --git a/crates/api/src/trampoline/func.rs b/crates/api/src/trampoline/func.rs index d283370b44..f30cd9bf7c 100644 --- a/crates/api/src/trampoline/func.rs +++ b/crates/api/src/trampoline/func.rs @@ -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 (args, returns_len) = { @@ -130,7 +130,10 @@ fn make_trampoline( // Add error/trap return. 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(); context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone()); @@ -139,7 +142,6 @@ fn make_trampoline( StackSlotKind::ExplicitSlot, values_vec_len, )); - let value_size = 8; { let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); diff --git a/crates/api/src/values.rs b/crates/api/src/values.rs index 49a7e3115e..7f9d367982 100644 --- a/crates/api/src/values.rs +++ b/crates/api/src/values.rs @@ -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 { Val::I32(i) => ptr::write(p as *mut i32, *i), Val::I64(i) => ptr::write(p as *mut i64, *i), Val::F32(u) => ptr::write(p as *mut u32, *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"), } } - 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 { ir::types::I32 => Val::I32(ptr::read(p as *const i32)), ir::types::I64 => Val::I64(ptr::read(p as *const i64)), ir::types::F32 => Val::F32(ptr::read(p as *const u32)), 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"), } } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index af24b7e51e..4a6b9e0876 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -11,12 +11,10 @@ readme = "README.md" edition = "2018" [dependencies] -wasmtime-jit = { path = "../jit" } -wasmtime-runtime = { path = "../runtime" } -wasmtime-environ = { path = "../environ" } -wast = "4.0.0" anyhow = "1.0.19" -target-lexicon = "0.9.0" +wasmtime = { path = "../api" } +wasmtime-runtime = { path = "../runtime" } +wast = "4.0.0" [badges] maintenance = { status = "actively-developed" } diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 293fc0542c..d9b689cb66 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -1,233 +1,105 @@ #![allow(improper_ctypes)] -use std::cell::RefCell; -use std::collections::hash_map::HashMap; +use anyhow::Result; +use std::collections::HashMap; use std::rc::Rc; -use target_lexicon::HOST; -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}; +use wasmtime::*; -extern "C" fn spectest_print() {} +struct MyCall(F); -#[allow(clippy::print_stdout)] -extern "C" fn spectest_print_i32(_vmctx: &mut VMContext, x: i32) { - println!("{}: i32", x); +impl Callable for MyCall +where + F: Fn(&[Val], &mut [Val]) -> Result<(), HostRef>, +{ + fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), HostRef> { + (self.0)(params, results) + } } -#[allow(clippy::print_stdout)] -extern "C" fn spectest_print_i64(_vmctx: &mut VMContext, x: i64) { - println!("{}: i64", x); -} - -#[allow(clippy::print_stdout)] -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); +fn wrap( + store: &HostRef, + ty: FuncType, + callable: impl Fn(&[Val], &mut [Val]) -> Result<(), HostRef> + 'static, +) -> Func { + Func::new(store, ty, Rc::new(MyCall(callable))) } /// Return an instance implementing the "spectest" interface used in the /// spec testsuite. -pub fn instantiate_spectest() -> Result { - let call_conv = isa::CallConv::triple_default(&HOST); - let pointer_type = types::Type::triple_pointer_type(&HOST); - let mut module = Module::new(); - let mut finished_functions: PrimaryMap = - PrimaryMap::new(); +pub fn instantiate_spectest(store: &HostRef) -> HashMap<&'static str, Extern> { + let mut ret = HashMap::new(); - let sig = module.signatures.push(translate_signature( - ir::Signature { - params: vec![], - 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 ty = FuncType::new(Box::new([]), Box::new([])); + let func = wrap(store, ty, |_params, _results| Ok(())); + ret.insert("print", Extern::Func(HostRef::new(func))); - let sig = module.signatures.push(translate_signature( - ir::Signature { - params: vec![ir::AbiParam::new(types::I32)], - returns: vec![], - 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), + let ty = FuncType::new(Box::new([ValType::I32]), Box::new([])); + let func = wrap(store, ty, |params, _results| { + println!("{}: i32", params[0].unwrap_i32()); + Ok(()) }); - module - .exports - .insert("global_i32".to_owned(), Export::Global(global)); + ret.insert("print_i32", Extern::Func(HostRef::new(func))); - let global = module.globals.push(Global { - ty: types::I64, - mutability: false, - initializer: GlobalInit::I64Const(666), + let ty = FuncType::new(Box::new([ValType::I64]), Box::new([])); + let func = wrap(store, ty, |params, _results| { + println!("{}: i64", params[0].unwrap_i64()); + Ok(()) }); - module - .exports - .insert("global_i64".to_owned(), Export::Global(global)); + ret.insert("print_i64", Extern::Func(HostRef::new(func))); - let global = module.globals.push(Global { - ty: types::F32, - mutability: false, - initializer: GlobalInit::F32Const(0x44268000), + let ty = FuncType::new(Box::new([ValType::F32]), Box::new([])); + let func = wrap(store, ty, |params, _results| { + println!("{}: f32", params[0].unwrap_f32()); + Ok(()) }); - module - .exports - .insert("global_f32".to_owned(), Export::Global(global)); + ret.insert("print_f32", Extern::Func(HostRef::new(func))); - let global = module.globals.push(Global { - ty: types::F64, - mutability: false, - initializer: GlobalInit::F64Const(0x4084d00000000000), + let ty = FuncType::new(Box::new([ValType::F64]), Box::new([])); + let func = wrap(store, ty, |params, _results| { + println!("{}: f64", params[0].unwrap_f64()); + Ok(()) }); - module - .exports - .insert("global_f64".to_owned(), Export::Global(global)); + ret.insert("print_f64", Extern::Func(HostRef::new(func))); - let tunables = target_tunables(&HOST); - let table = module.table_plans.push(TablePlan::for_table( - Table { - ty: TableElementType::Func, - minimum: 10, - maximum: Some(20), - }, - &tunables, - )); - module - .exports - .insert("table".to_owned(), Export::Table(table)); + let ty = FuncType::new(Box::new([ValType::I32, ValType::F32]), Box::new([])); + let func = wrap(store, ty, |params, _results| { + println!("{}: i32", params[0].unwrap_i32()); + println!("{}: f32", params[1].unwrap_f32()); + Ok(()) + }); + ret.insert("print_i32_f32", Extern::Func(HostRef::new(func))); - let memory = module.memory_plans.push(MemoryPlan::for_memory( - Memory { - minimum: 1, - maximum: Some(2), - shared: false, - }, - &tunables, - )); - module - .exports - .insert("memory".to_owned(), Export::Memory(memory)); + let ty = FuncType::new(Box::new([ValType::F64, ValType::F64]), Box::new([])); + let func = wrap(store, ty, |params, _results| { + println!("{}: f64", params[0].unwrap_f64()); + println!("{}: f64", params[1].unwrap_f64()); + Ok(()) + }); + ret.insert("print_f64_f64", Extern::Func(HostRef::new(func))); - let imports = Imports::none(); - let data_initializers = Vec::new(); - let signatures = PrimaryMap::new(); + let ty = GlobalType::new(ValType::I32, Mutability::Const); + let g = Global::new(store, ty, Val::I32(666)); + ret.insert("global_i32", Extern::Global(HostRef::new(g))); - InstanceHandle::new( - Rc::new(module), - Rc::new(RefCell::new(HashMap::new())), - finished_functions.into_boxed_slice(), - imports, - &data_initializers, - signatures.into_boxed_slice(), - None, - Box::new(()), - ) + let ty = GlobalType::new(ValType::I64, Mutability::Const); + let g = Global::new(store, ty, Val::I64(666)); + ret.insert("global_i64", Extern::Global(HostRef::new(g))); + + let ty = GlobalType::new(ValType::F32, Mutability::Const); + let g = Global::new(store, ty, Val::F32(0x44268000)); + ret.insert("global_f32", Extern::Global(HostRef::new(g))); + + 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; } diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 17f353bf12..aaa45bca6c 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -1,28 +1,25 @@ use crate::spectest::instantiate_spectest; -use anyhow::{bail, Context as _, Result}; -use std::convert::TryInto; +use anyhow::{anyhow, bail, Context as _, Result}; +use std::collections::HashMap; use std::path::Path; use std::str; -use wasmtime_jit::{ - ActionError, ActionOutcome, Compiler, Context, Features, InstanceHandle, InstantiationError, - RuntimeValue, SetupError, -}; +use wasmtime::*; /// Translate from a `script::Value` to a `RuntimeValue`. -fn runtime_value(v: &wast::Expression<'_>) -> RuntimeValue { +fn runtime_value(v: &wast::Expression<'_>) -> Result { use wast::Instruction::*; if v.instrs.len() != 1 { - panic!("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), + bail!("too many instructions in {:?}", v); } + 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 @@ -30,85 +27,131 @@ fn runtime_value(v: &wast::Expression<'_>) -> RuntimeValue { pub struct WastContext { /// Wast files have a concept of a "current" module, which is the most /// recently defined. - current: Option, + current: Option>, - context: Context, + instances: HashMap>, + store: HostRef, + spectest: Option>, +} + +enum Outcome> { + Ok(T), + Trap(HostRef), } impl WastContext { /// Construct a new instance of `WastContext`. - pub fn new(compiler: Box) -> Self { + pub fn new(store: HostRef) -> Self { Self { current: None, - context: Context::new(compiler), + store, + spectest: None, + instances: HashMap::new(), } } - /// Construct a new instance with the given features using the current `Context` - pub fn with_features(self, features: Features) -> Self { - Self { - context: self.context.with_features(features), - ..self + fn get_instance(&self, instance_name: Option<&str>) -> Result> { + match instance_name { + Some(name) => self + .instances + .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> { - let instance = if let Some(instance_name) = instance_name { - self.context - .get_instance(instance_name) - .context("failed to fetch instance")? - } else { - self.current - .as_mut() - .ok_or_else(|| anyhow::format_err!("no current instance"))? + fn instantiate(&self, module: &[u8]) -> Result>> { + let module = HostRef::new(Module::new(&self.store, module)?); + let mut imports = Vec::new(); + for import in module.borrow().imports() { + if import.module() == "spectest" { + let spectest = self + .spectest + .as_ref() + .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::()) + .next(); + if let Some(InstantiationError::StartTrap(msg)) = err { + return Ok(Outcome::Trap(HostRef::new(Trap::new(msg.clone())))); + } + return Err(e); + } }; - - Ok(instance) + Ok(Outcome::Ok(HostRef::new(instance))) } /// Register "spectest" which is used by the spec testsuite. pub fn register_spectest(&mut self) -> Result<()> { - let instance = instantiate_spectest()?; - self.context.name_instance("spectest".to_owned(), instance); + self.spectest = Some(instantiate_spectest(&self.store)); Ok(()) } /// Perform the action portion of a command. - fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result { + fn perform_execute(&mut self, exec: wast::WastExecute<'_>) -> Result { match exec { wast::WastExecute::Invoke(invoke) => self.perform_invoke(invoke), wast::WastExecute::Module(mut module) => { let binary = module.encode()?; - let result = self.context.instantiate_module(None, &binary); - match result { - Ok(_) => Ok(ActionOutcome::Returned { values: Vec::new() }), - Err(ActionError::Setup(SetupError::Instantiate( - InstantiationError::StartTrap(message), - ))) => Ok(ActionOutcome::Trapped { message }), - Err(e) => Err(e.into()), - } + let result = self.instantiate(&binary)?; + Ok(match result { + Outcome::Ok(_) => Outcome::Ok(Vec::new()), + Outcome::Trap(e) => Outcome::Trap(e), + }) } wast::WastExecute::Get { module, global } => self.get(module.map(|s| s.name()), global), } } - fn perform_invoke(&mut self, exec: wast::WastInvoke<'_>) -> Result { + fn perform_invoke(&mut self, exec: wast::WastInvoke<'_>) -> Result { self.invoke(exec.module.map(|i| i.name()), exec.name, &exec.args) } /// Define a module and register it. fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<()> { - let index = self - .context - .instantiate_module(instance_name.map(|s| s.to_string()), module)?; - self.current = Some(index); + let instance = match self.instantiate(module)? { + Outcome::Ok(i) => i, + Outcome::Trap(e) => bail!("instantiation failed with: {}", e.borrow().message()), + }; + if let Some(name) = instance_name { + self.instances.insert(name.to_string(), instance.clone()); + } + self.current = Some(instance); Ok(()) } /// Register an instance to make it available for performing actions. fn register(&mut self, name: Option<&str>, as_name: &str) -> Result<()> { 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(()) } @@ -118,26 +161,35 @@ impl WastContext { instance_name: Option<&str>, field: &str, args: &[wast::Expression], - ) -> Result { - let value_args = args.iter().map(runtime_value).collect::>(); - let mut instance = self.get_instance(instance_name)?.clone(); - let result = self - .context - .invoke(&mut instance, field, &value_args) - .with_context(|| format!("failed to invoke `{}`", field))?; - Ok(result) + ) -> Result { + let values = args.iter().map(runtime_value).collect::>>()?; + let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?; + let instance = instance.borrow(); + let export = instance + .find_export_by_name(field) + .ok_or_else(|| anyhow!("no global named `{}`", field))?; + 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. - fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result { - let instance = self - .get_instance(instance_name.as_ref().map(|x| &**x))? - .clone(); - let result = self - .context - .get(&instance, field) - .with_context(|| format!("failed to get field `{}`", field))?; - Ok(result) + fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result { + let instance = self.get_instance(instance_name.as_ref().map(|x| &**x))?; + let instance = instance.borrow(); + let export = instance + .find_export_by_name(field) + .ok_or_else(|| anyhow!("no global named `{}`", field))?; + let global = match export { + Extern::Global(g) => g.borrow(), + _ => bail!("export of `{}` wasn't a global", field), + }; + Ok(Outcome::Ok(vec![global.get()])) } /// Run a wast script from a byte buffer. @@ -179,16 +231,18 @@ impl WastContext { exec, results, } => 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)) { - if *v == e { + let e = e?; + if values_equal(v, &e)? { continue; } - bail!("{}\nexpected {}, got {}", context(span), e, v) + bail!("{}\nexpected {:?}, got {:?}", context(span), e, v) } } - ActionOutcome::Trapped { message } => { - bail!("{}\nunexpected trap: {}", context(span), message) + Outcome::Trap(t) => { + let t = t.borrow(); + bail!("{}\nunexpected trap: {}", context(span), t.message()) } }, AssertTrap { @@ -196,13 +250,12 @@ impl WastContext { exec, message, } => match self.perform_execute(exec).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { bail!("{}\nexpected trap, got {:?}", context(span), values) } - ActionOutcome::Trapped { - message: trap_message, - } => { - if trap_message.contains(message) { + Outcome::Trap(t) => { + let t = t.borrow(); + if t.message().contains(message) { continue; } if cfg!(feature = "lightbeam") { @@ -217,7 +270,7 @@ impl WastContext { "{}\nexpected {}, got {}", context(span), message, - trap_message + t.message(), ) } }, @@ -226,202 +279,169 @@ impl WastContext { call, message, } => match self.perform_invoke(call).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { bail!("{}\nexpected trap, got {:?}", context(span), values) } - ActionOutcome::Trapped { - message: trap_message, - } => { - if trap_message.contains(message) { + Outcome::Trap(t) => { + let t = t.borrow(); + if t.message().contains(message) { continue; } bail!( "{}\nexpected exhaustion with {}, got {}", context(span), message, - trap_message + t.message(), ) } }, AssertReturnCanonicalNan { span, invoke } => { match self.perform_invoke(invoke).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { for v in values.iter() { match v { - RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - bail!("{}\nunexpected integer in NaN test", context(span)) - } - RuntimeValue::V128(_) => { - bail!("{}\nunexpected vector in NaN test", context(span)) - } - RuntimeValue::F32(x) => { + Val::F32(x) => { if !is_canonical_f32_nan(x) { bail!("{}\nexpected canonical NaN", context(span)) } } - RuntimeValue::F64(x) => { + Val::F64(x) => { if !is_canonical_f64_nan(x) { bail!("{}\nexpected canonical NaN", context(span)) } } + other => bail!("expected float, got {:?}", other), }; } } - ActionOutcome::Trapped { message } => { - bail!("{}\nunexpected trap: {}", context(span), message) + Outcome::Trap(t) => { + let t = t.borrow(); + bail!("{}\nunexpected trap: {}", context(span), t.message()) } } } AssertReturnCanonicalNanF32x4 { span, invoke } => { match self.perform_invoke(invoke).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { for v in values.iter() { - match v { - RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - bail!("{}\nunexpected integer in NaN test", context(span)) - } - RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!( - "{}\nunexpected scalar float in vector NaN test", - context(span) - ), - RuntimeValue::V128(x) => { - for l in 0..4 { - if !is_canonical_f32_nan(&extract_lane_as_u32(x, l)?) { - bail!( - "{}\nexpected f32x4 canonical NaN in lane {}", - context(span), - l - ) - } - } - } + let val = match v { + Val::V128(x) => x, + other => bail!("expected v128, got {:?}", other), }; + for l in 0..4 { + if !is_canonical_f32_nan(&extract_lane_as_u32(val, l)?) { + bail!( + "{}\nexpected f32x4 canonical NaN in lane {}", + context(span), + l + ) + } + } } } - ActionOutcome::Trapped { message } => { - bail!("{}\nunexpected trap: {}", context(span), message) + Outcome::Trap(t) => { + let t = t.borrow(); + bail!("{}\nunexpected trap: {}", context(span), t.message()) } } } AssertReturnCanonicalNanF64x2 { span, invoke } => { match self.perform_invoke(invoke).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { for v in values.iter() { - match v { - RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - bail!("{}\nunexpected integer in NaN test", context(span)) - } - RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!( - "{}\nunexpected scalar float in vector NaN test", - context(span) - ), - RuntimeValue::V128(x) => { - for l in 0..2 { - if !is_canonical_f64_nan(&extract_lane_as_u64(x, l)?) { - bail!( - "{}\nexpected f64x2 canonical NaN in lane {}", - context(span), - l - ) - } - } - } + let val = match v { + Val::V128(x) => x, + other => bail!("expected v128, got {:?}", other), }; + for l in 0..2 { + if !is_canonical_f64_nan(&extract_lane_as_u64(val, l)?) { + bail!( + "{}\nexpected f64x2 canonical NaN in lane {}", + context(span), + l + ) + } + } } } - ActionOutcome::Trapped { message } => { - bail!("{}\nunexpected trap: {}", context(span), message) + Outcome::Trap(t) => { + let t = t.borrow(); + bail!("{}\nunexpected trap: {}", context(span), t.message()) } } } AssertReturnArithmeticNan { span, invoke } => { match self.perform_invoke(invoke).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { for v in values.iter() { match v { - RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - bail!("{}\nunexpected integer in NaN test", context(span)) - } - RuntimeValue::V128(_) => { - bail!("{}\nunexpected vector in NaN test", context(span)) - } - RuntimeValue::F32(x) => { + Val::F32(x) => { if !is_arithmetic_f32_nan(x) { bail!("{}\nexpected arithmetic NaN", context(span)) } } - RuntimeValue::F64(x) => { + Val::F64(x) => { if !is_arithmetic_f64_nan(x) { bail!("{}\nexpected arithmetic NaN", context(span)) } } + other => bail!("expected float, got {:?}", other), }; } } - ActionOutcome::Trapped { message } => { - bail!("{}\nunexpected trap: {}", context(span), message) + Outcome::Trap(t) => { + let t = t.borrow(); + bail!("{}\nunexpected trap: {}", context(span), t.message()) } } } AssertReturnArithmeticNanF32x4 { span, invoke } => { match self.perform_invoke(invoke).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { for v in values.iter() { - match v { - RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - bail!("{}\nunexpected integer in NaN test", context(span)) - } - RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!( - "{}\nunexpected scalar float in vector NaN test", - context(span) - ), - RuntimeValue::V128(x) => { - for l in 0..4 { - if !is_arithmetic_f32_nan(&extract_lane_as_u32(x, l)?) { - bail!( - "{}\nexpected f32x4 arithmetic NaN in lane {}", - context(span), - l - ) - } - } - } + let val = match v { + Val::V128(x) => x, + other => bail!("expected v128, got {:?}", other), }; + for l in 0..4 { + if !is_arithmetic_f32_nan(&extract_lane_as_u32(val, l)?) { + bail!( + "{}\nexpected f32x4 arithmetic NaN in lane {}", + context(span), + l + ) + } + } } } - ActionOutcome::Trapped { message } => { - bail!("{}\nunexpected trap: {}", context(span), message) + Outcome::Trap(t) => { + let t = t.borrow(); + bail!("{}\nunexpected trap: {}", context(span), t.message()) } } } AssertReturnArithmeticNanF64x2 { span, invoke } => { match self.perform_invoke(invoke).with_context(|| context(span))? { - ActionOutcome::Returned { values } => { + Outcome::Ok(values) => { for v in values.iter() { - match v { - RuntimeValue::I32(_) | RuntimeValue::I64(_) => { - bail!("{}\nunexpected integer in NaN test", context(span)) - } - RuntimeValue::F32(_) | RuntimeValue::F64(_) => bail!( - "{}\nunexpected scalar float in vector NaN test", - context(span) - ), - RuntimeValue::V128(x) => { - for l in 0..2 { - if !is_arithmetic_f64_nan(&extract_lane_as_u64(x, l)?) { - bail!( - "{}\nexpected f64x2 arithmetic NaN in lane {}", - context(span), - l - ) - } - } - } + let val = match v { + Val::V128(x) => x, + other => bail!("expected v128, got {:?}", other), }; + for l in 0..2 { + if !is_arithmetic_f64_nan(&extract_lane_as_u64(val, l)?) { + bail!( + "{}\nexpected f64x2 arithmetic NaN in lane {}", + context(span), + l + ) + } + } } } - ActionOutcome::Trapped { message } => { - bail!("{}\nunexpected trap: {}", context(span), message) + Outcome::Trap(t) => { + let t = t.borrow(); + 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 { - let i = lane * 4; - Ok(u32::from_le_bytes(bytes[i..i + 4].try_into()?)) +fn extract_lane_as_u32(bytes: &u128, lane: usize) -> Result { + Ok((*bytes >> (lane * 32)) as u32) } -fn extract_lane_as_u64(bytes: &[u8; 16], lane: usize) -> Result { - let i = lane * 8; - Ok(u64::from_le_bytes(bytes[i..i + 8].try_into()?)) +fn extract_lane_as_u64(bytes: &u128, lane: usize) -> Result { + Ok((*bytes >> (lane * 64)) as u64) } 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 { return (bits & 0x0008000000000000) == 0x0008000000000000; } + +fn values_equal(v1: &Val, v2: &Val) -> Result { + 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), + }) +} diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index a0abb06fa6..deeb8fa19f 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -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; #[cfg(feature = "wasi-c")] use wasmtime_wasi_c::instantiate_wasi_c; -use wasmtime_wast::instantiate_spectest; const USAGE: &str = " Wasm runner. @@ -275,12 +274,6 @@ fn main() -> Result<()> { 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. let preopen_dirs = compute_preopen_dirs(&args.flag_dir, &args.flag_mapdir); let argv = compute_argv(&args.arg_file, &args.arg_arg); diff --git a/src/bin/wast.rs b/src/bin/wast.rs index e0967305ba..a93237b12f 100644 --- a/src/bin/wast.rs +++ b/src/bin/wast.rs @@ -30,12 +30,12 @@ use pretty_env_logger; use serde::Deserialize; use std::path::Path; use std::process; +use wasmtime::{Config, Engine, HostRef, Store}; use wasmtime_cli::pick_compilation_strategy; use wasmtime_environ::settings; use wasmtime_environ::settings::Configurable; use wasmtime_environ::{cache_create_new_config, cache_init}; -use wasmtime_jit::native; -use wasmtime_jit::{Compiler, Features}; +use wasmtime_jit::Features; use wasmtime_wast::WastContext; const USAGE: &str = " @@ -128,7 +128,6 @@ fn main() { process::exit(1); } - let isa_builder = native::builder(); let mut flag_builder = settings::builder(); let mut features: Features = Default::default(); @@ -154,10 +153,12 @@ fn main() { // Decide how to compile. let strategy = pick_compilation_strategy(args.flag_cranelift, args.flag_lightbeam); - - let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - let engine = Compiler::new(isa, strategy); - let mut wast_context = WastContext::new(Box::new(engine)).with_features(features); + let mut cfg = Config::new(); + cfg.strategy(strategy) + .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() diff --git a/tests/wast_testsuites.rs b/tests/wast_testsuites.rs index 8bf9886ae9..1a0c1748c6 100644 --- a/tests/wast_testsuites.rs +++ b/tests/wast_testsuites.rs @@ -1,7 +1,8 @@ use std::path::Path; +use wasmtime::{Config, Engine, HostRef, Store}; +use wasmtime_environ::settings; use wasmtime_environ::settings::Configurable; -use wasmtime_environ::{isa, settings}; -use wasmtime_jit::{native, CompilationStrategy, Compiler, Features}; +use wasmtime_jit::{CompilationStrategy, Features}; use wasmtime_wast::WastContext; 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. fn run_wast(wast: &str, strategy: CompilationStrategy) -> anyhow::Result<()> { let wast = Path::new(wast); - let isa = native_isa(); - let compiler = Compiler::new(isa, strategy); let features = Features { simd: wast.iter().any(|s| s == "simd"), multi_value: wast.iter().any(|s| s == "multi-value"), ..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 { let mut flag_builder = settings::builder(); flag_builder.enable("enable_verifier").unwrap(); flag_builder.enable("avoid_div_traps").unwrap(); flag_builder.enable("enable_simd").unwrap(); - let isa_builder = native::builder(); - - isa_builder.finish(settings::Flags::new(flag_builder)) + let mut cfg = Config::new(); + cfg.strategy(strategy) + .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(()) }