Add support for emitting code with a single return at the end. (#153)

This also enables testing of the wasmtests tests.

This also updates for wabt updating to the official "wat" filename
extension, as opposed to "wast".
This commit is contained in:
Dan Gohman
2017-09-12 13:27:36 -07:00
committed by GitHub
parent 2e046d68ce
commit 1ab207b93c
15 changed files with 154 additions and 38 deletions

View File

@@ -30,7 +30,7 @@ Usage:
cton-util cat <file>... cton-util cat <file>...
cton-util filecheck [-v] <file> cton-util filecheck [-v] <file>
cton-util print-cfg <file>... cton-util print-cfg <file>...
cton-util wasm [-cvo] <file>... cton-util wasm [-cvo] [--enable=<flag>]... <file>...
cton-util --help | --version cton-util --help | --version
Options: Options:
@@ -53,6 +53,7 @@ struct Args {
flag_check: bool, flag_check: bool,
flag_optimize: bool, flag_optimize: bool,
flag_verbose: bool, flag_verbose: bool,
flag_enable: Vec<String>,
} }
/// A command either succeeds or fails with an error message. /// A command either succeeds or fails with an error message.
@@ -84,6 +85,7 @@ fn cton_util() -> CommandResult {
args.flag_verbose, args.flag_verbose,
args.flag_optimize, args.flag_optimize,
args.flag_check, args.flag_check,
args.flag_enable,
) )
} else { } else {
// Debugging / shouldn't happen with proper command line handling above. // Debugging / shouldn't happen with proper command line handling above.

View File

@@ -1,5 +1,4 @@
//! CLI tool to use the functions provided by crates [wasm2cretonne](../wasm2cretonne/index.html) //! CLI tool to use the functions provided by the [cretonne-wasm](../cton_wasm/index.html) crate.
//! and [wasmstandalone](../wasmstandalone/index.html).
//! //!
//! Reads Wasm binary files (one Wasm module per file), translates the functions' code to Cretonne //! 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 //! 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::Context;
use cretonne::result::CtonError; use cretonne::result::CtonError;
use cretonne::verifier; use cretonne::verifier;
use cretonne::settings::{self, Configurable};
use std::fs::File; use std::fs::File;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
@@ -54,7 +54,16 @@ pub fn run(
flag_verbose: bool, flag_verbose: bool,
flag_optimize: bool, flag_optimize: bool,
flag_check: bool, flag_check: bool,
flag_enable: Vec<String>,
) -> Result<(), String> { ) -> 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 { for filename in files {
let path = Path::new(&filename); let path = Path::new(&filename);
let name = String::from(path.as_os_str().to_string_lossy()); let name = String::from(path.as_os_str().to_string_lossy());
@@ -64,6 +73,7 @@ pub fn run(
flag_check, flag_check,
path.to_path_buf(), path.to_path_buf(),
name, name,
&flags,
) { ) {
Ok(()) => {} Ok(()) => {}
Err(message) => return Err(message), Err(message) => return Err(message),
@@ -78,6 +88,7 @@ fn handle_module(
flag_check: bool, flag_check: bool,
path: PathBuf, path: PathBuf,
name: String, name: String,
flags: &settings::Flags,
) -> Result<(), String> { ) -> Result<(), String> {
let mut terminal = term::stdout().unwrap(); let mut terminal = term::stdout().unwrap();
terminal.fg(term::color::YELLOW).unwrap(); terminal.fg(term::color::YELLOW).unwrap();
@@ -89,7 +100,7 @@ fn handle_module(
terminal.reset().unwrap(); terminal.reset().unwrap();
let data = match path.extension() { let data = match path.extension() {
None => { 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) => { Some(ext) => {
match ext.to_str() { match ext.to_str() {
@@ -101,17 +112,17 @@ fn handle_module(
} }
} }
} }
Some("wast") => { Some("wat") => {
let tmp_dir = TempDir::new("wasm2cretonne").unwrap(); let tmp_dir = TempDir::new("cretonne-wasm").unwrap();
let file_path = tmp_dir.path().join("module.wasm"); let file_path = tmp_dir.path().join("module.wasm");
File::create(file_path.clone()).unwrap(); File::create(file_path.clone()).unwrap();
Command::new("wast2wasm") Command::new("wat2wasm")
.arg(path.clone()) .arg(path.clone())
.arg("-o") .arg("-o")
.arg(file_path.to_str().unwrap()) .arg(file_path.to_str().unwrap())
.output() .output()
.or_else(|e| if let io::ErrorKind::NotFound = e.kind() { .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 { } else {
return Err(String::from(e.description())); return Err(String::from(e.description()));
}) })
@@ -124,12 +135,12 @@ fn handle_module(
} }
} }
None | Some(&_) => { 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 translation = {
let runtime: &mut WasmRuntime = &mut dummy_runtime; let runtime: &mut WasmRuntime = &mut dummy_runtime;
match translate_module(&data, runtime) { match translate_module(&data, runtime) {

View File

@@ -0,0 +1,10 @@
(module
(memory 1)
(func $main (param i32)
(if
(get_local 0)
(then (return))
(else (unreachable))
)
)
)

View File

@@ -14,3 +14,6 @@ name = "cton_wasm"
wasmparser = "0.10.0" wasmparser = "0.10.0"
cretonne = { path = "../cretonne" } cretonne = { path = "../cretonne" }
cretonne-frontend = { path = "../frontend" } cretonne-frontend = { path = "../frontend" }
[dev-dependencies]
tempdir = "0.3.5"

View File

@@ -329,8 +329,20 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
} }
} }
Operator::Return => { Operator::Return => {
let return_count = state.control_stack[0].return_values().len(); let (return_count, br_destination) = {
builder.ins().return_(state.peekn(return_count)); 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.popn(return_count);
state.real_unreachable_stack_depth = 1; state.real_unreachable_stack_depth = 1;
} }

View File

@@ -237,7 +237,7 @@ mod tests {
]; ];
let mut trans = FuncTranslator::new(); let mut trans = FuncTranslator::new();
let mut runtime = DummyRuntime::new(); let mut runtime = DummyRuntime::default();
let mut ctx = Context::new(); let mut ctx = Context::new();
ctx.func.name = ir::FunctionName::new("small1"); ctx.func.name = ir::FunctionName::new("small1");
@@ -271,7 +271,7 @@ mod tests {
]; ];
let mut trans = FuncTranslator::new(); let mut trans = FuncTranslator::new();
let mut runtime = DummyRuntime::new(); let mut runtime = DummyRuntime::default();
let mut ctx = Context::new(); let mut ctx = Context::new();
ctx.func.name = ir::FunctionName::new("small2"); ctx.func.name = ir::FunctionName::new("small2");
@@ -314,7 +314,7 @@ mod tests {
]; ];
let mut trans = FuncTranslator::new(); let mut trans = FuncTranslator::new();
let mut runtime = DummyRuntime::new(); let mut runtime = DummyRuntime::default();
let mut ctx = Context::new(); let mut ctx = Context::new();
ctx.func.name = ir::FunctionName::new("infloop"); ctx.func.name = ir::FunctionName::new("infloop");

View File

@@ -4,6 +4,7 @@ use translation_utils::{Global, Memory, Table, GlobalIndex, TableIndex, Signatur
use cretonne::ir::{self, InstBuilder}; use cretonne::ir::{self, InstBuilder};
use cretonne::ir::types::*; use cretonne::ir::types::*;
use cretonne::cursor::FuncCursor; use cretonne::cursor::FuncCursor;
use cretonne::settings;
/// This runtime implementation is a "naïve" one, doing essentially nothing and emitting /// 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 /// 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. // Names of imported functions.
imported_funcs: Vec<ir::FunctionName>, imported_funcs: Vec<ir::FunctionName>,
// Compilation setting flags.
flags: settings::Flags,
} }
impl DummyRuntime { impl DummyRuntime {
/// Allocates the runtime data structures. /// Allocates the runtime data structures with default flags.
pub fn new() -> Self { 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 { Self {
signatures: Vec::new(), signatures: Vec::new(),
globals: Vec::new(), globals: Vec::new(),
func_types: Vec::new(), func_types: Vec::new(),
imported_funcs: Vec::new(), imported_funcs: Vec::new(),
flags,
} }
} }
} }
impl FuncEnvironment for DummyRuntime { impl FuncEnvironment for DummyRuntime {
fn native_pointer(&self) -> ir::Type { fn flags(&self) -> &settings::Flags {
ir::types::I64 &self.flags
} }
fn make_global(&self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue { fn make_global(&self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue {

View File

@@ -2,6 +2,7 @@
//! trait `WasmRuntime`. //! trait `WasmRuntime`.
use cretonne::ir::{self, InstBuilder}; use cretonne::ir::{self, InstBuilder};
use cretonne::cursor::FuncCursor; use cretonne::cursor::FuncCursor;
use cretonne::settings::Flags;
use translation_utils::{SignatureIndex, FunctionIndex, TableIndex, GlobalIndex, MemoryIndex, use translation_utils::{SignatureIndex, FunctionIndex, TableIndex, GlobalIndex, MemoryIndex,
Global, Table, Memory}; Global, Table, Memory};
@@ -26,10 +27,19 @@ pub enum GlobalValue {
/// IL. The function environment provides information about the WebAssembly module as well as the /// IL. The function environment provides information about the WebAssembly module as well as the
/// runtime environment. /// runtime environment.
pub trait FuncEnvironment { pub trait FuncEnvironment {
/// Get the flags for the current compilation.
fn flags(&self) -> &Flags;
/// Get the Cretonne integer type to use for native pointers. /// Get the Cretonne integer type to use for native pointers.
/// ///
/// This should be `I64` for 64-bit architectures and `I32` for 32-bit architectures. /// This returns `I64` for 64-bit architectures and `I32` for 32-bit architectures.
fn native_pointer(&self) -> ir::Type; 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 /// Set up the necessary preamble definitions in `func` to access the global variable
/// identified by `index`. /// identified by `index`.
@@ -138,7 +148,7 @@ pub trait FuncEnvironment {
/// An object satisfyng the `WasmRuntime` trait can be passed as argument to the /// 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 /// [`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 { pub trait WasmRuntime: FuncEnvironment {
/// Declares a function signature to the runtime. /// Declares a function signature to the runtime.
fn declare_signature(&mut self, sig: &ir::Signature); fn declare_signature(&mut self, sig: &ir::Signature);

View File

@@ -1,18 +1,24 @@
extern crate cton_wasm; extern crate cton_wasm;
extern crate cretonne; extern crate cretonne;
extern crate tempdir;
use cton_wasm::{translate_module, DummyRuntime, WasmRuntime}; use cton_wasm::{translate_module, DummyRuntime, WasmRuntime};
use std::path::PathBuf; use std::path::PathBuf;
use std::borrow::Borrow;
use std::fs::File; use std::fs::File;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
use std::str;
use std::io::BufReader; use std::io::BufReader;
use std::io::prelude::*; use std::io::prelude::*;
use std::process::Command;
use std::fs; use std::fs;
use cretonne::ir; use cretonne::ir;
use cretonne::ir::entities::AnyEntity; use cretonne::ir::entities::AnyEntity;
use cretonne::isa::TargetIsa; use cretonne::isa::{self, TargetIsa};
use cretonne::settings::{self, Configurable};
use cretonne::verifier; use cretonne::verifier;
use tempdir::TempDir;
#[test] #[test]
fn testsuite() { fn testsuite() {
@@ -23,13 +29,29 @@ fn testsuite() {
paths.sort_by_key(|dir| dir.path()); paths.sort_by_key(|dir| dir.path());
for path in paths { for path in paths {
let path = path.path(); let path = path.path();
match handle_module(path) { handle_module(path, None);
Ok(()) => (),
Err(message) => println!("{}", message),
};
} }
} }
#[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<Vec<u8>, io::Error> { fn read_wasm_file(path: PathBuf) -> Result<Vec<u8>, io::Error> {
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
let file = File::open(path)?; let file = File::open(path)?;
@@ -38,44 +60,80 @@ fn read_wasm_file(path: PathBuf) -> Result<Vec<u8>, io::Error> {
Ok(buf) Ok(buf)
} }
fn handle_module(path: PathBuf) -> Result<(), String> { fn handle_module(path: PathBuf, isa: Option<&TargetIsa>) {
let data = match path.extension() { let data = match path.extension() {
None => { None => {
return Err(String::from("the file extension is not wasm or wast")); panic!("the file extension is not wasm or wat");
} }
Some(ext) => { Some(ext) => {
match ext.to_str() { match ext.to_str() {
Some("wasm") => { Some("wasm") => {
match read_wasm_file(path.clone()) { 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, Ok(data) => data,
Err(err) => { Err(err) => {
return Err(String::from(err.description())); panic!("error reading converted wasm file: {}", err.description());
} }
} }
} }
None | Some(&_) => { None | Some(&_) => panic!("the file extension is not wasm or wat"),
return Err(String::from("the file extension is not wasm or wast"));
}
} }
} }
}; };
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 translation = {
let runtime: &mut WasmRuntime = &mut dummy_runtime; let runtime: &mut WasmRuntime = &mut dummy_runtime;
match translate_module(&data, runtime) { match translate_module(&data, runtime) {
Ok(x) => x, Ok(x) => x,
Err(string) => { Err(string) => {
return Err(string); panic!(string);
} }
} }
}; };
for func in &translation.functions { for func in &translation.functions {
match verifier::verify_function(func, None) { match verifier::verify_function(func, isa) {
Ok(()) => (), Ok(()) => (),
Err(err) => return Err(pretty_verifier_error(func, None, err)), Err(err) => panic!(pretty_verifier_error(func, isa, err)),
} }
} }
Ok(())
} }