diff --git a/Cargo.toml b/Cargo.toml index e28e3ad577..47045d1b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,18 @@ publish = false name = "wasmstandalone" path = "src/main.rs" +[[bin]] +name = "wasm2obj" +path = "src/wasm2obj.rs" + [dependencies] cretonne = { path = "/home/sunfish/rust/cretonne/lib/cretonne" } +cretonne-frontend = { path = "/home/sunfish/rust/cretonne/lib/frontend" } cretonne-reader = { path = "/home/sunfish/rust/cretonne/lib/reader" } cretonne-wasm = { path = "/home/sunfish/rust/cretonne/lib/wasm" } cretonne-native = { path = "/home/sunfish/rust/cretonne/lib/native" } wasmstandalone = { path = "lib/wasmstandalone" } +wasm2obj = { path = "lib/wasm2obj" } wasmparser = "0.8.2" wasmtext = { git = "https://github.com/yurydelendik/wasmtext" } filecheck = "0.0.1" @@ -26,6 +32,7 @@ serde = "1.0.8" serde_derive = "1.0.8" num_cpus = "1.5.1" term = "0.4.6" -tempdir="*" +tempdir = "*" +faerie = { git = "https://github.com/m4b/faerie" } [workspace] diff --git a/lib/wasm2obj/.gitignore b/lib/wasm2obj/.gitignore new file mode 100644 index 0000000000..4308d82204 --- /dev/null +++ b/lib/wasm2obj/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/lib/wasm2obj/Cargo.toml b/lib/wasm2obj/Cargo.toml new file mode 100644 index 0000000000..d00ad37f45 --- /dev/null +++ b/lib/wasm2obj/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "wasm2obj" +version = "0.0.0" +authors = ["The Cretonne Project Developers"] +publish = false + +[dependencies] +cretonne = { path = "/home/sunfish/rust/cretonne/lib/cretonne" } +cretonne-frontend = { path = "/home/sunfish/rust/cretonne/lib/frontend" } +cretonne-wasm = { path = "/home/sunfish/rust/cretonne/lib/wasm" } +faerie = { git = "https://github.com/m4b/faerie" } diff --git a/lib/wasm2obj/src/emit_module.rs b/lib/wasm2obj/src/emit_module.rs new file mode 100644 index 0000000000..79c8a9b0f3 --- /dev/null +++ b/lib/wasm2obj/src/emit_module.rs @@ -0,0 +1,116 @@ +use cretonne::Context; +use cretonne::settings; +use cretonne::isa::TargetIsa; +use cretonne::verify_function; +use cretonne::verifier; +use cretonne::settings::Configurable; +use cretonne::result::CtonError; +use cretonne::ir::entities::AnyEntity; +use cretonne::ir::{self, Ebb, FuncRef, JumpTable, Function}; +use cretonne::binemit::{RelocSink, Reloc, CodeOffset}; +use cton_wasm::TranslationResult; +use std::collections::HashMap; +use std::fmt::Write; +use faerie::Artifact; + +type RelocRef = u16; + +// Implementation of a relocation sink that just saves all the information for later +struct FaerieRelocSink { + ebbs: HashMap, + funcs: HashMap, + jts: HashMap, +} + +impl RelocSink for FaerieRelocSink { + fn reloc_ebb(&mut self, offset: CodeOffset, reloc: Reloc, ebb: Ebb) { + self.ebbs.insert(reloc.0, (ebb, offset)); + } + fn reloc_func(&mut self, offset: CodeOffset, reloc: Reloc, func: FuncRef) { + self.funcs.insert(reloc.0, (func, offset)); + } + fn reloc_jt(&mut self, offset: CodeOffset, reloc: Reloc, jt: JumpTable) { + self.jts.insert(reloc.0, (jt, offset)); + } +} + +impl FaerieRelocSink { + fn new() -> FaerieRelocSink { + FaerieRelocSink { + ebbs: HashMap::new(), + funcs: HashMap::new(), + jts: HashMap::new(), + } + } +} + +/// Emits a module that has been emitted with the `WasmRuntime` runtime +/// implementation to a native object file. +pub fn emit_module( + trans_result: &TranslationResult, + obj: &mut Artifact, + isa: &TargetIsa, +) -> Result<(), String> { + debug_assert!( + trans_result.start_index.is_none() || + trans_result.start_index.unwrap() >= trans_result.function_imports_count, + "imported start functions not supported yet" + ); + + let mut shared_builder = settings::builder(); + shared_builder.enable("enable_verifier").expect( + "Missing enable_verifier setting", + ); + + for function in &trans_result.functions { + let mut context = Context::new(); + verify_function(function, isa).unwrap(); + context.func = function.clone(); // TODO: Avoid this clone. + let code_size = context.compile(&*isa).map_err(|e| { + pretty_error(&context.func, Some(isa), e) + })? as usize; + if code_size == 0 { + return Err(String::from("no code generated by Cretonne")); + } + let mut code_buf: Vec = Vec::with_capacity(code_size); + code_buf.resize(code_size, 0); + let mut relocsink = FaerieRelocSink::new(); + context.emit_to_memory(code_buf.as_mut_ptr(), &mut relocsink, &*isa); + + // FIXME: get the real linkage name of the function + obj.add_code("the_function_name", code_buf); + + assert!(relocsink.jts.is_empty(), "jump tables not yet implemented"); + assert!(relocsink.ebbs.is_empty(), "ebb relocs not yet implemented"); + assert!( + relocsink.funcs.is_empty(), + "function relocs not yet implemented" + ); + + // FIXME: handle imports + } + + Ok(()) +} + +/// Pretty-print a verifier error. +fn pretty_verifier_error(func: &Function, isa: Option<&TargetIsa>, err: verifier::Error) -> String { + let mut msg = err.to_string(); + match err.location { + AnyEntity::Inst(inst) => { + write!(msg, "\n{}: {}\n\n", inst, func.dfg.display_inst(inst, isa)).unwrap() + } + _ => msg.push('\n'), + } + write!(msg, "{}", func.display(isa)).unwrap(); + msg +} + +/// Pretty-print a Cretonne error. +fn pretty_error(func: &ir::Function, isa: Option<&TargetIsa>, err: CtonError) -> String { + if let CtonError::Verifier(e) = err { + pretty_verifier_error(func, isa, e) + } else { + err.to_string() + } +} diff --git a/lib/wasm2obj/src/lib.rs b/lib/wasm2obj/src/lib.rs new file mode 100644 index 0000000000..d79f851abf --- /dev/null +++ b/lib/wasm2obj/src/lib.rs @@ -0,0 +1,7 @@ +extern crate cretonne; +extern crate cton_wasm; +extern crate faerie; + +mod emit_module; + +pub use emit_module::emit_module; diff --git a/lib/wasmstandalone/src/standalone.rs b/lib/wasmstandalone/src/standalone.rs index c83c8c94bc..a9c35968ce 100644 --- a/lib/wasmstandalone/src/standalone.rs +++ b/lib/wasmstandalone/src/standalone.rs @@ -16,7 +16,7 @@ use std::ptr::copy_nonoverlapping; use std::ptr::write; #[derive(Clone, Debug)] -enum TableElement { +pub enum TableElement { Trap(), Function(FunctionIndex), } @@ -31,15 +31,22 @@ struct GlobalsData { info: Vec, } -struct TableData { - data: Vec, - elements: Vec, - info: Table, +/// A description of a WebAssembly table. +pub struct TableData { + /// The data stored in the table. + pub data: Vec, + /// Function indices to be stored in the table. + pub elements: Vec, + /// The description of the table. + pub info: Table, } -struct MemoryData { - data: Vec, - info: Memory, +/// A description of a WebAssembly linear memory. +pub struct MemoryData { + /// The data stored in the memory. + pub data: Vec, + /// The description of the memory. + pub info: Memory, } const PAGE_SIZE: usize = 65536; @@ -58,8 +65,10 @@ pub struct StandaloneRuntime { imported_funcs: Vec, globals: GlobalsData, - tables: Vec, - memories: Vec, + /// WebAssembly tables. + pub tables: Vec, + /// WebAssembly linear memories. + pub memories: Vec, instantiated: bool, has_current_memory: Option, diff --git a/src/main.rs b/src/main.rs index 8165f8f416..86c9be958e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -//! CLI tool to use the functions provided by crates [wasm2cretonne](../wasm2cretonne/index.html) -//! and [wasmstandalone](../wasmstandalone/index.html). +//! CLI tool to use the functions provided by the [wasmstandalone](../wasmstandalone/index.html) +//! crate. //! //! Reads Wasm binary files (one Wasm module per file), translates the functions' code to Cretonne //! IL. Can also executes the `start` function of the module by laying out the memories, globals diff --git a/src/wasm2obj.rs b/src/wasm2obj.rs new file mode 100644 index 0000000000..5b4c6d00aa --- /dev/null +++ b/src/wasm2obj.rs @@ -0,0 +1,134 @@ +//! Translation from wasm to native object files. +//! +//! Reads a Wasm binary file, translates the functions' code to Cretonne +//! IL, then translates it to native code, and writes it out to a native +//! object file with relocations. + +extern crate cton_wasm; +extern crate wasm2obj; +extern crate cretonne; +extern crate cton_native; +extern crate docopt; +#[macro_use] +extern crate serde_derive; +extern crate wasmstandalone; +extern crate faerie; + +use cton_wasm::translate_module; +use cretonne::settings; +use wasm2obj::emit_module; +use std::path::PathBuf; +use std::fs::File; +use std::error::Error; +use std::io; +use std::io::BufReader; +use std::io::prelude::*; +use docopt::Docopt; +use std::path::Path; +use std::process; +use std::fmt::format; +use faerie::{Artifact, Elf, Target}; + +const USAGE: &str = " +Wasm to native object translation utility. +Takes a binary WebAssembly module into a native object file. +The translation is dependent on the runtime chosen. +The default is a dummy runtime that produces placeholder values. + +Usage: + wasm2obj -o + wasm2obj --help | --version + +Options: + -v, --verbose displays the module and translated functions + -h, --help print this help message + --version print the Cretonne version +"; + +#[derive(Deserialize, Debug, Clone)] +struct Args { + arg_file: String, + arg_output: String, +} + +fn read_wasm_file(path: PathBuf) -> Result, io::Error> { + let mut buf: Vec = Vec::new(); + let file = File::open(path)?; + let mut buf_reader = BufReader::new(file); + buf_reader.read_to_end(&mut buf)?; + Ok(buf) +} + +fn main() { + let args: Args = Docopt::new(USAGE) + .and_then(|d| { + d.help(true) + .version(Some(String::from("0.0.0"))) + .deserialize() + }) + .unwrap_or_else(|e| e.exit()); + + let path = Path::new(&args.arg_file); + match handle_module(path.to_path_buf(), &args.arg_output) { + Ok(()) => {} + Err(message) => { + println!(" error: {}", message); + process::exit(1); + } + } +} + +fn handle_module(path: PathBuf, output: &str) -> Result<(), String> { + let data = match read_wasm_file(path) { + Ok(data) => data, + Err(err) => { + return Err(String::from(err.description())); + } + }; + + let (flag_builder, isa_builder) = cton_native::builders().unwrap_or_else(|_| { + panic!("host machine is not a supported target"); + }); + let isa = isa_builder.finish(settings::Flags::new(&flag_builder)); + + let mut runtime = wasmstandalone::StandaloneRuntime::with_flags(isa.flags().clone()); + + let translation = { + match translate_module(&data, &mut runtime) { + Ok(x) => x, + Err(string) => { + return Err(string); + } + } + }; + + // FIXME: Make the target a parameter. + // FIXME: Make the output filename a parameter. + let mut obj = Artifact::new(Target::X86_64, Some(String::from(output))); + + emit_module(&translation, &mut obj, &*isa)?; + + if !runtime.tables.is_empty() { + if runtime.tables.len() > 1 { + return Err(String::from("multiple tables not supported yet")); + } + obj.add_data("table", runtime.tables[0].data.clone()); + } + + if !runtime.memories.is_empty() { + if runtime.memories.len() > 1 { + return Err(String::from("multiple memories not supported yet")); + } + obj.add_data("memory", runtime.memories[0].data.clone()); + } + + // FIXME: Make the format a parameter. + let file = ::std::fs::File::create(Path::new(output)).map_err(|x| { + format(format_args!("{}", x)) + })?; + obj.write::(file).map_err( + |x| format(format_args!("{}", x)), + )?; + + Ok(()) +}