Dumped code from the wasm2cretonne repo.
Integrated wasm test suite translation as cretonne test Fixes #146. Fixes #143.
This commit is contained in:
committed by
Jakob Stoklund Olesen
parent
e8276ed965
commit
ee9989c4b9
@@ -16,10 +16,14 @@ path = "src/cton-util.rs"
|
||||
cretonne = { path = "lib/cretonne" }
|
||||
cretonne-reader = { path = "lib/reader" }
|
||||
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]
|
||||
|
||||
@@ -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,10 +32,13 @@ 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
|
||||
-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
227
cranelift/src/wasm.rs
Normal 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))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
13
cranelift/wasmtests/arith.wast
Normal file
13
cranelift/wasmtests/arith.wast
Normal 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")
|
||||
)
|
||||
10
cranelift/wasmtests/call.wast
Normal file
10
cranelift/wasmtests/call.wast
Normal 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)
|
||||
)
|
||||
22
cranelift/wasmtests/fibonacci.wast
Normal file
22
cranelift/wasmtests/fibonacci.wast
Normal 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")
|
||||
)
|
||||
8
cranelift/wasmtests/globals.wast
Normal file
8
cranelift/wasmtests/globals.wast
Normal 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)
|
||||
)
|
||||
11
cranelift/wasmtests/memory.wast
Normal file
11
cranelift/wasmtests/memory.wast
Normal 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
3
lib/wasm/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
13
lib/wasm/Cargo.toml
Normal file
13
lib/wasm/Cargo.toml
Normal 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" }
|
||||
1385
lib/wasm/src/code_translator.rs
Normal file
1385
lib/wasm/src/code_translator.rs
Normal file
File diff suppressed because it is too large
Load Diff
27
lib/wasm/src/lib.rs
Normal file
27
lib/wasm/src/lib.rs
Normal 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};
|
||||
288
lib/wasm/src/module_translator.rs
Normal file
288
lib/wasm/src/module_translator.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
93
lib/wasm/src/runtime/dummy.rs
Normal file
93
lib/wasm/src/runtime/dummy.rs
Normal 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
|
||||
}
|
||||
}
|
||||
5
lib/wasm/src/runtime/mod.rs
Normal file
5
lib/wasm/src/runtime/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod spec;
|
||||
mod dummy;
|
||||
|
||||
pub use runtime::spec::WasmRuntime;
|
||||
pub use runtime::dummy::DummyRuntime;
|
||||
61
lib/wasm/src/runtime/spec.rs
Normal file
61
lib/wasm/src/runtime/spec.rs
Normal 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];
|
||||
}
|
||||
372
lib/wasm/src/sections_translator.rs
Normal file
372
lib/wasm/src/sections_translator.rs
Normal 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(())
|
||||
}
|
||||
138
lib/wasm/src/translation_utils.rs
Normal file
138
lib/wasm/src/translation_utils.rs
Normal 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
102
lib/wasm/tests/testsuite.rs
Normal 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))
|
||||
}
|
||||
Reference in New Issue
Block a user