Dumped code from the wasm2cretonne repo.

Integrated wasm test suite translation as cretonne test

Fixes #146.
Fixes #143.
This commit is contained in:
Denis Merigoux
2017-08-10 16:05:04 -07:00
committed by Jakob Stoklund Olesen
parent e8276ed965
commit ee9989c4b9
20 changed files with 2804 additions and 5 deletions

View File

@@ -15,11 +15,15 @@ path = "src/cton-util.rs"
[dependencies]
cretonne = { path = "lib/cretonne" }
cretonne-reader = { path = "lib/reader" }
cretonne-frontend = { path ="lib/frontend" }
cretonne-frontend = { path = "lib/frontend" }
cretonne-wasm = { path = "lib/wasm" }
filecheck = { path = "lib/filecheck" }
wasmparser = "0.6.1"
docopt = "0.8.0"
serde = "1.0.8"
serde_derive = "1.0.8"
num_cpus = "1.5.1"
tempdir="0.3.5"
term = "0.4.6"
[workspace]

View File

@@ -1,12 +1,16 @@
#[macro_use(dbg)]
extern crate cretonne;
extern crate cton_reader;
extern crate cretonne_wasm;
extern crate wasmparser;
extern crate docopt;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate filecheck;
extern crate num_cpus;
extern crate tempdir;
extern crate term;
use cretonne::VERSION;
use docopt::Docopt;
@@ -18,6 +22,7 @@ mod filetest;
mod cat;
mod print_cfg;
mod rsfilecheck;
mod wasm;
const USAGE: &str = "
Cretonne code generator utility
@@ -27,12 +32,15 @@ Usage:
cton-util cat <file>...
cton-util filecheck [-v] <file>
cton-util print-cfg <file>...
cton-util wasm [-cvo] <file>...
cton-util --help | --version
Options:
-v, --verbose be more verbose
-h, --help print this help message
--version print the Cretonne version
-v, --verbose be more verbose
-c, --check checks the correctness of Cretonne IL translated from WebAssembly
-o, --optimize runs otpimization passes on translated WebAssembly functions
-h, --help print this help message
--version print the Cretonne version
";
@@ -42,7 +50,10 @@ struct Args {
cmd_cat: bool,
cmd_filecheck: bool,
cmd_print_cfg: bool,
cmd_wasm: bool,
arg_file: Vec<String>,
flag_check: bool,
flag_optimize: bool,
flag_verbose: bool,
}
@@ -69,6 +80,11 @@ fn cton_util() -> CommandResult {
rsfilecheck::run(args.arg_file, args.flag_verbose)
} else if args.cmd_print_cfg {
print_cfg::run(args.arg_file)
} else if args.cmd_wasm {
wasm::run(args.arg_file,
args.flag_verbose,
args.flag_optimize,
args.flag_check)
} else {
// Debugging / shouldn't happen with proper command line handling above.
Err(format!("Unhandled args: {:?}", args))

227
cranelift/src/wasm.rs Normal file
View File

@@ -0,0 +1,227 @@
//! CLI tool to use the functions provided by crates [wasm2cretonne](../wasm2cretonne/index.html)
//! and [wasmstandalone](../wasmstandalone/index.html).
//!
//! 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
//! and tables, then emitting the translated code with hardcoded addresses to memory.
use cretonne_wasm::{translate_module, FunctionTranslation, DummyRuntime, WasmRuntime};
use std::path::PathBuf;
use cretonne::loop_analysis::LoopAnalysis;
use cretonne::flowgraph::ControlFlowGraph;
use cretonne::dominator_tree::DominatorTree;
use cretonne::Context;
use cretonne::result::CtonError;
use cretonne::ir;
use cretonne::ir::entities::AnyEntity;
use cretonne::isa::TargetIsa;
use cretonne::verifier;
use std::fs::File;
use std::error::Error;
use std::io;
use std::io::BufReader;
use std::io::prelude::*;
use std::path::Path;
use std::process::Command;
use tempdir::TempDir;
use term;
macro_rules! vprintln {
($x: expr, $($tts:tt)*) => {
if $x {
println!($($tts)*);
}
}
}
macro_rules! vprint {
($x: expr, $($tts:tt)*) => {
if $x {
print!($($tts)*);
}
}
}
fn read_wasm_file(path: PathBuf) -> Result<Vec<u8>, io::Error> {
let mut buf: Vec<u8> = Vec::new();
let file = File::open(path)?;
let mut buf_reader = BufReader::new(file);
buf_reader.read_to_end(&mut buf)?;
Ok(buf)
}
pub fn run(files: Vec<String>,
flag_verbose: bool,
flag_optimize: bool,
flag_check: bool)
-> Result<(), String> {
for filename in files.iter() {
let path = Path::new(&filename);
let name = String::from(path.as_os_str().to_string_lossy());
match handle_module(flag_verbose,
flag_optimize,
flag_check,
path.to_path_buf(),
name) {
Ok(()) => {}
Err(message) => return Err(message),
}
}
Ok(())
}
fn handle_module(flag_verbose: bool,
flag_optimize: bool,
flag_check: bool,
path: PathBuf,
name: String)
-> Result<(), String> {
let mut terminal = term::stdout().unwrap();
terminal.fg(term::color::YELLOW).unwrap();
vprint!(flag_verbose, "Handling: ");
terminal.reset().unwrap();
vprintln!(flag_verbose, "\"{}\"", name);
terminal.fg(term::color::MAGENTA).unwrap();
vprint!(flag_verbose, "Translating...");
terminal.reset().unwrap();
let data = match path.extension() {
None => {
return Err(String::from("the file extension is not wasm or wast"));
}
Some(ext) => {
match ext.to_str() {
Some("wasm") => {
match read_wasm_file(path.clone()) {
Ok(data) => data,
Err(err) => {
return Err(String::from(err.description()));
}
}
}
Some("wast") => {
let tmp_dir = TempDir::new("wasm2cretonne").unwrap();
let file_path = tmp_dir.path().join("module.wasm");
File::create(file_path.clone()).unwrap();
Command::new("wast2wasm")
.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"));
} else {
return Err(String::from(e.description()));
})
.unwrap();
match read_wasm_file(file_path) {
Ok(data) => data,
Err(err) => {
return Err(String::from(err.description()));
}
}
}
None | Some(&_) => {
return Err(String::from("the file extension is not wasm or wast"));
}
}
}
};
let mut dummy_runtime = DummyRuntime::new();
let translation = {
let mut runtime: &mut WasmRuntime = &mut dummy_runtime;
match translate_module(&data, runtime) {
Ok(x) => x,
Err(string) => {
return Err(string);
}
}
};
terminal.fg(term::color::GREEN).unwrap();
vprintln!(flag_verbose, " ok");
terminal.reset().unwrap();
if flag_check {
terminal.fg(term::color::MAGENTA).unwrap();
vprint!(flag_verbose, "Checking... ");
terminal.reset().unwrap();
for func in translation.functions.iter() {
let il = match func {
&FunctionTranslation::Import() => continue,
&FunctionTranslation::Code { ref il, .. } => il.clone(),
};
match verifier::verify_function(&il, None) {
Ok(()) => (),
Err(err) => return Err(pretty_verifier_error(&il, None, err)),
}
}
terminal.fg(term::color::GREEN).unwrap();
vprintln!(flag_verbose, " ok");
terminal.reset().unwrap();
}
if flag_optimize {
terminal.fg(term::color::MAGENTA).unwrap();
vprint!(flag_verbose, "Optimizing... ");
terminal.reset().unwrap();
for func in translation.functions.iter() {
let mut il = match func {
&FunctionTranslation::Import() => continue,
&FunctionTranslation::Code { ref il, .. } => il.clone(),
};
let mut loop_analysis = LoopAnalysis::new();
let mut cfg = ControlFlowGraph::new();
cfg.compute(&il);
let mut domtree = DominatorTree::new();
domtree.compute(&mut il, &cfg);
loop_analysis.compute(&mut il, &mut cfg, &mut domtree);
let mut context = Context::new();
context.func = il;
context.cfg = cfg;
context.domtree = domtree;
context.loop_analysis = loop_analysis;
match verifier::verify_context(&context.func, &context.cfg, &context.domtree, None) {
Ok(()) => (),
Err(err) => {
return Err(pretty_verifier_error(&context.func, None, err));
}
};
match context.licm() {
Ok(())=> (),
Err(error) => {
match error {
CtonError::Verifier(err) => {
return Err(pretty_verifier_error(&context.func, None, err));
}
CtonError::ImplLimitExceeded |
CtonError::CodeTooLarge => return Err(String::from(error.description())),
}
}
};
match verifier::verify_context(&context.func, &context.cfg, &context.domtree, None) {
Ok(()) => (),
Err(err) => return Err(pretty_verifier_error(&context.func, None, err)),
}
}
terminal.fg(term::color::GREEN).unwrap();
vprintln!(flag_verbose, " ok");
terminal.reset().unwrap();
}
Ok(())
}
/// Pretty-print a verifier error.
pub fn pretty_verifier_error(func: &ir::Function,
isa: Option<&TargetIsa>,
err: verifier::Error)
-> String {
let msg = err.to_string();
let str1 = match err.location {
AnyEntity::Inst(inst) => {
format!("{}\n{}: {}\n\n",
msg,
inst,
func.dfg.display_inst(inst, isa))
}
_ => String::from(format!("{}\n", msg)),
};
format!("{}{}", str1, func.display(isa))
}

View File

@@ -40,7 +40,8 @@ if [ -n "$needcheck" ]; then
touch $tsfile || echo no target directory
fi
PKGS="cretonne cretonne-reader cretonne-tools cretonne-frontend filecheck"
PKGS="cretonne cretonne-reader cretonne-tools cretonne-frontend cretonne-wasm \
filecheck "
cd "$topdir"
for PKG in $PKGS
do

View File

@@ -0,0 +1,13 @@
(module
(memory 1)
(func $main (local i32)
(set_local 0 (i32.sub (i32.const 4) (i32.const 4)))
(if
(get_local 0)
(then unreachable)
(else (drop (i32.mul (i32.const 6) (get_local 0))))
)
)
(start $main)
(data (i32.const 0) "abcdefgh")
)

View File

@@ -0,0 +1,10 @@
(module
(func $main (local i32)
(set_local 0 (i32.const 0))
(drop (call $inc))
)
(func $inc (result i32)
(i32.const 1)
)
(start $main)
)

View File

@@ -0,0 +1,22 @@
(module
(memory 1)
(func $main (local i32 i32 i32 i32)
(set_local 0 (i32.const 0))
(set_local 1 (i32.const 1))
(set_local 2 (i32.const 1))
(set_local 3 (i32.const 0))
(block
(loop
(br_if 1 (i32.gt_s (get_local 0) (i32.const 5)))
(set_local 3 (get_local 2))
(set_local 2 (i32.add (get_local 2) (get_local 1)))
(set_local 1 (get_local 3))
(set_local 0 (i32.add (get_local 0) (i32.const 1)))
(br 0)
)
)
(i32.store (i32.const 0) (get_local 2))
)
(start $main)
(data (i32.const 0) "0000")
)

View File

@@ -0,0 +1,8 @@
(module
(global $x (mut i32) (i32.const 4))
(memory 1)
(func $main (local i32)
(i32.store (i32.const 0) (get_global $x))
)
(start $main)
)

View File

@@ -0,0 +1,11 @@
(module
(memory 1)
(func $main (local i32)
(i32.store (i32.const 0) (i32.const 0x0))
(if (i32.load (i32.const 0))
(then (i32.store (i32.const 0) (i32.const 0xa)))
(else (i32.store (i32.const 0) (i32.const 0xb))))
)
(start $main)
(data (i32.const 0) "0000")
)

3
lib/wasm/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
target/
**/*.rs.bk
Cargo.lock

13
lib/wasm/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "cretonne-wasm"
version = "0.0.0"
authors = ["The Cretonne Project Developers"]
publish = false
description = "Translator from WebAssembly to Cretonne IL"
repository = "https://github.com/stoklund/cretonne"
license = "Apache-2.0"
[dependencies]
wasmparser = "0.6.1"
cretonne = { path = "../cretonne" }
cretonne-frontend = { path = "../frontend" }

File diff suppressed because it is too large Load Diff

27
lib/wasm/src/lib.rs Normal file
View File

@@ -0,0 +1,27 @@
//! Performs the translation from a wasm module in binary format to the in-memory representation
//! of the Cretonne IL. More particularly, it translates the code of all the functions bodies and
//! interacts with a runtime implementing the [`WasmRuntime`](trait.WasmRuntime.html) trait to
//! deal with tables, globals and linear memory.
//!
//! The crate provides a `DummyRuntime` trait that will allow to translate the code of the
//! functions but will fail at execution. You should use
//! [`wasmstandalone::StandaloneRuntime`](../wasmstandalone/struct.StandaloneRuntime.html) to be
//! able to execute the translated code.
//!
//! The main function of this module is [`translate_module`](fn.translate_module.html).
extern crate wasmparser;
extern crate cton_frontend;
extern crate cretonne;
mod module_translator;
mod translation_utils;
mod code_translator;
mod runtime;
mod sections_translator;
pub use module_translator::{translate_module, TranslationResult, FunctionTranslation,
ImportMappings};
pub use runtime::{WasmRuntime, DummyRuntime};
pub use translation_utils::{Local, FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, RawByte,
MemoryAddress, SignatureIndex, Global, GlobalInit, Table, Memory};

View File

@@ -0,0 +1,288 @@
//! Translation skeletton that traverses the whole WebAssembly module and call helper functions
//! to deal with each part of it.
use wasmparser::{ParserState, SectionCode, ParserInput, Parser, WasmDecoder};
use sections_translator::{SectionParsingError, parse_function_signatures, parse_import_section,
parse_function_section, parse_export_section, parse_memory_section,
parse_global_section, parse_table_section, parse_elements_section,
parse_data_section};
use translation_utils::{type_to_type, Import, SignatureIndex, FunctionIndex, invert_hashmaps};
use cretonne::ir::{Function, Type, FuncRef, SigRef};
use code_translator::translate_function_body;
use cton_frontend::ILBuilder;
use std::collections::HashMap;
use runtime::WasmRuntime;
/// Output of the [`translate_module`](fn.translate_module.html) function. Contains the translated
/// functions and when present the index of the function defined as `start` of the module.
pub struct TranslationResult {
pub functions: Vec<FunctionTranslation>,
pub start_index: Option<FunctionIndex>,
}
/// A function in a WebAssembly module can be either imported, or defined inside it. If it is
/// defined inside it, then the translation in Cretonne IL is available as well as the mappings
/// between Cretonne imports and indexes in the function index space.
#[derive(Clone)]
pub enum FunctionTranslation {
Code {
il: Function,
imports: ImportMappings,
},
Import(),
}
#[derive(Clone,Debug)]
/// Mappings describing the relations between imports of the Cretonne IL functions and the
/// functions in the WebAssembly module.
pub struct ImportMappings {
/// Find the index of a function in the WebAssembly module thanks to a `FuncRef`.
pub functions: HashMap<FuncRef, FunctionIndex>,
/// Find the index of a signature in the WebAssembly module thanks to a `SigRef`.
pub signatures: HashMap<SigRef, SignatureIndex>,
}
impl ImportMappings {
pub fn new() -> ImportMappings {
ImportMappings {
functions: HashMap::new(),
signatures: HashMap::new(),
}
}
}
/// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cretonne IL
/// [`Function`](../cretonne/ir/function/struct.Function.html).
/// Returns the functions and also the mappings for imported functions and signature between the
/// indexes in the wasm module and the indexes inside each functions.
pub fn translate_module(data: &Vec<u8>,
runtime: &mut WasmRuntime)
-> Result<TranslationResult, String> {
let mut parser = Parser::new(data.as_slice());
match *parser.read() {
ParserState::BeginWasm { .. } => {}
ref s @ _ => panic!("modules should begin properly: {:?}", s),
}
let mut signatures = None;
let mut functions: Option<Vec<SignatureIndex>> = None;
let mut globals = Vec::new();
let mut exports: Option<HashMap<FunctionIndex, String>> = None;
let mut next_input = ParserInput::Default;
let mut function_index: FunctionIndex = 0;
let mut function_imports_count = 0;
let mut start_index: Option<FunctionIndex> = None;
loop {
match *parser.read_with_input(next_input) {
ParserState::BeginSection { code: SectionCode::Type, .. } => {
match parse_function_signatures(&mut parser) {
Ok(sigs) => signatures = Some(sigs),
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the type section: {}", s))
}
};
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Import, .. } => {
match parse_import_section(&mut parser) {
Ok(imps) => {
for import in imps {
match import {
Import::Function { sig_index } => {
functions = match functions {
None => Some(vec![sig_index as SignatureIndex]),
Some(mut funcs) => {
funcs.push(sig_index as SignatureIndex);
Some(funcs)
}
};
function_index += 1;
}
Import::Memory(mem) => {
runtime.declare_memory(mem);
}
Import::Global(glob) => {
runtime.declare_global(glob.clone());
globals.push(glob);
}
Import::Table(tab) => {
runtime.declare_table(tab);
}
}
}
}
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the import section: {}", s))
}
}
function_imports_count = function_index;
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Function, .. } => {
match parse_function_section(&mut parser) {
Ok(funcs) => {
match functions {
None => functions = Some(funcs),
Some(ref mut imps) => imps.extend(funcs),
}
}
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the function section: {}", s))
}
}
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Table, .. } => {
match parse_table_section(&mut parser, runtime) {
Ok(()) => (),
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the table section: {}", s))
}
}
}
ParserState::BeginSection { code: SectionCode::Memory, .. } => {
match parse_memory_section(&mut parser) {
Ok(mems) => {
for mem in mems {
runtime.declare_memory(mem);
}
}
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the memory section: {}", s))
}
}
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Global, .. } => {
match parse_global_section(&mut parser, runtime) {
Ok(mut globs) => globals.append(&mut globs),
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the global section: {}", s))
}
}
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Export, .. } => {
match parse_export_section(&mut parser) {
Ok(exps) => exports = Some(exps),
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the export section: {}", s))
}
}
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Start, .. } => {
match *parser.read() {
ParserState::StartSectionEntry(index) => {
start_index = Some(index as FunctionIndex)
}
_ => return Err(String::from("wrong content in the start section")),
}
match *parser.read() {
ParserState::EndSection => {}
_ => return Err(String::from("wrong content in the start section")),
}
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Element, .. } => {
match parse_elements_section(&mut parser, runtime, &globals) {
Ok(()) => (),
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the element section: {}", s))
}
}
next_input = ParserInput::Default;
}
ParserState::BeginSection { code: SectionCode::Code, .. } => {
// The code section begins
break;
}
ParserState::EndSection => {
next_input = ParserInput::Default;
}
ParserState::EndWasm => {
return Ok(TranslationResult {
functions: Vec::new(),
start_index: None,
})
}
ParserState::BeginSection { code: SectionCode::Data, .. } => {
match parse_data_section(&mut parser, runtime, &globals) {
Ok(()) => (),
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the data section: {}", s))
}
}
}
_ => return Err(String::from("wrong content in the preamble")),
};
}
// At this point we've entered the code section
// First we check that we have all that is necessary to translate a function.
let signatures = match signatures {
None => Vec::new(),
Some(sigs) => sigs,
};
let functions = match functions {
None => return Err(String::from("missing a function section")),
Some(functions) => functions,
};
let mut il_functions: Vec<FunctionTranslation> = Vec::new();
il_functions.resize(function_imports_count, FunctionTranslation::Import());
let mut il_builder = ILBuilder::new();
runtime.begin_translation();
loop {
let locals: Vec<(usize, Type)> = match *parser.read() {
ParserState::BeginFunctionBody { ref locals, .. } => {
locals
.iter()
.map(|&(index, ref ty)| {
(index as usize,
match type_to_type(ty) {
Ok(ty) => ty,
Err(()) => panic!("unsupported type for local variable"),
})
})
.collect()
}
ParserState::EndSection => break,
_ => return Err(String::from(format!("wrong content in code section"))),
};
let signature = signatures[functions[function_index as usize] as usize].clone();
match translate_function_body(&mut parser,
function_index,
signature,
&locals,
&exports,
&signatures,
&functions,
&mut il_builder,
runtime) {
Ok((il_func, imports)) => {
il_functions.push(FunctionTranslation::Code {
il: il_func,
imports: invert_hashmaps(imports),
})
}
Err(s) => return Err(s),
}
function_index += 1;
}
loop {
match *parser.read() {
ParserState::BeginSection { code: SectionCode::Data, .. } => {
match parse_data_section(&mut parser, runtime, &globals) {
Ok(()) => (),
Err(SectionParsingError::WrongSectionContent(s)) => {
return Err(format!("wrong content in the data section: {}", s))
}
}
}
ParserState::EndWasm => {
return Ok(TranslationResult {
functions: il_functions,
start_index,
})
}
_ => (),
}
}
}

View File

@@ -0,0 +1,93 @@
use runtime::WasmRuntime;
use translation_utils::{Local, Global, Memory, Table, GlobalIndex, TableIndex, FunctionIndex,
MemoryIndex};
use cton_frontend::FunctionBuilder;
use cretonne::ir::{Value, InstBuilder, SigRef};
use cretonne::ir::immediates::{Ieee32, Ieee64};
use cretonne::ir::types::*;
/// 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
/// essentially here for translation debug purposes.
pub struct DummyRuntime {
globals: Vec<Global>,
}
impl DummyRuntime {
/// Allocates the runtime data structures.
pub fn new() -> DummyRuntime {
DummyRuntime { globals: Vec::new() }
}
}
impl WasmRuntime for DummyRuntime {
fn translate_get_global(&self,
builder: &mut FunctionBuilder<Local>,
global_index: GlobalIndex)
-> Value {
let ref glob = self.globals.get(global_index as usize).unwrap();
match glob.ty {
I32 => builder.ins().iconst(glob.ty, -1),
I64 => builder.ins().iconst(glob.ty, -1),
F32 => builder.ins().f32const(Ieee32::with_bits(0xbf800000)), // -1.0
F64 => {
builder
.ins()
.f64const(Ieee64::with_bits(0xbff0000000000000))
} // -1.0
_ => panic!("should not happen"),
}
}
fn translate_set_global(&self, _: &mut FunctionBuilder<Local>, _: GlobalIndex, _: Value) {
// We do nothing
}
fn translate_grow_memory(&mut self, builder: &mut FunctionBuilder<Local>, _: Value) -> Value {
builder.ins().iconst(I32, -1)
}
fn translate_current_memory(&mut self, builder: &mut FunctionBuilder<Local>) -> Value {
builder.ins().iconst(I32, -1)
}
fn translate_call_indirect<'a>(&self,
builder: &'a mut FunctionBuilder<Local>,
sig_ref: SigRef,
index_val: Value,
call_args: &[Value])
-> &'a [Value] {
let call_inst = builder.ins().call_indirect(sig_ref, index_val, call_args);
builder.inst_results(call_inst)
}
fn translate_memory_base_address(&self,
builder: &mut FunctionBuilder<Local>,
_: MemoryIndex)
-> Value {
builder.ins().iconst(I64, 0)
}
fn declare_global(&mut self, global: Global) {
self.globals.push(global);
}
fn declare_table(&mut self, _: Table) {
//We do nothing
}
fn declare_table_elements(&mut self, _: TableIndex, _: usize, _: &[FunctionIndex]) {
//We do nothing
}
fn declare_memory(&mut self, _: Memory) {
//We do nothing
}
fn declare_data_initialization(&mut self,
_: MemoryIndex,
_: usize,
_: &[u8])
-> Result<(), String> {
// We do nothing
Ok(())
}
fn begin_translation(&mut self) {
// We do nothing
}
fn next_function(&mut self) {
// We do nothing
}
}

View File

@@ -0,0 +1,5 @@
mod spec;
mod dummy;
pub use runtime::spec::WasmRuntime;
pub use runtime::dummy::DummyRuntime;

View File

@@ -0,0 +1,61 @@
//! All the runtime support necessary for the wasm to cretonne translation is formalized by the
//! trait `WasmRuntime`.
use cton_frontend::FunctionBuilder;
use cretonne::ir::{Value, SigRef};
use translation_utils::{Local, FunctionIndex, TableIndex, GlobalIndex, MemoryIndex, Global, Table,
Memory};
/// 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.
pub trait WasmRuntime {
/// Declares a global to the runtime.
fn declare_global(&mut self, global: Global);
/// Declares a table to the runtime.
fn declare_table(&mut self, table: Table);
/// Fills a declared table with references to functions in the module.
fn declare_table_elements(&mut self,
table_index: TableIndex,
offset: usize,
elements: &[FunctionIndex]);
/// Declares a memory to the runtime
fn declare_memory(&mut self, memory: Memory);
/// Fills a declared memory with bytes at module instantiation.
fn declare_data_initialization(&mut self,
memory_index: MemoryIndex,
offset: usize,
data: &[u8])
-> Result<(), String>;
/// Call this function after having declared all the runtime elements but prior to the
/// function body translation.
fn begin_translation(&mut self);
/// Call this function between each function body translation.
fn next_function(&mut self);
/// Translates a `get_global` wasm instruction.
fn translate_get_global(&self,
builder: &mut FunctionBuilder<Local>,
global_index: GlobalIndex)
-> Value;
/// Translates a `set_global` wasm instruction.
fn translate_set_global(&self,
builder: &mut FunctionBuilder<Local>,
global_index: GlobalIndex,
val: Value);
/// Translates a `grow_memory` wasm instruction. Returns the old size (in pages) of the memory.
fn translate_grow_memory(&mut self, builder: &mut FunctionBuilder<Local>, val: Value) -> Value;
/// Translates a `current_memory` wasm instruction. Returns the size in pages of the memory.
fn translate_current_memory(&mut self, builder: &mut FunctionBuilder<Local>) -> Value;
/// Returns the base address of a wasm memory as a Cretonne `Value`.
fn translate_memory_base_address(&self,
builder: &mut FunctionBuilder<Local>,
index: MemoryIndex)
-> Value;
/// Translates a `call_indirect` wasm instruction. It involves looking up the value contained
/// it the table at location `index_val` and calling the corresponding function.
fn translate_call_indirect<'a>(&self,
builder: &'a mut FunctionBuilder<Local>,
sig_ref: SigRef,
index_val: Value,
call_args: &[Value])
-> &'a [Value];
}

View File

@@ -0,0 +1,372 @@
//! Helper functions to gather information for each of the non-function sections of a
//! WebAssembly module.
//!
//! The code of theses helper function is straightforward since it is only about reading metadata
//! about linear memories, tables, globals, etc. and storing them for later use.
//!
//! The special case of the initialize expressions for table elements offsets or global variables
//! is handled, according to the semantics of WebAssembly, to only specific expressions that are
//! interpreted on the fly.
use translation_utils::{type_to_type, Import, TableIndex, FunctionIndex, GlobalIndex,
SignatureIndex, MemoryIndex, Global, GlobalInit, Table, TableElementType,
Memory};
use cretonne::ir::{Signature, ArgumentType, CallConv};
use cretonne;
use wasmparser::{Parser, ParserState, FuncType, ImportSectionEntryType, ExternalKind, WasmDecoder,
MemoryType, Operator};
use wasmparser;
use std::collections::HashMap;
use std::str::from_utf8;
use runtime::WasmRuntime;
pub enum SectionParsingError {
WrongSectionContent(String),
}
/// Reads the Type Section of the wasm module and returns the corresponding function signatures.
pub fn parse_function_signatures(parser: &mut Parser)
-> Result<Vec<Signature>, SectionParsingError> {
let mut signatures: Vec<Signature> = Vec::new();
loop {
match *parser.read() {
ParserState::EndSection => break,
ParserState::TypeSectionEntry(FuncType {
form: wasmparser::Type::Func,
ref params,
ref returns,
}) => {
let mut sig = Signature::new(CallConv::Native);
sig.argument_types
.extend(params
.iter()
.map(|ty| {
let cret_arg: cretonne::ir::Type = match type_to_type(ty) {
Ok(ty) => ty,
Err(()) => panic!("only numeric types are supported in\
function signatures"),
};
ArgumentType::new(cret_arg)
}));
sig.return_types
.extend(returns
.iter()
.map(|ty| {
let cret_arg: cretonne::ir::Type = match type_to_type(ty) {
Ok(ty) => ty,
Err(()) => panic!("only numeric types are supported in\
function signatures"),
};
ArgumentType::new(cret_arg)
}));
signatures.push(sig);
}
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
}
}
Ok(signatures)
}
/// Retrieves the imports from the imports section of the binary.
pub fn parse_import_section(parser: &mut Parser) -> Result<Vec<Import>, SectionParsingError> {
let mut imports = Vec::new();
loop {
match *parser.read() {
ParserState::ImportSectionEntry {
ty: ImportSectionEntryType::Function(sig), ..
} => imports.push(Import::Function { sig_index: sig }),
ParserState::ImportSectionEntry {
ty: ImportSectionEntryType::Memory(MemoryType { limits: ref memlimits }), ..
} => {
imports.push(Import::Memory(Memory {
pages_count: memlimits.initial as usize,
maximum: memlimits.maximum.map(|x| x as usize),
}))
}
ParserState::ImportSectionEntry {
ty: ImportSectionEntryType::Global(ref ty), ..
} => {
imports.push(Import::Global(Global {
ty: type_to_type(&ty.content_type).unwrap(),
mutability: ty.mutability != 0,
initializer: GlobalInit::Import(),
}));
}
ParserState::ImportSectionEntry {
ty: ImportSectionEntryType::Table(ref tab), ..
} => {
imports.push(Import::Table(Table {
ty: match type_to_type(&tab.element_type) {
Ok(t) => TableElementType::Val(t),
Err(()) => TableElementType::Func(),
},
size: tab.limits.initial as usize,
maximum: tab.limits.maximum.map(|x| x as usize),
}));
}
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
}
Ok(imports)
}
/// Retrieves the correspondances between functions and signatures from the function section
pub fn parse_function_section(parser: &mut Parser)
-> Result<Vec<SignatureIndex>, SectionParsingError> {
let mut funcs = Vec::new();
loop {
match *parser.read() {
ParserState::FunctionSectionEntry(sigindex) => funcs.push(sigindex as SignatureIndex),
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
}
Ok(funcs)
}
/// Retrieves the names of the functions from the export section
pub fn parse_export_section(parser: &mut Parser)
-> Result<HashMap<FunctionIndex, String>, SectionParsingError> {
let mut exports: HashMap<FunctionIndex, String> = HashMap::new();
loop {
match *parser.read() {
ParserState::ExportSectionEntry {
field,
ref kind,
index,
} => {
match kind {
&ExternalKind::Function => {
exports.insert(index as FunctionIndex,
String::from(from_utf8(field).unwrap()));
()
}
_ => (),//TODO: deal with other kind of exports
}
}
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
}
Ok(exports)
}
/// Retrieves the size and maximum fields of memories from the memory section
pub fn parse_memory_section(parser: &mut Parser) -> Result<Vec<Memory>, SectionParsingError> {
let mut memories: Vec<Memory> = Vec::new();
loop {
match *parser.read() {
ParserState::MemorySectionEntry(ref ty) => {
memories.push(Memory {
pages_count: ty.limits.initial as usize,
maximum: ty.limits.maximum.map(|x| x as usize),
})
}
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
}
Ok(memories)
}
/// Retrieves the size and maximum fields of memories from the memory section
pub fn parse_global_section(parser: &mut Parser,
runtime: &mut WasmRuntime)
-> Result<Vec<Global>, SectionParsingError> {
let mut globals = Vec::new();
loop {
let (content_type, mutability) = match *parser.read() {
ParserState::BeginGlobalSectionEntry(ref ty) => (ty.content_type, ty.mutability),
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::BeginInitExpressionBody => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
}
let initializer = match *parser.read() {
ParserState::InitExpressionOperator(Operator::I32Const { value }) => {
GlobalInit::I32Const(value)
}
ParserState::InitExpressionOperator(Operator::I64Const { value }) => {
GlobalInit::I64Const(value)
}
ParserState::InitExpressionOperator(Operator::F32Const { value }) => {
GlobalInit::F32Const(value.bits())
}
ParserState::InitExpressionOperator(Operator::F64Const { value }) => {
GlobalInit::F64Const(value.bits())
}
ParserState::InitExpressionOperator(Operator::GetGlobal { global_index }) => {
GlobalInit::GlobalRef(global_index as GlobalIndex)
}
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::EndInitExpressionBody => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
}
let global = Global {
ty: type_to_type(&content_type).unwrap(),
mutability: mutability != 0,
initializer: initializer,
};
runtime.declare_global(global.clone());
globals.push(global);
match *parser.read() {
ParserState::EndGlobalSectionEntry => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
}
}
Ok(globals)
}
pub fn parse_data_section(parser: &mut Parser,
runtime: &mut WasmRuntime,
globals: &Vec<Global>)
-> Result<(), SectionParsingError> {
loop {
let memory_index = match *parser.read() {
ParserState::BeginDataSectionEntry(memory_index) => memory_index,
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::BeginInitExpressionBody => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
let offset = match *parser.read() {
ParserState::InitExpressionOperator(Operator::I32Const { value }) => {
if value < 0 {
return Err(SectionParsingError::WrongSectionContent(String::from("negative \
offset value")));
} else {
value as usize
}
}
ParserState::InitExpressionOperator(Operator::GetGlobal { global_index }) => {
match globals[global_index as usize].initializer {
GlobalInit::I32Const(value) => {
if value < 0 {
return Err(SectionParsingError::WrongSectionContent(String::from("\
negative offset value")));
} else {
value as usize
}
}
GlobalInit::Import() => {
return Err(SectionParsingError::WrongSectionContent(String::from("\
imported globals not supported")))
} // TODO: add runtime support
_ => panic!("should not happen"),
}
}
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::EndInitExpressionBody => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
{
let data = match *parser.read() {
ParserState::DataSectionEntryBody(data) => data,
ref s @ _ => {
return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s)))
}
};
match runtime.declare_data_initialization(memory_index as MemoryIndex, offset, data) {
Ok(()) => (),
Err(s) => return Err(SectionParsingError::WrongSectionContent(format!("{}", s))),
};
}
match *parser.read() {
ParserState::EndDataSectionEntry => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
}
Ok(())
}
/// Retrieves the tables from the table section
pub fn parse_table_section(parser: &mut Parser,
runtime: &mut WasmRuntime)
-> Result<(), SectionParsingError> {
loop {
match *parser.read() {
ParserState::TableSectionEntry(ref table) => {
runtime.declare_table(Table {
ty: match type_to_type(&table.element_type) {
Ok(t) => TableElementType::Val(t),
Err(()) => TableElementType::Func(),
},
size: table.limits.initial as usize,
maximum: table.limits.maximum.map(|x| x as usize),
})
}
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
}
Ok(())
}
/// Retrieves the tables from the table section
pub fn parse_elements_section(parser: &mut Parser,
runtime: &mut WasmRuntime,
globals: &Vec<Global>)
-> Result<(), SectionParsingError> {
loop {
let table_index = match *parser.read() {
ParserState::BeginElementSectionEntry(ref table_index) => *table_index as TableIndex,
ParserState::EndSection => break,
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::BeginInitExpressionBody => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
let offset = match *parser.read() {
ParserState::InitExpressionOperator(Operator::I32Const { value }) => {
if value < 0 {
return Err(SectionParsingError::WrongSectionContent(String::from("negative \
offset value")));
} else {
value as usize
}
}
ParserState::InitExpressionOperator(Operator::GetGlobal { global_index }) => {
match globals[global_index as usize].initializer {
GlobalInit::I32Const(value) => {
if value < 0 {
return Err(SectionParsingError::WrongSectionContent(String::from("\
negative offset value")));
} else {
value as usize
}
}
GlobalInit::Import() => 0, // TODO: add runtime support
_ => panic!("should not happen"),
}
}
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::EndInitExpressionBody => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::ElementSectionEntryBody(ref elements) => {
let elems: Vec<FunctionIndex> =
elements.iter().map(|&x| x as FunctionIndex).collect();
runtime.declare_table_elements(table_index, offset, elems.as_slice())
}
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
match *parser.read() {
ParserState::EndElementSectionEntry => (),
ref s @ _ => return Err(SectionParsingError::WrongSectionContent(format!("{:?}", s))),
};
}
Ok(())
}

View File

@@ -0,0 +1,138 @@
///! Helper functions and structures for the translation.
use wasmparser;
use cretonne;
use std::u32;
use code_translator;
use module_translator;
/// Index of a function (imported or defined) inside the WebAssembly module.
pub type FunctionIndex = usize;
/// Index of a table (imported or defined) inside the WebAssembly module.
pub type TableIndex = usize;
/// Index of a global variable (imported or defined) inside the WebAssembly module.
pub type GlobalIndex = usize;
/// Index of a linear memory (imported or defined) inside the WebAssembly module.
pub type MemoryIndex = usize;
/// Index of a signature (imported or defined) inside the WebAssembly module.
pub type SignatureIndex = usize;
/// Raw byte read from memory.
pub type RawByte = u8;
/// Pointer referring to a memory address.
pub type MemoryAddress = usize;
/// WebAssembly import.
#[derive(Debug,Clone,Copy)]
pub enum Import {
Function { sig_index: u32 },
Memory(Memory),
Global(Global),
Table(Table),
}
/// WebAssembly global.
#[derive(Debug,Clone,Copy)]
pub struct Global {
pub ty: cretonne::ir::Type,
pub mutability: bool,
pub initializer: GlobalInit,
}
/// Globals are initialized via the four `const` operators or by referring to another import.
#[derive(Debug,Clone,Copy)]
pub enum GlobalInit {
I32Const(i32),
I64Const(i64),
F32Const(u32),
F64Const(u64),
Import(),
GlobalRef(GlobalIndex),
}
/// WebAssembly table.
#[derive(Debug,Clone,Copy)]
pub struct Table {
pub ty: TableElementType,
pub size: usize,
pub maximum: Option<usize>,
}
/// WebAssembly table element. Can be a function or a scalar type.
#[derive(Debug,Clone,Copy)]
pub enum TableElementType {
Val(cretonne::ir::Type),
Func(),
}
/// WebAssembly linear memory.
#[derive(Debug,Clone,Copy)]
pub struct Memory {
pub pages_count: usize,
pub maximum: Option<usize>,
}
/// Wrapper to a `get_local` and `set_local` index. They are WebAssembly's non-SSA variables.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Local(pub u32);
impl cretonne::entity::EntityRef for Local {
fn new(index: usize) -> Self {
assert!(index < (u32::MAX as usize));
Local(index as u32)
}
fn index(self) -> usize {
self.0 as usize
}
}
impl Default for Local {
fn default() -> Local {
Local(u32::MAX)
}
}
/// Helper function translating wasmparser types to Cretonne types when possible.
pub fn type_to_type(ty: &wasmparser::Type) -> Result<cretonne::ir::Type, ()> {
match *ty {
wasmparser::Type::I32 => Ok(cretonne::ir::types::I32),
wasmparser::Type::I64 => Ok(cretonne::ir::types::I64),
wasmparser::Type::F32 => Ok(cretonne::ir::types::F32),
wasmparser::Type::F64 => Ok(cretonne::ir::types::F64),
_ => Err(()),
}
}
/// Turns a `wasmparser` `f32` into a `Cretonne` one.
pub fn f32_translation(x: wasmparser::Ieee32) -> cretonne::ir::immediates::Ieee32 {
cretonne::ir::immediates::Ieee32::with_bits(x.bits())
}
/// Turns a `wasmparser` `f64` into a `Cretonne` one.
pub fn f64_translation(x: wasmparser::Ieee64) -> cretonne::ir::immediates::Ieee64 {
cretonne::ir::immediates::Ieee64::with_bits(x.bits())
}
/// Translate a `wasmparser` type into its `Cretonne` equivalent, when possible
pub fn translate_type(ty: wasmparser::Type) -> Result<Vec<cretonne::ir::Type>, ()> {
match ty {
wasmparser::Type::EmptyBlockType => Ok(Vec::new()),
wasmparser::Type::I32 => Ok(vec![cretonne::ir::types::I32]),
wasmparser::Type::F32 => Ok(vec![cretonne::ir::types::F32]),
wasmparser::Type::I64 => Ok(vec![cretonne::ir::types::I64]),
wasmparser::Type::F64 => Ok(vec![cretonne::ir::types::F64]),
_ => panic!("unsupported return value type"),
}
}
/// Inverts the key-value relation in the imports hashmap. Indeed, these hashmaps are built by
/// feeding the function indexes in the module but are used by the runtime with the `FuncRef` as
/// keys.
pub fn invert_hashmaps(imports: code_translator::FunctionImports)
-> module_translator::ImportMappings {
let mut new_imports = module_translator::ImportMappings::new();
for (func_index, func_ref) in imports.functions.iter() {
new_imports.functions.insert(*func_ref, *func_index);
}
for (sig_index, sig_ref) in imports.signatures.iter() {
new_imports.signatures.insert(*sig_ref, *sig_index);
}
new_imports
}

102
lib/wasm/tests/testsuite.rs Normal file
View File

@@ -0,0 +1,102 @@
extern crate cretonne_wasm;
extern crate cretonne;
use cretonne_wasm::{translate_module, FunctionTranslation, DummyRuntime, WasmRuntime};
use std::path::PathBuf;
use std::fs::File;
use std::error::Error;
use std::io;
use std::io::BufReader;
use std::io::prelude::*;
use std::fs;
use cretonne::ir;
use cretonne::ir::entities::AnyEntity;
use cretonne::isa::TargetIsa;
use cretonne::verifier;
#[test]
fn testsuite() {
let mut paths: Vec<_> = fs::read_dir("../../wasmtests")
.unwrap()
.map(|r| r.unwrap())
.collect();
paths.sort_by_key(|dir| dir.path());
for path in paths {
let path = path.path();
match handle_module(path) {
Ok(()) => (),
Err(message) => println!("{}", message),
};
}
}
fn read_wasm_file(path: PathBuf) -> Result<Vec<u8>, io::Error> {
let mut buf: Vec<u8> = Vec::new();
let file = File::open(path)?;
let mut buf_reader = BufReader::new(file);
buf_reader.read_to_end(&mut buf)?;
Ok(buf)
}
fn handle_module(path: PathBuf) -> Result<(), String> {
let data = match path.extension() {
None => {
return Err(String::from("the file extension is not wasm or wast"));
}
Some(ext) => {
match ext.to_str() {
Some("wasm") => {
match read_wasm_file(path.clone()) {
Ok(data) => data,
Err(err) => {
return Err(String::from(err.description()));
}
}
}
None | Some(&_) => {
return Err(String::from("the file extension is not wasm or wast"));
}
}
}
};
let mut dummy_runtime = DummyRuntime::new();
let translation = {
let mut runtime: &mut WasmRuntime = &mut dummy_runtime;
match translate_module(&data, runtime) {
Ok(x) => x,
Err(string) => {
return Err(string);
}
}
};
for func in translation.functions.iter() {
let il = match func {
&FunctionTranslation::Import() => continue,
&FunctionTranslation::Code { ref il, .. } => il.clone(),
};
match verifier::verify_function(&il, None) {
Ok(()) => (),
Err(err) => return Err(pretty_verifier_error(&il, None, err)),
}
}
Ok(())
}
/// Pretty-print a verifier error.
pub fn pretty_verifier_error(func: &ir::Function,
isa: Option<&TargetIsa>,
err: verifier::Error)
-> String {
let msg = err.to_string();
let str1 = match err.location {
AnyEntity::Inst(inst) => {
format!("{}\n{}: {}\n\n",
msg,
inst,
func.dfg.display_inst(inst, isa))
}
_ => String::from(format!("{}\n", msg)),
};
format!("{}{}", str1, func.display(isa))
}