diff --git a/Cargo.toml b/Cargo.toml index 188ea582f2..9b7d45ac4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ cranelift-wasm = { git = "https://github.com/sunfishcode/cranelift.git", branch wasmtime-environ = { path = "lib/environ" } wasmtime-execute = { path = "lib/execute" } wasmtime-obj = { path = "lib/obj" } +wasmtime-wast = { path = "lib/wast" } docopt = "1.0.1" serde = "1.0.75" serde_derive = "1.0.75" diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 0d26a99040..adc3c3882f 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -55,6 +55,7 @@ mod signalhandlers; mod table; mod traphandlers; mod vmcontext; +mod world; pub use code::Code; pub use execute::{compile_and_link_module, finish_instantiation}; @@ -62,6 +63,7 @@ pub use instance::Instance; pub use invoke::{invoke, InvokeOutcome, Value}; pub use traphandlers::{call_wasm, LookupCodeSegment, RecordTrap, Unwind}; pub use vmcontext::VMContext; +pub use world::InstanceWorld; #[cfg(not(feature = "std"))] mod std { diff --git a/lib/execute/src/world.rs b/lib/execute/src/world.rs new file mode 100644 index 0000000000..15f5a6b28c --- /dev/null +++ b/lib/execute/src/world.rs @@ -0,0 +1,66 @@ +use cranelift_codegen::isa; +use std::str; +use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables}; +use {compile_and_link_module, finish_instantiation, invoke, Code, Instance, InvokeOutcome, Value}; + +/// A module, an instance of that module, and accompanying compilation artifacts. +/// +/// TODO: Rename and reorganize this. +pub struct InstanceWorld { + module: Module, + instance: Instance, + compilation: Compilation, +} + +impl InstanceWorld { + /// Create a new `InstanceWorld` by compiling the wasm module in `data` and instatiating it. + pub fn new(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { + let mut module = Module::new(); + let tunables = Tunables::default(); + let (instance, compilation) = { + let translation = { + let environ = ModuleEnvironment::new(isa, &mut module, tunables); + + environ.translate(&data).map_err(|e| e.to_string())? + }; + + let imports_resolver = |_env: &str, _function: &str| None; + + let compilation = compile_and_link_module(isa, &translation, &imports_resolver)?; + let mut instance = Instance::new( + translation.module, + &compilation, + &translation.lazy.data_initializers, + )?; + + finish_instantiation(code, isa, &translation.module, &compilation, &mut instance)?; + + (instance, compilation) + }; + + Ok(Self { + module, + instance, + compilation, + }) + } + + /// Invoke a function in this `InstanceWorld` by name. + pub fn invoke( + &mut self, + code: &mut Code, + isa: &isa::TargetIsa, + function_name: &str, + args: &[Value], + ) -> Result { + invoke( + code, + isa, + &self.module, + &self.compilation, + self.instance.vmctx(), + &function_name, + args, + ).map_err(|e| e.to_string()) + } +} diff --git a/lib/wast/Cargo.toml b/lib/wast/Cargo.toml new file mode 100644 index 0000000000..ab4ba1aaa9 --- /dev/null +++ b/lib/wast/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wasmtime-wast" +version = "0.0.0" +authors = ["The Cranelift Project Developers"] +publish = false +description = "wast testing support for wasmtime" +categories = ["wasm"] +repository = "https://github.com/CraneStation/wasmtime" +license = "Apache-2.0 WITH LLVM-exception" +readme = "README.md" + +[dependencies] +cranelift-codegen = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +cranelift-native = { git = "https://github.com/sunfishcode/cranelift.git", branch = "guard-offset" } +wasmtime-execute = { path = "../execute" } +wabt = "0.7" + +[badges] +maintenance = { status = "experimental" } +travis-ci = { repository = "CraneStation/wasmtime" } diff --git a/lib/wast/LICENSE b/lib/wast/LICENSE new file mode 100644 index 0000000000..f9d81955f4 --- /dev/null +++ b/lib/wast/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/lib/wast/README.md b/lib/wast/README.md new file mode 100644 index 0000000000..f9001eaa85 --- /dev/null +++ b/lib/wast/README.md @@ -0,0 +1,4 @@ +This is the `wasmtime-wast` crate, which contains support for running +"wast" tests, such as the spec testsuite. + +[`wasmtime-wast`]: https://crates.io/crates/wasmtime-wast diff --git a/lib/wast/build.rs b/lib/wast/build.rs new file mode 100644 index 0000000000..6241c3f62c --- /dev/null +++ b/lib/wast/build.rs @@ -0,0 +1,46 @@ +use std::env; +use std::fs::{read_dir, File}; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + let out_dir = + PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set")); + let mut out = + File::create(out_dir.join("run_wast_files.rs")).expect("error creating run_wast_files.rs"); + + let mut paths: Vec<_> = read_dir("spec_testsuite") + .unwrap() + .map(|r| r.unwrap()) + .filter(|p| { + // Ignore files starting with `.`, which could be editor temporary files + if let Some(stem) = p.path().file_stem() { + if let Some(stemstr) = stem.to_str() { + return !stemstr.starts_with('.'); + } + } + false + }).collect(); + + paths.sort_by_key(|dir| dir.path()); + for path in paths { + let path = path.path(); + writeln!(out, "#[test]"); + writeln!( + out, + "fn {}() {{", + path.file_stem() + .expect("file_stem") + .to_str() + .expect("to_str") + ); + writeln!( + out, + " wast_file(Path::new(\"{}\"), &*native_isa()).expect(\"error loading wast file {}\");", + path.display(), + path.display() + ); + writeln!(out, "}}"); + writeln!(out); + } +} diff --git a/tests/wast/misc_traps.wast b/lib/wast/misc_testsuite/misc_traps.wast similarity index 100% rename from tests/wast/misc_traps.wast rename to lib/wast/misc_testsuite/misc_traps.wast diff --git a/tests/wast/stack_overflow.wast b/lib/wast/misc_testsuite/stack_overflow.wast similarity index 100% rename from tests/wast/stack_overflow.wast rename to lib/wast/misc_testsuite/stack_overflow.wast diff --git a/lib/wast/src/lib.rs b/lib/wast/src/lib.rs new file mode 100644 index 0000000000..2ec356061b --- /dev/null +++ b/lib/wast/src/lib.rs @@ -0,0 +1,34 @@ +//! JIT-style runtime for WebAssembly using Cranelift. + +#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)] +#![warn(unused_import_braces)] +#![deny(unstable_features)] +#![cfg_attr( + feature = "clippy", + plugin(clippy(conf_file = "../../clippy.toml")) +)] +#![cfg_attr( + feature = "cargo-clippy", + allow(new_without_default, new_without_default_derive) +)] +#![cfg_attr( + feature = "cargo-clippy", + warn( + float_arithmetic, + mut_mut, + nonminimal_bool, + option_map_unwrap_or, + option_map_unwrap_or_else, + print_stdout, + unicode_not_nfc, + use_self + ) +)] + +extern crate cranelift_codegen; +extern crate wabt; +extern crate wasmtime_execute; + +mod wast; + +pub use wast::{wast_buffer, wast_file}; diff --git a/lib/wast/src/wast.rs b/lib/wast/src/wast.rs new file mode 100644 index 0000000000..529baf5d9d --- /dev/null +++ b/lib/wast/src/wast.rs @@ -0,0 +1,177 @@ +use cranelift_codegen::isa; +use std::collections::HashMap; +use std::fs; +use std::io; +use std::io::Read; +use std::path::Path; +use std::str; +use wabt::script::{self, Action, Command, CommandKind, ModuleBinary, ScriptParser}; +use wasmtime_execute::{Code, InstanceWorld, InvokeOutcome, Value}; + +struct Instances { + current: Option, + namespace: HashMap, + code: Code, +} + +impl Instances { + pub fn new() -> Self { + Self { + current: None, + namespace: HashMap::new(), + code: Code::new(), + } + } + + fn instantiate(&mut self, isa: &isa::TargetIsa, module: ModuleBinary) -> InstanceWorld { + InstanceWorld::new(&mut self.code, isa, &module.into_vec()).unwrap() + } + + pub fn define_unnamed_module(&mut self, isa: &isa::TargetIsa, module: ModuleBinary) { + self.current = Some(self.instantiate(isa, module)); + } + + pub fn define_named_module( + &mut self, + isa: &isa::TargetIsa, + name: String, + module: ModuleBinary, + ) { + let world = self.instantiate(isa, module); + self.namespace.insert(name, world); + } + + pub fn perform_action(&mut self, isa: &isa::TargetIsa, action: Action) -> InvokeOutcome { + match action { + Action::Invoke { + module, + field, + args, + } => { + let mut value_args = Vec::with_capacity(args.len()); + for a in args { + value_args.push(match a { + script::Value::I32(i) => Value::I32(i), + script::Value::I64(i) => Value::I64(i), + script::Value::F32(i) => Value::F32(i.to_bits()), + script::Value::F64(i) => Value::F64(i.to_bits()), + }); + } + match module { + None => match self.current { + None => panic!("invoke performed with no module present"), + Some(ref mut instance_world) => instance_world + .invoke(&mut self.code, isa, &field, &value_args) + .expect(&format!("error invoking {} in current module", field)), + }, + Some(name) => self + .namespace + .get_mut(&name) + .expect(&format!("module {} not declared", name)) + .invoke(&mut self.code, isa, &field, &value_args) + .expect(&format!("error invoking {} in module {}", field, name)), + } + } + _ => panic!("unsupported action {:?}", action), + } + } +} + +/// Run a wast script from a byte buffer. +pub fn wast_buffer(name: &str, isa: &isa::TargetIsa, wast: &[u8]) { + let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); + let mut instances = Instances::new(); + + while let Some(Command { kind, line }) = parser.next().unwrap() { + match kind { + CommandKind::Module { module, name } => { + if let Some(name) = name { + instances.define_named_module(&*isa, name, module.clone()); + } + + instances.define_unnamed_module(&*isa, module) + } + CommandKind::PerformAction(action) => match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { .. } => {} + InvokeOutcome::Trapped { message } => { + panic!("{}:{}: a trap occurred: {}", name, line, message); + } + }, + CommandKind::AssertReturn { action, expected } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => { + for (v, e) in values.iter().zip(expected.iter()) { + match *e { + script::Value::I32(x) => { + assert_eq!(x, v.unwrap_i32(), "at {}:{}", name, line) + } + script::Value::I64(x) => { + assert_eq!(x, v.unwrap_i64(), "at {}:{}", name, line) + } + script::Value::F32(x) => { + assert_eq!(x.to_bits(), v.unwrap_f32(), "at {}:{}", name, line) + } + script::Value::F64(x) => { + assert_eq!(x.to_bits(), v.unwrap_f64(), "at {}:{}", name, line) + } + }; + } + } + InvokeOutcome::Trapped { message } => { + panic!( + "{}:{}: expected normal return, but a trap occurred: {}", + name, line, message + ); + } + } + } + CommandKind::AssertTrap { action, message } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => panic!( + "{}:{}: expected trap, but invoke returned with {:?}", + name, line, values + ), + InvokeOutcome::Trapped { + message: trap_message, + } => { + println!( + "{}:{}: TODO: Check the trap message: expected {}, got {}", + name, line, message, trap_message + ); + } + } + } + CommandKind::AssertExhaustion { action } => { + match instances.perform_action(&*isa, action) { + InvokeOutcome::Returned { values } => panic!( + "{}:{}: expected exhaustion, but invoke returned with {:?}", + name, line, values + ), + InvokeOutcome::Trapped { message } => { + println!( + "{}:{}: TODO: Check the exhaustion message: {}", + name, line, message + ); + } + } + } + command => { + println!("{}:{}: TODO: implement {:?}", name, line, command); + } + } + } +} + +/// Run a wast script from a file. +pub fn wast_file(path: &Path, isa: &isa::TargetIsa) -> Result<(), String> { + let wast = read_to_end(path).map_err(|e| e.to_string())?; + wast_buffer(&path.display().to_string(), isa, &wast); + Ok(()) +} + +fn read_to_end(path: &Path) -> Result, io::Error> { + let mut buf: Vec = Vec::new(); + let mut file = fs::File::open(path)?; + file.read_to_end(&mut buf)?; + Ok(buf) +} diff --git a/lib/wast/tests/wast_files.rs b/lib/wast/tests/wast_files.rs new file mode 100644 index 0000000000..9cdf59ab06 --- /dev/null +++ b/lib/wast/tests/wast_files.rs @@ -0,0 +1,22 @@ +extern crate cranelift_codegen; +extern crate cranelift_native; +extern crate wasmtime_wast; + +use cranelift_codegen::isa; +use cranelift_codegen::settings; +use cranelift_codegen::settings::Configurable; +use std::path::Path; +use wasmtime_wast::wast_file; + +include!(concat!(env!("OUT_DIR"), "/run_wast_files.rs")); + +#[cfg(test)] +fn native_isa() -> Box { + let mut flag_builder = settings::builder(); + flag_builder.enable("enable_verifier").unwrap(); + + let isa_builder = cranelift_native::builder().unwrap_or_else(|_| { + panic!("host machine is not a supported target"); + }); + isa_builder.finish(settings::Flags::new(flag_builder)) +} diff --git a/tests/wast.rs b/tests/wast.rs deleted file mode 100644 index b050b0fa81..0000000000 --- a/tests/wast.rs +++ /dev/null @@ -1,291 +0,0 @@ -extern crate cranelift_codegen; -extern crate wabt; -extern crate wasmtime_environ; -extern crate wasmtime_execute; - -use cranelift_codegen::settings::Configurable; -use cranelift_codegen::{isa, settings}; -use std::collections::HashMap; -use std::fs; -use std::io; -use std::io::Read; -use std::path::Path; -use std::str; -use wabt::script::{self, Action, Command, CommandKind, ScriptParser}; -use wasmtime_environ::{Compilation, Module, ModuleEnvironment, Tunables}; -use wasmtime_execute::{ - compile_and_link_module, finish_instantiation, invoke, Code, Instance, InvokeOutcome, Value, -}; - -struct InstanceWorld { - module: Module, - instance: Instance, - compilation: Compilation, -} - -impl InstanceWorld { - fn new(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { - let mut module = Module::new(); - let tunables = Tunables::default(); - let (instance, compilation) = { - let translation = { - let environ = ModuleEnvironment::new(isa, &mut module, tunables); - - environ.translate(&data).map_err(|e| e.to_string())? - }; - - let imports_resolver = |_env: &str, _function: &str| None; - - let compilation = compile_and_link_module(isa, &translation, &imports_resolver)?; - let mut instance = Instance::new( - translation.module, - &compilation, - &translation.lazy.data_initializers, - )?; - - finish_instantiation(code, isa, &translation.module, &compilation, &mut instance)?; - - (instance, compilation) - }; - - Ok(Self { - module, - instance, - compilation, - }) - } - - fn invoke( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - f: &str, - args: &[Value], - ) -> Result { - invoke( - code, - isa, - &self.module, - &self.compilation, - self.instance.vmctx(), - &f, - args, - ).map_err(|e| e.to_string()) - } -} - -fn translate(code: &mut Code, isa: &isa::TargetIsa, data: &[u8]) -> Result { - InstanceWorld::new(code, isa, data) -} - -struct Instances { - current: Option, - namespace: HashMap, -} - -impl Instances { - fn new() -> Self { - Self { - current: None, - namespace: HashMap::new(), - } - } - - fn unnamed(&mut self, instance: InstanceWorld) { - self.current = Some(instance); - } - - fn named(&mut self, name: String, instance: InstanceWorld) { - self.namespace.insert(name, instance); - } - - fn perform_action( - &mut self, - code: &mut Code, - isa: &isa::TargetIsa, - action: Action, - ) -> InvokeOutcome { - match action { - Action::Invoke { - module, - field, - args, - } => { - let mut value_args = Vec::new(); - for a in args { - value_args.push(match a { - script::Value::I32(i) => Value::I32(i), - script::Value::I64(i) => Value::I64(i), - script::Value::F32(i) => Value::F32(i.to_bits()), - script::Value::F64(i) => Value::F64(i.to_bits()), - }); - } - match module { - None => match self.current { - None => panic!("invoke performed with no module present"), - Some(ref mut instance_world) => instance_world - .invoke(code, isa, &field, &value_args) - .expect(&format!("error invoking {} in current module", field)), - }, - Some(name) => self - .namespace - .get_mut(&name) - .expect(&format!("module {} not declared", name)) - .invoke(code, isa, &field, &value_args) - .expect(&format!("error invoking {} in module {}", field, name)), - } - } - _ => panic!("unsupported action {:?}", action), - } - } -} - -#[test] -fn spec_core() { - let mut flag_builder = settings::builder(); - flag_builder.enable("enable_verifier").unwrap(); - - let isa_builder = cranelift_native::builder().unwrap_or_else(|_| { - panic!("host machine is not a supported target"); - }); - let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - - let mut paths: Vec<_> = fs::read_dir("tests/wast") - .unwrap() - .map(|r| r.unwrap()) - .filter(|p| { - // Ignore files starting with `.`, which could be editor temporary files - if let Some(stem) = p.path().file_stem() { - if let Some(stemstr) = stem.to_str() { - return !stemstr.starts_with('.'); - } - } - false - }).collect(); - paths.sort_by_key(|dir| dir.path()); - for path in paths { - let path = path.path(); - let source = read_to_end(&path).unwrap(); - test_wast(&path, &*isa, &source); - } -} - -#[cfg(test)] -fn read_to_end(path: &Path) -> Result, io::Error> { - let mut buf: Vec = Vec::new(); - let mut file = fs::File::open(path)?; - file.read_to_end(&mut buf)?; - Ok(buf) -} - -#[cfg(test)] -fn test_wast(path: &Path, isa: &isa::TargetIsa, wast: &[u8]) { - println!("Testing {}", path.display()); - - let mut parser = ScriptParser::from_str(str::from_utf8(wast).unwrap()).unwrap(); - let mut instances = Instances::new(); - let mut code = Code::new(); - - while let Some(Command { kind, line }) = parser.next().unwrap() { - match kind { - CommandKind::Module { module, name } => { - if let Some(name) = name { - instances.named( - name, - translate(&mut code, &*isa, &module.clone().into_vec()).unwrap(), - ); - } - - instances.unnamed(translate(&mut code, &*isa, &module.clone().into_vec()).unwrap()); - } - CommandKind::PerformAction(action) => { - match instances.perform_action(&mut code, &*isa, action) { - InvokeOutcome::Returned { .. } => {} - InvokeOutcome::Trapped { message } => { - panic!("{}:{}: a trap occurred: {}", path.display(), line, message); - } - } - } - CommandKind::AssertReturn { action, expected } => { - match instances.perform_action(&mut code, &*isa, action) { - InvokeOutcome::Returned { values } => { - for (v, e) in values.iter().zip(expected.iter()) { - match *e { - script::Value::I32(x) => { - assert_eq!(x, v.unwrap_i32(), "at {}:{}", path.display(), line) - } - script::Value::I64(x) => { - assert_eq!(x, v.unwrap_i64(), "at {}:{}", path.display(), line) - } - script::Value::F32(x) => assert_eq!( - x.to_bits(), - v.unwrap_f32(), - "at {}:{}", - path.display(), - line - ), - script::Value::F64(x) => assert_eq!( - x.to_bits(), - v.unwrap_f64(), - "at {}:{}", - path.display(), - line - ), - }; - } - } - InvokeOutcome::Trapped { message } => { - panic!( - "{}:{}: expected normal return, but a trap occurred: {}", - path.display(), - line, - message - ); - } - } - } - CommandKind::AssertTrap { action, message } => { - match instances.perform_action(&mut code, &*isa, action) { - InvokeOutcome::Returned { values } => panic!( - "{}:{}: expected trap, but invoke returned with {:?}", - path.display(), - line, - values - ), - InvokeOutcome::Trapped { - message: trap_message, - } => { - println!( - "{}:{}: TODO: Check the trap message: expected {}, got {}", - path.display(), - line, - message, - trap_message - ); - } - } - } - CommandKind::AssertExhaustion { action } => { - match instances.perform_action(&mut code, &*isa, action) { - InvokeOutcome::Returned { values } => panic!( - "{}:{}: expected exhaustion, but invoke returned with {:?}", - path.display(), - line, - values - ), - InvokeOutcome::Trapped { message } => { - println!( - "{}:{}: TODO: Check the exhaustion message: {}", - path.display(), - line, - message - ); - } - } - } - command => { - println!("{}:{}: TODO: implement {:?}", path.display(), line, command); - } - } - } -}