diff --git a/cranelift/src/cton-util.rs b/cranelift/src/cton-util.rs index bd104e9d5f..a7ad8a44b4 100644 --- a/cranelift/src/cton-util.rs +++ b/cranelift/src/cton-util.rs @@ -30,7 +30,7 @@ Usage: cton-util cat ... cton-util filecheck [-v] cton-util print-cfg ... - cton-util wasm [-cvo] ... + cton-util wasm [-cvo] [--enable=]... ... cton-util --help | --version Options: @@ -53,6 +53,7 @@ struct Args { flag_check: bool, flag_optimize: bool, flag_verbose: bool, + flag_enable: Vec, } /// A command either succeeds or fails with an error message. @@ -84,6 +85,7 @@ fn cton_util() -> CommandResult { args.flag_verbose, args.flag_optimize, args.flag_check, + args.flag_enable, ) } else { // Debugging / shouldn't happen with proper command line handling above. diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs index c915379d7e..4dddddf65c 100644 --- a/cranelift/src/wasm.rs +++ b/cranelift/src/wasm.rs @@ -1,5 +1,4 @@ -//! CLI tool to use the functions provided by crates [wasm2cretonne](../wasm2cretonne/index.html) -//! and [wasmstandalone](../wasmstandalone/index.html). +//! CLI tool to use the functions provided by the [cretonne-wasm](../cton_wasm/index.html) crate. //! //! Reads Wasm binary files (one Wasm module per file), translates the functions' code to Cretonne //! IL. Can also executes the `start` function of the module by laying out the memories, globals @@ -13,6 +12,7 @@ use cretonne::dominator_tree::DominatorTree; use cretonne::Context; use cretonne::result::CtonError; use cretonne::verifier; +use cretonne::settings::{self, Configurable}; use std::fs::File; use std::error::Error; use std::io; @@ -54,7 +54,16 @@ pub fn run( flag_verbose: bool, flag_optimize: bool, flag_check: bool, + flag_enable: Vec, ) -> Result<(), String> { + let mut flag_builder = settings::builder(); + for enable in flag_enable { + flag_builder.enable(&enable).map_err(|_| { + format!("unrecognized flag: {}", enable) + })?; + } + let flags = settings::Flags::new(&flag_builder); + for filename in files { let path = Path::new(&filename); let name = String::from(path.as_os_str().to_string_lossy()); @@ -64,6 +73,7 @@ pub fn run( flag_check, path.to_path_buf(), name, + &flags, ) { Ok(()) => {} Err(message) => return Err(message), @@ -78,6 +88,7 @@ fn handle_module( flag_check: bool, path: PathBuf, name: String, + flags: &settings::Flags, ) -> Result<(), String> { let mut terminal = term::stdout().unwrap(); terminal.fg(term::color::YELLOW).unwrap(); @@ -89,7 +100,7 @@ fn handle_module( terminal.reset().unwrap(); let data = match path.extension() { None => { - return Err(String::from("the file extension is not wasm or wast")); + return Err(String::from("the file extension is not wasm or wat")); } Some(ext) => { match ext.to_str() { @@ -101,17 +112,17 @@ fn handle_module( } } } - Some("wast") => { - let tmp_dir = TempDir::new("wasm2cretonne").unwrap(); + Some("wat") => { + let tmp_dir = TempDir::new("cretonne-wasm").unwrap(); let file_path = tmp_dir.path().join("module.wasm"); File::create(file_path.clone()).unwrap(); - Command::new("wast2wasm") + Command::new("wat2wasm") .arg(path.clone()) .arg("-o") .arg(file_path.to_str().unwrap()) .output() .or_else(|e| if let io::ErrorKind::NotFound = e.kind() { - return Err(String::from("wast2wasm not found")); + return Err(String::from("wat2wasm not found")); } else { return Err(String::from(e.description())); }) @@ -124,12 +135,12 @@ fn handle_module( } } None | Some(&_) => { - return Err(String::from("the file extension is not wasm or wast")); + return Err(String::from("the file extension is not wasm or wat")); } } } }; - let mut dummy_runtime = DummyRuntime::new(); + let mut dummy_runtime = DummyRuntime::with_flags(flags.clone()); let translation = { let runtime: &mut WasmRuntime = &mut dummy_runtime; match translate_module(&data, runtime) { diff --git a/cranelift/wasmtests/arith.wast b/cranelift/wasmtests/arith.wat similarity index 100% rename from cranelift/wasmtests/arith.wast rename to cranelift/wasmtests/arith.wat diff --git a/cranelift/wasmtests/call.wast b/cranelift/wasmtests/call.wat similarity index 100% rename from cranelift/wasmtests/call.wast rename to cranelift/wasmtests/call.wat diff --git a/cranelift/wasmtests/fibonacci.wast b/cranelift/wasmtests/fibonacci.wat similarity index 100% rename from cranelift/wasmtests/fibonacci.wast rename to cranelift/wasmtests/fibonacci.wat diff --git a/cranelift/wasmtests/globals.wast b/cranelift/wasmtests/globals.wat similarity index 100% rename from cranelift/wasmtests/globals.wast rename to cranelift/wasmtests/globals.wat diff --git a/cranelift/wasmtests/icall.wast b/cranelift/wasmtests/icall.wat similarity index 100% rename from cranelift/wasmtests/icall.wast rename to cranelift/wasmtests/icall.wat diff --git a/cranelift/wasmtests/memory.wast b/cranelift/wasmtests/memory.wat similarity index 100% rename from cranelift/wasmtests/memory.wast rename to cranelift/wasmtests/memory.wat diff --git a/cranelift/wasmtests/return_at_end.wat b/cranelift/wasmtests/return_at_end.wat new file mode 100644 index 0000000000..44eab2b3f6 --- /dev/null +++ b/cranelift/wasmtests/return_at_end.wat @@ -0,0 +1,10 @@ +(module + (memory 1) + (func $main (param i32) + (if + (get_local 0) + (then (return)) + (else (unreachable)) + ) + ) +) diff --git a/lib/wasm/Cargo.toml b/lib/wasm/Cargo.toml index 3de76671d0..1bd44219c7 100644 --- a/lib/wasm/Cargo.toml +++ b/lib/wasm/Cargo.toml @@ -14,3 +14,6 @@ name = "cton_wasm" wasmparser = "0.10.0" cretonne = { path = "../cretonne" } cretonne-frontend = { path = "../frontend" } + +[dev-dependencies] +tempdir = "0.3.5" diff --git a/lib/wasm/src/code_translator.rs b/lib/wasm/src/code_translator.rs index 625552232b..b3d202aa2d 100644 --- a/lib/wasm/src/code_translator.rs +++ b/lib/wasm/src/code_translator.rs @@ -329,8 +329,20 @@ pub fn translate_operator( } } Operator::Return => { - let return_count = state.control_stack[0].return_values().len(); - builder.ins().return_(state.peekn(return_count)); + let (return_count, br_destination) = { + let frame = &mut state.control_stack[0]; + frame.set_reachable(); + let return_count = frame.return_values().len(); + (return_count, frame.br_destination()) + }; + { + let args = state.peekn(return_count); + if environ.flags().return_at_end() { + builder.ins().jump(br_destination, args); + } else { + builder.ins().return_(args); + } + } state.popn(return_count); state.real_unreachable_stack_depth = 1; } diff --git a/lib/wasm/src/func_translator.rs b/lib/wasm/src/func_translator.rs index e2dede8b88..3383b0caf1 100644 --- a/lib/wasm/src/func_translator.rs +++ b/lib/wasm/src/func_translator.rs @@ -237,7 +237,7 @@ mod tests { ]; let mut trans = FuncTranslator::new(); - let mut runtime = DummyRuntime::new(); + let mut runtime = DummyRuntime::default(); let mut ctx = Context::new(); ctx.func.name = ir::FunctionName::new("small1"); @@ -271,7 +271,7 @@ mod tests { ]; let mut trans = FuncTranslator::new(); - let mut runtime = DummyRuntime::new(); + let mut runtime = DummyRuntime::default(); let mut ctx = Context::new(); ctx.func.name = ir::FunctionName::new("small2"); @@ -314,7 +314,7 @@ mod tests { ]; let mut trans = FuncTranslator::new(); - let mut runtime = DummyRuntime::new(); + let mut runtime = DummyRuntime::default(); let mut ctx = Context::new(); ctx.func.name = ir::FunctionName::new("infloop"); diff --git a/lib/wasm/src/runtime/dummy.rs b/lib/wasm/src/runtime/dummy.rs index 04762988f2..b6d849d751 100644 --- a/lib/wasm/src/runtime/dummy.rs +++ b/lib/wasm/src/runtime/dummy.rs @@ -4,6 +4,7 @@ use translation_utils::{Global, Memory, Table, GlobalIndex, TableIndex, Signatur use cretonne::ir::{self, InstBuilder}; use cretonne::ir::types::*; use cretonne::cursor::FuncCursor; +use cretonne::settings; /// This runtime implementation is a "naïve" one, doing essentially nothing and emitting /// placeholders when forced to. Don't try to execute code translated with this runtime, it is @@ -18,23 +19,32 @@ pub struct DummyRuntime { // Names of imported functions. imported_funcs: Vec, + + // Compilation setting flags. + flags: settings::Flags, } impl DummyRuntime { - /// Allocates the runtime data structures. - pub fn new() -> Self { + /// Allocates the runtime data structures with default flags. + pub fn default() -> Self { + Self::with_flags(settings::Flags::new(&settings::builder())) + } + + /// Allocates the runtime data structures with the given flags. + pub fn with_flags(flags: settings::Flags) -> Self { Self { signatures: Vec::new(), globals: Vec::new(), func_types: Vec::new(), imported_funcs: Vec::new(), + flags, } } } impl FuncEnvironment for DummyRuntime { - fn native_pointer(&self) -> ir::Type { - ir::types::I64 + fn flags(&self) -> &settings::Flags { + &self.flags } fn make_global(&self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue { diff --git a/lib/wasm/src/runtime/spec.rs b/lib/wasm/src/runtime/spec.rs index 9f093e9a07..cdd3b69920 100644 --- a/lib/wasm/src/runtime/spec.rs +++ b/lib/wasm/src/runtime/spec.rs @@ -2,6 +2,7 @@ //! trait `WasmRuntime`. use cretonne::ir::{self, InstBuilder}; use cretonne::cursor::FuncCursor; +use cretonne::settings::Flags; use translation_utils::{SignatureIndex, FunctionIndex, TableIndex, GlobalIndex, MemoryIndex, Global, Table, Memory}; @@ -26,10 +27,19 @@ pub enum GlobalValue { /// IL. The function environment provides information about the WebAssembly module as well as the /// runtime environment. pub trait FuncEnvironment { + /// Get the flags for the current compilation. + fn flags(&self) -> &Flags; + /// Get the Cretonne integer type to use for native pointers. /// - /// This should be `I64` for 64-bit architectures and `I32` for 32-bit architectures. - fn native_pointer(&self) -> ir::Type; + /// This returns `I64` for 64-bit architectures and `I32` for 32-bit architectures. + fn native_pointer(&self) -> ir::Type { + if self.flags().is_64bit() { + ir::types::I64 + } else { + ir::types::I32 + } + } /// Set up the necessary preamble definitions in `func` to access the global variable /// identified by `index`. @@ -138,7 +148,7 @@ pub trait FuncEnvironment { /// An object satisfyng the `WasmRuntime` trait can be passed as argument to the /// [`translate_module`](fn.translate_module.html) function. These methods should not be called -/// by the user, they are only for the `wasm2cretonne` internal use. +/// by the user, they are only for `cretonne-wasm` internal use. pub trait WasmRuntime: FuncEnvironment { /// Declares a function signature to the runtime. fn declare_signature(&mut self, sig: &ir::Signature); diff --git a/lib/wasm/tests/testsuite.rs b/lib/wasm/tests/testsuite.rs index 9e59b2bab7..7234541b66 100644 --- a/lib/wasm/tests/testsuite.rs +++ b/lib/wasm/tests/testsuite.rs @@ -1,18 +1,24 @@ extern crate cton_wasm; extern crate cretonne; +extern crate tempdir; use cton_wasm::{translate_module, DummyRuntime, WasmRuntime}; use std::path::PathBuf; +use std::borrow::Borrow; use std::fs::File; use std::error::Error; use std::io; +use std::str; use std::io::BufReader; use std::io::prelude::*; +use std::process::Command; use std::fs; use cretonne::ir; use cretonne::ir::entities::AnyEntity; -use cretonne::isa::TargetIsa; +use cretonne::isa::{self, TargetIsa}; +use cretonne::settings::{self, Configurable}; use cretonne::verifier; +use tempdir::TempDir; #[test] fn testsuite() { @@ -23,13 +29,29 @@ fn testsuite() { paths.sort_by_key(|dir| dir.path()); for path in paths { let path = path.path(); - match handle_module(path) { - Ok(()) => (), - Err(message) => println!("{}", message), - }; + handle_module(path, None); } } +#[test] +fn return_at_end() { + let mut flag_builder = settings::builder(); + flag_builder.enable("return_at_end").unwrap(); + let flags = settings::Flags::new(&flag_builder); + // We don't care about the target itself here, so just pick one arbitrarily. + let isa = match isa::lookup("riscv") { + Err(_) => { + println!("riscv target not found; disabled test return_at_end.wat"); + return; + } + Ok(isa_builder) => isa_builder.finish(flags), + }; + handle_module( + PathBuf::from("../../wasmtests/return_at_end.wat"), + Some(isa.borrow()), + ); +} + fn read_wasm_file(path: PathBuf) -> Result, io::Error> { let mut buf: Vec = Vec::new(); let file = File::open(path)?; @@ -38,44 +60,80 @@ fn read_wasm_file(path: PathBuf) -> Result, io::Error> { Ok(buf) } -fn handle_module(path: PathBuf) -> Result<(), String> { +fn handle_module(path: PathBuf, isa: Option<&TargetIsa>) { let data = match path.extension() { None => { - return Err(String::from("the file extension is not wasm or wast")); + panic!("the file extension is not wasm or wat"); } Some(ext) => { match ext.to_str() { Some("wasm") => { match read_wasm_file(path.clone()) { + Ok(data) => data, + Err(err) => panic!("error reading wasm file: {}", err.description()), + } + } + Some("wat") => { + let tmp_dir = TempDir::new("cretonne-wasm").unwrap(); + let file_path = tmp_dir.path().join("module.wasm"); + File::create(file_path.clone()).unwrap(); + let result_output = Command::new("wat2wasm") + .arg(path.clone()) + .arg("-o") + .arg(file_path.to_str().unwrap()) + .output(); + match result_output { + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + println!( + "wat2wasm not found; disabled test {}", + path.to_str().unwrap() + ); + return; + } + panic!("error convering wat file: {}", e.description()); + } + Ok(output) => { + if !output.status.success() { + panic!( + "error running wat2wasm: {}", + str::from_utf8(&output.stderr).expect( + "wat2wasm's error message should be valid UTF-8", + ) + ); + } + } + } + match read_wasm_file(file_path) { Ok(data) => data, Err(err) => { - return Err(String::from(err.description())); + panic!("error reading converted wasm file: {}", err.description()); } } } - None | Some(&_) => { - return Err(String::from("the file extension is not wasm or wast")); - } + None | Some(&_) => panic!("the file extension is not wasm or wat"), } } }; - let mut dummy_runtime = DummyRuntime::new(); + let mut dummy_runtime = match isa { + Some(isa) => DummyRuntime::with_flags(isa.flags().clone()), + None => DummyRuntime::default(), + }; let translation = { let runtime: &mut WasmRuntime = &mut dummy_runtime; match translate_module(&data, runtime) { Ok(x) => x, Err(string) => { - return Err(string); + panic!(string); } } }; for func in &translation.functions { - match verifier::verify_function(func, None) { + match verifier::verify_function(func, isa) { Ok(()) => (), - Err(err) => return Err(pretty_verifier_error(func, None, err)), + Err(err) => panic!(pretty_verifier_error(func, isa, err)), } } - Ok(()) }