From ca1b461375f8b07697c392e88101da04c276d953 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 12 Oct 2017 13:21:29 -0700 Subject: [PATCH] Begin internal reorganization. This begins reorganizing how translation and compilation occur, and setting up infrastructure for imports/exports and relocations. It splits parts out of StandaloneRuntime, forming Module, Compilation, and Instance structs, which can be used more independently. It also simplifies the command-line interface, in a step towards making simple tools that just expose the functionality of the libraries. --- Cargo.toml | 5 - lib/execute/src/lib.rs | 198 ++--------- lib/obj/src/emit_module.rs | 103 +----- lib/runtime/Cargo.toml | 1 + lib/runtime/src/compilation.rs | 21 ++ lib/runtime/src/instance.rs | 90 +++++ lib/runtime/src/lib.rs | 587 ++++++++++++++++----------------- lib/runtime/src/module.rs | 91 +++++ src/main.rs | 347 +++---------------- src/wasm2obj.rs | 32 +- 10 files changed, 607 insertions(+), 868 deletions(-) create mode 100644 lib/runtime/src/compilation.rs create mode 100644 lib/runtime/src/instance.rs create mode 100644 lib/runtime/src/module.rs diff --git a/Cargo.toml b/Cargo.toml index 4d5f85a533..61a5982e73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,14 +25,9 @@ cretonne-native = { git = "https://github.com/stoklund/cretonne.git" } wasmstandalone_runtime = { path = "lib/runtime" } wasmstandalone_execute = { path = "lib/execute" } wasmstandalone_obj = { path = "lib/obj" } -wasmparser = "0.11.2" -wasmtext = { git = "https://github.com/yurydelendik/wasmtext" } -filecheck = "0.0.1" docopt = "0.8.0" serde = "1.0.8" serde_derive = "1.0.8" -num_cpus = "1.5.1" -term = "0.4.6" tempdir = "*" faerie = { git = "https://github.com/m4b/faerie" } diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index fcde102267..cb59d9e9ee 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -7,115 +7,58 @@ extern crate cton_wasm; extern crate region; extern crate wasmstandalone_runtime; -use cretonne::Context; use cretonne::isa::TargetIsa; -use cretonne::verify_function; -use cretonne::verifier; -use cretonne::result::CtonError; -use cretonne::ir::entities::AnyEntity; -use cretonne::ir::{Ebb, FuncRef, JumpTable, Function}; -use cretonne::binemit::{RelocSink, Reloc, CodeOffset}; -use cton_wasm::{TranslationResult, FunctionIndex}; use std::mem::transmute; use region::Protection; use region::protect; use std::ptr::write_unaligned; -use std::fmt::Write; - -type RelocRef = u16; - -// Implementation of a relocation sink that just saves all the information for later -struct StandaloneRelocSink { - ebbs: Vec<(RelocRef, Ebb, CodeOffset)>, - funcs: Vec<(RelocRef, FuncRef, CodeOffset)>, - jts: Vec<(RelocRef, JumpTable, CodeOffset)>, -} - -// Contains all the metadata necessary to perform relocations -struct FunctionMetaData { - relocs: StandaloneRelocSink, - il_func: Function, -} - -impl RelocSink for StandaloneRelocSink { - fn reloc_ebb(&mut self, offset: CodeOffset, reloc: Reloc, ebb: Ebb) { - self.ebbs.push((reloc.0, ebb, offset)); - } - fn reloc_func(&mut self, offset: CodeOffset, reloc: Reloc, func: FuncRef) { - self.funcs.push((reloc.0, func, offset)); - } - fn reloc_jt(&mut self, offset: CodeOffset, reloc: Reloc, jt: JumpTable) { - self.jts.push((reloc.0, jt, offset)); - } -} - -impl StandaloneRelocSink { - fn new() -> Self { - Self { - ebbs: Vec::new(), - funcs: Vec::new(), - jts: Vec::new(), - } - } -} - -/// Structure containing the compiled code of the functions, ready to be executed. -pub struct ExecutableCode { - functions_code: Vec>, - start_index: FunctionIndex, -} +use wasmstandalone_runtime::Compilation; /// Executes a module that has been translated with the `standalone::Runtime` runtime implementation. -pub fn compile_module( - trans_result: &TranslationResult, +pub fn compile_module<'data, 'module>( isa: &TargetIsa, - runtime: &wasmstandalone_runtime::Runtime, -) -> Result { + translation: &wasmstandalone_runtime::ModuleTranslation<'data, 'module>, +) -> Result, String> { debug_assert!( - runtime.start_func.is_none() || runtime.start_func.unwrap() >= runtime.imported_funcs.len(), + translation.module.start_func.is_none() || + translation.module.start_func.unwrap() >= translation.module.imported_funcs.len(), "imported start functions not supported yet" ); - let mut functions_metatada = Vec::new(); - let mut functions_code = Vec::new(); - 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 = StandaloneRelocSink::new(); - context.emit_to_memory(code_buf.as_mut_ptr(), &mut relocsink, isa); - functions_metatada.push(FunctionMetaData { - relocs: relocsink, - il_func: context.func, - }); - functions_code.push(code_buf); - } - relocate(&functions_metatada, &mut functions_code, runtime); - // After having emmitted the code to memory, we deal with relocations - match runtime.start_func { - None => Err(String::from( - "No start function defined, aborting execution", - )), - Some(index) => { - Ok(ExecutableCode { - functions_code, - start_index: index, - }) + let (mut compilation, relocations) = translation.compile(isa)?; + + // Apply relocations, now that we have virtual addresses for everything. + relocate(&mut compilation, &relocations); + + Ok(compilation) +} + +/// Performs the relocations inside the function bytecode, provided the necessary metadata +fn relocate(compilation: &mut Compilation, relocations: &wasmstandalone_runtime::Relocations) { + // The relocations are relative to the relocation's address plus four bytes + // TODO: Support architectures other than x64, and other reloc kinds. + for (i, function_relocs) in relocations.iter().enumerate() { + for &(_reloc, func_index, offset) in function_relocs { + let target_func_address: isize = compilation.functions[func_index].as_ptr() as isize; + let body = &mut compilation.functions[i]; + unsafe { + let reloc_address: isize = body.as_mut_ptr().offset(offset as isize + 4) as isize; + let reloc_delta_i32: i32 = (target_func_address - reloc_address) as i32; + write_unaligned(reloc_address as *mut i32, reloc_delta_i32); + } } } } /// Jumps to the code region of memory and execute the start function of the module. -pub fn execute(exec: &ExecutableCode) -> Result<(), String> { - let code_buf = &exec.functions_code[exec.start_index]; +pub fn execute( + compilation: &wasmstandalone_runtime::Compilation, + _instance: &wasmstandalone_runtime::Instance, +) -> Result<(), String> { + let start_index = compilation.module.start_func.ok_or_else(|| { + String::from("No start function defined, aborting execution") + })?; + let code_buf = &compilation.functions[start_index]; match unsafe { protect( code_buf.as_ptr(), @@ -141,74 +84,3 @@ pub fn execute(exec: &ExecutableCode) -> Result<(), String> { } Ok(()) } - -/// Performs the relocations inside the function bytecode, provided the necessary metadata -fn relocate( - functions_metatada: &[FunctionMetaData], - functions_code: &mut Vec>, - runtime: &wasmstandalone_runtime::Runtime, -) { - // The relocations are relative to the relocation's address plus four bytes - for (func_index, function_in_memory) in functions_metatada.iter().enumerate() { - let FunctionMetaData { - ref relocs, - ref il_func, - } = *function_in_memory; - for &(_reloc, func_ref, offset) in &relocs.funcs { - let target_func_index = runtime.func_indices[func_ref] - runtime.imported_funcs.len(); - let target_func_address: isize = functions_code[target_func_index].as_ptr() as isize; - unsafe { - let reloc_address: isize = functions_code[func_index].as_mut_ptr().offset( - offset as isize + - 4, - ) as isize; - let reloc_delta_i32: i32 = (target_func_address - reloc_address) as i32; - write_unaligned(reloc_address as *mut i32, reloc_delta_i32); - } - } - for &(_reloc, ebb, offset) in &relocs.ebbs { - unsafe { - let reloc_address: isize = functions_code[func_index].as_mut_ptr().offset( - offset as isize + - 4, - ) as isize; - let target_ebb_address: isize = functions_code[func_index].as_ptr().offset( - il_func.offsets[ebb] as - isize, - ) as isize; - let reloc_delta_i32: i32 = (target_ebb_address - reloc_address) as i32; - write_unaligned(reloc_address as *mut i32, reloc_delta_i32); - } - } - assert!( - relocs.jts.is_empty(), - "TODO: deal with jumptable relocations" - ); - } -} - -/// Pretty-print a verifier error. -pub 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. -pub fn pretty_error(func: &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/obj/src/emit_module.rs b/lib/obj/src/emit_module.rs index 6bc004e6b3..2c115c8d84 100644 --- a/lib/obj/src/emit_module.rs +++ b/lib/obj/src/emit_module.rs @@ -1,59 +1,18 @@ -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::fmt::Write; use faerie::Artifact; use wasmstandalone_runtime; -type RelocRef = u16; - -// Implementation of a relocation sink that just saves all the information for later -struct FaerieRelocSink { - ebbs: Vec<(RelocRef, Ebb, CodeOffset)>, - funcs: Vec<(RelocRef, FuncRef, CodeOffset)>, - jts: Vec<(RelocRef, JumpTable, CodeOffset)>, -} - -impl RelocSink for FaerieRelocSink { - fn reloc_ebb(&mut self, offset: CodeOffset, reloc: Reloc, ebb: Ebb) { - self.ebbs.push((reloc.0, ebb, offset)); - } - fn reloc_func(&mut self, offset: CodeOffset, reloc: Reloc, func: FuncRef) { - self.funcs.push((reloc.0, func, offset)); - } - fn reloc_jt(&mut self, offset: CodeOffset, reloc: Reloc, jt: JumpTable) { - self.jts.push((reloc.0, jt, offset)); - } -} - -impl FaerieRelocSink { - fn new() -> FaerieRelocSink { - FaerieRelocSink { - ebbs: Vec::new(), - funcs: Vec::new(), - jts: Vec::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, +pub fn emit_module<'module>( obj: &mut Artifact, - isa: &TargetIsa, - runtime: &wasmstandalone_runtime::Runtime, + compilation: &wasmstandalone_runtime::Compilation<'module>, + relocations: &wasmstandalone_runtime::Relocations, ) -> Result<(), String> { debug_assert!( - runtime.start_func.is_none() || runtime.start_func.unwrap() >= runtime.imported_funcs.len(), + compilation.module.start_func.is_none() || + compilation.module.start_func.unwrap() >= compilation.module.imported_funcs.len(), "imported start functions not supported yet" ); @@ -62,55 +21,15 @@ pub fn emit_module( "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); + for (i, function_relocs) in relocations.iter().enumerate() { + assert!(function_relocs.is_empty(), "relocations not supported yet"); + let body = &compilation.functions[i]; - // 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" + obj.add_code( + wasmstandalone_runtime::get_func_name(compilation.module.imported_funcs.len() + i), + body.clone(), ); - - // 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/runtime/Cargo.toml b/lib/runtime/Cargo.toml index 174ed8d893..d7c7b7109d 100644 --- a/lib/runtime/Cargo.toml +++ b/lib/runtime/Cargo.toml @@ -10,3 +10,4 @@ license = "Apache-2.0" [dependencies] cretonne = { git = "https://github.com/stoklund/cretonne.git" } cretonne-wasm = { git = "https://github.com/stoklund/cretonne.git" } +wasmparser = "0.13.0" diff --git a/lib/runtime/src/compilation.rs b/lib/runtime/src/compilation.rs new file mode 100644 index 0000000000..f0e0c19a59 --- /dev/null +++ b/lib/runtime/src/compilation.rs @@ -0,0 +1,21 @@ +//! A `Compilation` contains the compiled function bodies for a WebAssembly +//! module. + +use module::Module; + +/// An Instance of a WebAssemby module. +#[derive(Debug)] +pub struct Compilation<'module> { + /// The module this `Instance` is instantiated from. + pub module: &'module Module, + + /// Compiled machine code for the function bodies. + pub functions: Vec>, +} + +impl<'module> Compilation<'module> { + /// Allocates the runtime data structures with the given flags. + pub fn new(module: &'module Module, functions: Vec>) -> Self { + Self { module, functions } + } +} diff --git a/lib/runtime/src/instance.rs b/lib/runtime/src/instance.rs new file mode 100644 index 0000000000..ad0345c2c7 --- /dev/null +++ b/lib/runtime/src/instance.rs @@ -0,0 +1,90 @@ +//! An `Instance` contains all the runtime state used by execution of a wasm +//! module. + +use cretonne::ir; +use cton_wasm::GlobalIndex; +use module::Module; + +const PAGE_SIZE: usize = 65536; + +/// An Instance of a WebAssemby module. +#[derive(Debug)] +pub struct Instance { + /// WebAssembly table data. + pub tables: Vec>, + + /// WebAssembly linear memory data. + pub memories: Vec>, + + /// WebAssembly global variable data. + pub globals: Vec, +} + +impl Instance { + /// Create a new `Instance`. + pub fn new(module: &Module) -> Self { + let mut result = Self { + tables: Vec::new(), + memories: Vec::new(), + globals: Vec::new(), + }; + result.instantiate_tables(module); + result.instantiate_memories(module); + result.instantiate_globals(module); + result + } + + /// Allocate memory in `self` for just the tables of the current module, + /// without any initializers applied yet. + fn instantiate_tables(&mut self, module: &Module) { + debug_assert!(self.tables.is_empty()); + self.tables.reserve(module.tables.len()); + for table in &module.tables { + let len = table.size; + let mut v = Vec::with_capacity(len); + v.resize(len, 0); + self.tables.push(v); + } + } + + /// Allocate memory in `instance` for just the memories of the current module, + /// without any initializers applied yet. + fn instantiate_memories(&mut self, module: &Module) { + debug_assert!(self.memories.is_empty()); + // Allocate the underlying memory and initialize it to all zeros. + self.memories.reserve(module.memories.len()); + for memory in &module.memories { + let len = memory.pages_count * PAGE_SIZE; + let mut v = Vec::with_capacity(len); + v.resize(len, 0); + self.memories.push(v); + } + } + + /// Allocate memory in `instance` for just the globals of the current module, + /// without any initializers applied yet. + fn instantiate_globals(&mut self, module: &Module) { + debug_assert!(self.globals.is_empty()); + // Allocate the underlying memory and initialize it to all zeros. + let globals_data_size = module.globals.len() * 8; + self.globals.resize(globals_data_size, 0); + } + + /// Returns a slice of the contents of allocated linear memory. + pub fn inspect_memory(&self, memory_index: usize, address: usize, len: usize) -> &[u8] { + &self.memories.get(memory_index).expect( + format!( + "no memory for index {}", + memory_index + ).as_str(), + ) + [address..address + len] + } + + /// Shows the value of a global variable. + pub fn inspect_global(&self, global_index: GlobalIndex, ty: ir::Type) -> &[u8] { + let offset = global_index * 8; + let len = ty.bytes() as usize; + &self.globals[offset..offset + len] + } +} diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index 1436297321..472e0b403b 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -7,64 +7,35 @@ extern crate cretonne; extern crate cton_wasm; +extern crate wasmparser; -use cton_wasm::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, Global, GlobalInit, Table, - Memory, WasmRuntime, FuncEnvironment, GlobalValue, SignatureIndex}; +pub mod module; +pub mod compilation; +pub mod instance; + +pub use module::Module; +pub use compilation::Compilation; +pub use instance::Instance; + +use cton_wasm::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, Global, Table, Memory, + GlobalValue, SignatureIndex, FuncTranslator}; use cretonne::ir::{InstBuilder, FuncRef, ExtFuncData, FunctionName, Signature, ArgumentType, CallConv, ArgumentPurpose, ArgumentLoc, ArgumentExtension, Function}; use cretonne::ir::types::*; use cretonne::ir::immediates::Offset32; use cretonne::cursor::FuncCursor; -use cretonne::packed_option::PackedOption; use cretonne::ir; +use cretonne::isa; use cretonne::settings; -use cretonne::entity::EntityMap; -use std::mem::transmute; -use std::ptr::copy_nonoverlapping; -use std::ptr::write; -use std::collections::HashMap; +use cretonne::binemit; +use std::str::{FromStr, from_utf8}; +use std::error::Error; -/// Runtime state of a WebAssembly table element. -#[derive(Clone, Debug)] -pub enum TableElement { - /// A element that, if called, produces a trap. - Trap(), - /// A function. - Function(FunctionIndex), +/// Compute a `ir::FunctionName` for a given wasm function index. +pub fn get_func_name(func_index: FunctionIndex) -> cretonne::ir::FunctionName { + ir::FunctionName::new(format!("wasm_0x{:x}", func_index)) } -/// Information about a WebAssembly global variable. -pub struct GlobalInfo { - global: Global, - offset: usize, -} - -/// Runtime state of a WebAssembly global variable. -pub struct GlobalsData { - data: Vec, - info: Vec, -} - -/// 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, -} - -/// 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; - /// An entity to export. pub enum Export { /// Function export. @@ -77,86 +48,144 @@ pub enum Export { Global(GlobalIndex), } -/// Object containing the standalone runtime information. To be passed after creation as argument -/// to `cton_wasm::translatemodule`. -pub struct Runtime { - /// Compilation setting flags. - flags: settings::Flags, +type RelocRef = u16; - /// Unprocessed signatures exactly as provided by `declare_signature()`. - signatures: Vec, - - /// Names of imported functions. - pub imported_funcs: Vec<(String, String)>, - - /// Types of functions, imported and local. - functions: Vec, - - /// WebAssembly tables. - pub tables: Vec, - - /// WebAssembly linear memories. - pub memories: Vec, - - /// WebAssembly global variables. - pub globals: GlobalsData, - - /// Exported entities. - pub exports: HashMap, - - instantiated: bool, - - has_current_memory: Option, - has_grow_memory: Option, - - /// Mapping from cretonne FuncRef to wasm FunctionIndex. - pub func_indices: EntityMap, - - the_heap: PackedOption, - - /// The module "start" function, if present. - pub start_func: Option, +// Implementation of a relocation sink that just saves all the information for later +struct RelocSink<'func> { + func: &'func ir::Function, + pub func_relocs: Vec<(RelocRef, FunctionIndex, binemit::CodeOffset)>, } -impl Runtime { - /// Allocates the runtime data structures with default flags. - pub fn default() -> Self { - Self::with_flags(settings::Flags::new(&settings::builder())) +impl<'func> binemit::RelocSink for RelocSink<'func> { + fn reloc_ebb(&mut self, _offset: binemit::CodeOffset, _reloc: binemit::Reloc, _ebb: ir::Ebb) { + // This should use the `offsets` field of `ir::Function`. + panic!("ebb headers not yet implemented"); } + fn reloc_func(&mut self, offset: binemit::CodeOffset, reloc: binemit::Reloc, func: FuncRef) { + let name_bytes: &[u8] = self.func.dfg.ext_funcs[func].name.as_ref(); + let name = from_utf8(name_bytes).unwrap(); + // See `get_func_name`; names are encoded as `wasm_0x...`, so grab the + // `0x...` part and convert it back to an integer to get the index. + let func_index = FunctionIndex::from_str(&name[5..]).unwrap(); + self.func_relocs.push((reloc.0, func_index, offset)); + } + fn reloc_jt( + &mut self, + _offset: binemit::CodeOffset, + _reloc: binemit::Reloc, + _jt: ir::JumpTable, + ) { + panic!("jump tables not yet implemented"); + } +} - /// Allocates the runtime data structures with the given flags. - pub fn with_flags(flags: settings::Flags) -> Self { +impl<'func> RelocSink<'func> { + fn new(func: &'func Function) -> RelocSink { + RelocSink { + func, + func_relocs: Vec::new(), + } + } +} + +/// References to the input wasm data buffer to be decoded and processed later. +/// separately from the main module translation. +pub struct LazyContents<'data> { + /// References to the function bodies. + pub function_body_inputs: Vec<&'data [u8]>, + + /// References to the data initializers. + pub data_initializers: Vec<(MemoryIndex, Option, usize, &'data [u8])>, +} + +impl<'data> LazyContents<'data> { + fn new() -> Self { + Self { + function_body_inputs: Vec::new(), + data_initializers: Vec::new(), + } + } +} + +/// Object containing the standalone runtime information. To be passed after creation as argument +/// to `cton_wasm::translatemodule`. +pub struct ModuleEnvironment<'data, 'module> { + /// Compilation setting flags. + pub flags: &'module settings::Flags, + + /// Module information. + pub module: &'module mut Module, + + /// References to information to be decoded later. + pub lazy: LazyContents<'data>, +} + +impl<'data, 'module> ModuleEnvironment<'data, 'module> { + /// Allocates the runtime data structures with the given isa. + pub fn new(flags: &'module settings::Flags, module: &'module mut Module) -> Self { Self { flags, - signatures: Vec::new(), - imported_funcs: Vec::new(), - functions: Vec::new(), - tables: Vec::new(), - memories: Vec::new(), - globals: GlobalsData { - data: Vec::new(), - info: Vec::new(), - }, - exports: HashMap::new(), - instantiated: false, - has_current_memory: None, - has_grow_memory: None, - func_indices: EntityMap::new(), - the_heap: PackedOption::default(), - start_func: None, + module, + lazy: LazyContents::new(), } } - /// Return the offset from the VmCtx pointer where global `index` is allocated. - fn global_offset(index: GlobalIndex) -> usize { - // Add one for the hidden heap base global. - (index as usize + 1) * 8 + fn func_env(&self) -> FuncEnvironment { + FuncEnvironment::new(&self.flags, &self.module) } - /// Return the size of the VmCtx area needed to hold all currently declared globals. - fn globals_data_size(&self) -> usize { - // Add one for the hidden heap base global. - (self.globals.info.len() + 1) * 8 + fn native_pointer(&self) -> ir::Type { + use cton_wasm::FuncEnvironment; + self.func_env().native_pointer() + } + + /// Declare that translation of the module is complete. This consumes the + /// `ModuleEnvironment` with its mutable reference to the `Module` and + /// produces a `ModuleTranslation` with an immutable reference to the + /// `Module`. + pub fn finish_translation(self) -> ModuleTranslation<'data, 'module> { + ModuleTranslation { + flags: self.flags, + module: self.module, + lazy: self.lazy, + } + } +} + +/// The FuncEnvironment implementation for use by the `ModuleEnvironment`. +pub struct FuncEnvironment<'module_environment> { + /// Compilation setting flags. + settings_flags: &'module_environment settings::Flags, + + /// The module-level environment which this function-level environment belongs to. + pub module: &'module_environment Module, + + /// The Cretonne global holding the base address of the memories vector. + pub memories_base: Option, + + /// The Cretonne global holding the base address of the globals vector. + pub globals_base: Option, + + /// The external function declaration for implementing wasm's `current_memory`. + pub current_memory_extfunc: Option, + + /// The external function declaration for implementing wasm's `grow_memory`. + pub grow_memory_extfunc: Option, +} + +impl<'module_environment> FuncEnvironment<'module_environment> { + fn new( + flags: &'module_environment settings::Flags, + module: &'module_environment Module, + ) -> Self { + Self { + settings_flags: flags, + module, + memories_base: None, + globals_base: None, + current_memory_extfunc: None, + grow_memory_extfunc: None, + } } /// Transform the call argument list in preparation for making a call. @@ -166,56 +195,75 @@ impl Runtime { real_call_args.push(func.special_arg(ArgumentPurpose::VMContext).unwrap()); real_call_args } + + fn ptr_size(&self) -> usize { + if self.settings_flags.is_64bit() { 8 } else { 4 } + } } -impl FuncEnvironment for Runtime { +impl<'module_environment> cton_wasm::FuncEnvironment for FuncEnvironment<'module_environment> { fn flags(&self) -> &settings::Flags { - &self.flags + &self.settings_flags } fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalValue { - let offset = Self::global_offset(index); + let ptr_size = self.ptr_size(); + let globals_base = self.globals_base.unwrap_or_else(|| { + let offset = 0 * ptr_size; + let offset32 = offset as i32; + debug_assert_eq!(offset32 as usize, offset); + let new_base = func.create_global_var( + ir::GlobalVarData::VmCtx { offset: Offset32::new(offset32) }, + ); + self.globals_base = Some(new_base); + new_base + }); + let offset = index as usize * 8; let offset32 = offset as i32; debug_assert_eq!(offset32 as usize, offset); - let gv = - func.create_global_var(ir::GlobalVarData::VmCtx { offset: Offset32::new(offset32) }); + let gv = func.create_global_var(ir::GlobalVarData::Deref { + base: globals_base, + offset: Offset32::new(offset32), + }); GlobalValue::Memory { gv, - ty: self.globals.info[index].global.ty, + ty: self.module.globals[index].ty, } } - fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> ir::Heap { - debug_assert!(self.the_heap.is_none(), "multiple heaps not supported yet"); - - let heap_base = - func.create_global_var(ir::GlobalVarData::VmCtx { offset: Offset32::new(0) }); - - let heap = func.create_heap(ir::HeapData { + fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap { + let ptr_size = self.ptr_size(); + let memories_base = self.memories_base.unwrap_or_else(|| { + let new_base = func.create_global_var(ir::GlobalVarData::VmCtx { + offset: Offset32::new(ptr_size as i32), + }); + self.globals_base = Some(new_base); + new_base + }); + let offset = index as usize * ptr_size; + let offset32 = offset as i32; + debug_assert_eq!(offset32 as usize, offset); + let heap_base = func.create_global_var(ir::GlobalVarData::Deref { + base: memories_base, + offset: Offset32::new(offset32), + }); + func.create_heap(ir::HeapData { base: ir::HeapBase::GlobalVar(heap_base), min_size: 0.into(), guard_size: 0x8000_0000.into(), style: ir::HeapStyle::Static { bound: 0x1_0000_0000.into() }, - }); - - self.the_heap = PackedOption::from(heap); - - heap + }) } fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef { - func.import_signature(self.signatures[index].clone()) + func.import_signature(self.module.signatures[index].clone()) } fn make_direct_func(&mut self, func: &mut ir::Function, index: FunctionIndex) -> ir::FuncRef { - let sigidx = self.functions[index]; - let signature = func.import_signature(self.signatures[sigidx].clone()); - let name = self.get_func_name(index); - let func_ref = func.import_function(ir::ExtFuncData { name, signature }); - - self.func_indices[func_ref] = index; - - func_ref + let sigidx = self.module.functions[index]; + let signature = func.import_signature(self.module.signatures[sigidx].clone()); + let name = get_func_name(index); + func.import_function(ir::ExtFuncData { name, signature }) } fn translate_call_indirect( @@ -228,7 +276,7 @@ impl FuncEnvironment for Runtime { call_args: &[ir::Value], ) -> ir::Inst { debug_assert_eq!(table_index, 0, "non-default tables not supported yet"); - let real_call_args = Self::get_real_call_args(pos.func, call_args); + let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args); pos.ins().call_indirect(sig_ref, callee, &real_call_args) } @@ -239,7 +287,7 @@ impl FuncEnvironment for Runtime { callee: ir::FuncRef, call_args: &[ir::Value], ) -> ir::Inst { - let real_call_args = Self::get_real_call_args(pos.func, call_args); + let real_call_args = FuncEnvironment::get_real_call_args(pos.func, call_args); pos.ins().call(callee, &real_call_args) } @@ -247,17 +295,11 @@ impl FuncEnvironment for Runtime { &mut self, mut pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + _heap: ir::Heap, val: ir::Value, ) -> ir::Value { - debug_assert!(self.instantiated); debug_assert_eq!(index, 0, "non-default memories not supported yet"); - debug_assert_eq!( - heap, - self.the_heap.unwrap(), - "multiple heaps not supported yet" - ); - let grow_mem_func = self.has_grow_memory.unwrap_or_else(|| { + let grow_mem_func = self.grow_memory_extfunc.unwrap_or_else(|| { let sig_ref = pos.func.import_signature(Signature { call_conv: CallConv::Native, argument_bytes: None, @@ -269,7 +311,7 @@ impl FuncEnvironment for Runtime { signature: sig_ref, }) }); - self.has_grow_memory = Some(grow_mem_func); + self.grow_memory_extfunc = Some(grow_mem_func); let call_inst = pos.ins().call(grow_mem_func, &[val]); *pos.func.dfg.inst_results(call_inst).first().unwrap() } @@ -278,16 +320,10 @@ impl FuncEnvironment for Runtime { &mut self, mut pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + _heap: ir::Heap, ) -> ir::Value { - debug_assert!(self.instantiated); debug_assert_eq!(index, 0, "non-default memories not supported yet"); - debug_assert_eq!( - heap, - self.the_heap.unwrap(), - "multiple heaps not supported yet" - ); - let cur_mem_func = self.has_current_memory.unwrap_or_else(|| { + let cur_mem_func = self.current_memory_extfunc.unwrap_or_else(|| { let sig_ref = pos.func.import_signature(Signature { call_conv: CallConv::Native, argument_bytes: None, @@ -299,7 +335,7 @@ impl FuncEnvironment for Runtime { signature: sig_ref, }) }); - self.has_current_memory = Some(cur_mem_func); + self.current_memory_extfunc = Some(cur_mem_func); let call_inst = pos.ins().call(cur_mem_func, &[]); *pos.func.dfg.inst_results(call_inst).first().unwrap() } @@ -309,9 +345,9 @@ impl FuncEnvironment for Runtime { /// `cton_wasm::translatemodule` because it /// tells how to translate runtime-dependent wasm instructions. These functions should not be /// called by the user. -impl WasmRuntime for Runtime { +impl<'data, 'module> cton_wasm::ModuleEnvironment<'data> for ModuleEnvironment<'data, 'module> { fn get_func_name(&self, func_index: FunctionIndex) -> cretonne::ir::FunctionName { - ir::FunctionName::new(format!("wasm_0x{:x}", func_index)) + get_func_name(func_index) } fn declare_signature(&mut self, sig: &ir::Signature) { @@ -323,63 +359,49 @@ impl WasmRuntime for Runtime { location: ArgumentLoc::Unassigned, }); // TODO: Deduplicate signatures. - self.signatures.push(sig); + self.module.signatures.push(sig); } fn get_signature(&self, sig_index: SignatureIndex) -> &ir::Signature { - &self.signatures[sig_index] + &self.module.signatures[sig_index] } fn declare_func_import(&mut self, sig_index: SignatureIndex, module: &str, field: &str) { debug_assert_eq!( - self.functions.len(), - self.imported_funcs.len(), + self.module.functions.len(), + self.module.imported_funcs.len(), "Imported functions must be declared first" ); - self.functions.push(sig_index); + self.module.functions.push(sig_index); - self.imported_funcs.push(( + self.module.imported_funcs.push(( String::from(module), String::from(field), )); } fn get_num_func_imports(&self) -> usize { - self.imported_funcs.len() + self.module.imported_funcs.len() } fn declare_func_type(&mut self, sig_index: SignatureIndex) { - self.functions.push(sig_index); + self.module.functions.push(sig_index); } fn get_func_type(&self, func_index: FunctionIndex) -> usize { - self.functions[func_index] + self.module.functions[func_index] } fn declare_global(&mut self, global: Global) { - debug_assert!(!self.instantiated); - let index = self.globals.info.len() as GlobalIndex; - self.globals.info.push(GlobalInfo { - global: global, - offset: Self::global_offset(index), - }); + self.module.globals.push(global); } fn get_global(&self, global_index: GlobalIndex) -> &cton_wasm::Global { - &self.globals.info[global_index].global + &self.module.globals[global_index] } fn declare_table(&mut self, table: Table) { - debug_assert!(!self.instantiated); - let mut elements_vec = Vec::with_capacity(table.size); - elements_vec.resize(table.size, TableElement::Trap()); - let mut addresses_vec = Vec::with_capacity(table.size); - addresses_vec.resize(table.size, 0); - self.tables.push(TableData { - info: table, - data: addresses_vec, - elements: elements_vec, - }); + self.module.tables.push(table); } fn declare_table_elements( @@ -387,23 +409,19 @@ impl WasmRuntime for Runtime { table_index: TableIndex, base: Option, offset: usize, - elements: &[FunctionIndex], + elements: Vec, ) { debug_assert!(base.is_none(), "global-value offsets not supported yet"); - debug_assert!(!self.instantiated); - for (i, elt) in elements.iter().enumerate() { - self.tables[table_index].elements[offset + i] = TableElement::Function(*elt); - } + self.module.table_elements.push(module::TableElements { + table_index, + base, + offset, + elements, + }); } fn declare_memory(&mut self, memory: Memory) { - debug_assert!(!self.instantiated); - let mut memory_vec = Vec::with_capacity(memory.pages_count * PAGE_SIZE); - memory_vec.resize(memory.pages_count * PAGE_SIZE, 0); - self.memories.push(MemoryData { - info: memory, - data: memory_vec, - }); + self.module.memories.push(memory); } fn declare_data_initialization( @@ -411,133 +429,108 @@ impl WasmRuntime for Runtime { memory_index: MemoryIndex, base: Option, offset: usize, - data: &[u8], + data: &'data [u8], ) { debug_assert!(base.is_none(), "global-value offsets not supported yet"); - debug_assert!( - offset + data.len() <= self.memories[memory_index].info.pages_count * PAGE_SIZE, - "initialization data out of bounds" - ); - self.memories[memory_index].data[offset..offset + data.len()].copy_from_slice(data); + self.lazy.data_initializers.push(( + memory_index, + base, + offset, + data, + )); } fn declare_func_export(&mut self, func_index: FunctionIndex, name: &str) { - self.exports.insert( + self.module.exports.insert( String::from(name), - Export::Function(func_index), + module::Export::Function(func_index), ); } fn declare_table_export(&mut self, table_index: TableIndex, name: &str) { - self.exports.insert( + self.module.exports.insert( String::from(name), - Export::Table(table_index), + module::Export::Table(table_index), ); } fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &str) { - self.exports.insert( + self.module.exports.insert( String::from(name), - Export::Memory(memory_index), + module::Export::Memory(memory_index), ); } fn declare_global_export(&mut self, global_index: GlobalIndex, name: &str) { - self.exports.insert( + self.module.exports.insert( String::from(name), - Export::Global(global_index), + module::Export::Global(global_index), ); } fn declare_start_func(&mut self, func_index: FunctionIndex) { - debug_assert!(self.start_func.is_none()); - self.start_func = Some(func_index); + debug_assert!(self.module.start_func.is_none()); + self.module.start_func = Some(func_index); } - fn begin_translation(&mut self) { - debug_assert!(!self.instantiated); - self.instantiated = true; - // At instantiation, we allocate memory for the globals, the memories and the tables - // First the globals - let globals_data_size = self.globals_data_size(); - self.globals.data.resize(globals_data_size, 0); - for globalinfo in &self.globals.info { - match globalinfo.global.initializer { - GlobalInit::I32Const(val) => unsafe { - write( - self.globals.data.as_mut_ptr().offset( - globalinfo.offset as isize, - ) as *mut i32, - val, - ) - }, - GlobalInit::I64Const(val) => unsafe { - write( - self.globals.data.as_mut_ptr().offset( - globalinfo.offset as isize, - ) as *mut i64, - val, - ) - }, - GlobalInit::F32Const(val) => unsafe { - write( - self.globals.data.as_mut_ptr().offset( - globalinfo.offset as isize, - ) as *mut f32, - transmute(val), - ) - }, - GlobalInit::F64Const(val) => unsafe { - write( - self.globals.data.as_mut_ptr().offset( - globalinfo.offset as isize, - ) as *mut f64, - transmute(val), - ) - }, - GlobalInit::Import() => { - // We don't initialize, this is inter-module linking - // TODO: support inter-module imports - } - GlobalInit::GlobalRef(index) => { - let ref_offset = self.globals.info[index].offset; - let size = globalinfo.global.ty.bytes(); - unsafe { - let dst = self.globals.data.as_mut_ptr().offset( - globalinfo.offset as isize, - ); - let src = self.globals.data.as_ptr().offset(ref_offset as isize); - copy_nonoverlapping(src, dst, size as usize) - } - } - } - } + fn define_function_body(&mut self, body_bytes: &'data [u8]) -> Result<(), String> { + self.lazy.function_body_inputs.push(body_bytes); + Ok(()) } +} - fn next_function(&mut self) { - self.has_current_memory = None; - self.has_grow_memory = None; - self.func_indices.clear(); - self.the_heap = PackedOption::default(); - } +/// Relocations to apply to function bodies. +pub type Relocations = Vec>; + +/// The result of translating via `ModuleEnvironment`. +pub struct ModuleTranslation<'data, 'module> { + /// Compilation setting flags. + pub flags: &'module settings::Flags, + + /// Module information. + pub module: &'module Module, + + /// Pointers into the raw data buffer. + pub lazy: LazyContents<'data>, } /// Convenience functions for the user to be called after execution for debug purposes. -impl Runtime { - /// Returns a slice of the contents of allocated linear memory. - pub fn inspect_memory(&self, memory_index: usize, address: usize, len: usize) -> &[u8] { - &self.memories - .get(memory_index) - .expect(format!("no memory for index {}", memory_index).as_str()) - .data - [address..address + len] +impl<'data, 'module> ModuleTranslation<'data, 'module> { + fn func_env(&self) -> FuncEnvironment { + FuncEnvironment::new(&self.flags, &self.module) } - /// Shows the value of a global variable. - pub fn inspect_global(&self, global_index: usize) -> &[u8] { - let (offset, len) = ( - self.globals.info[global_index].offset, - self.globals.info[global_index].global.ty.bytes() as usize, - ); - &self.globals.data[offset..offset + len] + + /// Compile the module, producing a compilation result with associated + /// relocations. + pub fn compile( + &self, + isa: &isa::TargetIsa, + ) -> Result<(Compilation<'module>, Relocations), String> { + let mut functions = Vec::new(); + let mut relocations = Vec::new(); + for input in &self.lazy.function_body_inputs { + let mut trans = FuncTranslator::new(); + let mut context = cretonne::Context::new(); + let reader = wasmparser::BinaryReader::new(input); + + { + let mut func_environ = self.func_env(); + + trans + .translate_from_reader(reader, &mut context.func, &mut func_environ) + .map_err(|e| String::from(e.description()))?; + } + + let code_size = context.compile(isa).map_err( + |e| String::from(e.description()), + )? as usize; + let mut code_buf: Vec = Vec::with_capacity(code_size as usize); + let mut reloc_sink = RelocSink::new(&context.func); + code_buf.resize(code_size, 0); + context.emit_to_memory(code_buf.as_mut_ptr(), &mut reloc_sink, isa); + functions.push(code_buf); + relocations.push(reloc_sink.func_relocs); + } + Ok((Compilation::new(self.module, functions), relocations)) } } diff --git a/lib/runtime/src/module.rs b/lib/runtime/src/module.rs new file mode 100644 index 0000000000..291629b2fe --- /dev/null +++ b/lib/runtime/src/module.rs @@ -0,0 +1,91 @@ +//! A `Module` contains all the relevant information translated from a +//! WebAssembly module. + +use cton_wasm::{FunctionIndex, GlobalIndex, TableIndex, MemoryIndex, Global, Table, Memory, + SignatureIndex}; +use cretonne::ir; +use std::collections::HashMap; + +/// Possible values for a WebAssembly table element. +#[derive(Clone, Debug)] +pub enum TableElement { + /// A element that, if called, produces a trap. + Trap(), + /// A function. + Function(FunctionIndex), +} + +/// A WebAssembly table initializer. +#[derive(Clone, Debug)] +pub struct TableElements { + /// The index of a table to initialize. + pub table_index: TableIndex, + /// Optionally, a global variable giving a base index. + pub base: Option, + /// The offset to add to the base. + pub offset: usize, + /// The values to write into the table elements. + pub elements: Vec, +} + +/// An entity to export. +#[derive(Clone, Debug)] +pub enum Export { + /// Function export. + Function(FunctionIndex), + /// Table export. + Table(TableIndex), + /// Memory export. + Memory(MemoryIndex), + /// Global export. + Global(GlobalIndex), +} + +/// A translated WebAssembly module, excluding the function bodies and +/// memory initializers. +#[derive(Debug)] +pub struct Module { + /// Unprocessed signatures exactly as provided by `declare_signature()`. + pub signatures: Vec, + + /// Names of imported functions. + pub imported_funcs: Vec<(String, String)>, + + /// Types of functions, imported and local. + pub functions: Vec, + + /// WebAssembly tables. + pub tables: Vec, + + /// WebAssembly linear memories. + pub memories: Vec, + + /// WebAssembly global variables. + pub globals: Vec, + + /// Exported entities. + pub exports: HashMap, + + /// The module "start" function, if present. + pub start_func: Option, + + /// WebAssembly table initializers. + pub table_elements: Vec, +} + +impl Module { + /// Allocates the module data structures. + pub fn new() -> Self { + Self { + signatures: Vec::new(), + imported_funcs: Vec::new(), + functions: Vec::new(), + tables: Vec::new(), + memories: Vec::new(), + globals: Vec::new(), + exports: HashMap::new(), + start_func: None, + table_elements: Vec::new(), + } + } +} diff --git a/src/main.rs b/src/main.rs index d2c71b7f57..194dbba9fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,29 +9,17 @@ extern crate cton_wasm; extern crate cton_native; extern crate wasmstandalone_runtime; extern crate wasmstandalone_execute; -extern crate wasmparser; extern crate cretonne; -extern crate wasmtext; extern crate docopt; #[macro_use] extern crate serde_derive; -extern crate term; extern crate tempdir; -use cton_wasm::{translate_module, TranslationResult}; +use cton_wasm::translate_module; use wasmstandalone_execute::{compile_module, execute}; +use wasmstandalone_runtime::{Instance, Module, ModuleEnvironment}; use std::path::PathBuf; -use wasmparser::{Parser, ParserState, WasmDecoder, SectionCode}; -use wasmtext::Writer; -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 cretonne::settings; use std::fs::File; use std::error::Error; @@ -44,39 +32,17 @@ use std::process::{exit, Command}; use tempdir::TempDir; use cretonne::settings::Configurable; -macro_rules! vprintln { - ($x: expr, $($tts:tt)*) => { - if $x { - println!($($tts)*); - } - } -} - -macro_rules! vprint { - ($x: expr, $($tts:tt)*) => { - if $x { - print!($($tts)*); - } - } -} - const USAGE: &str = " Wasm to Cretonne IL translation utility. Takes a binary WebAssembly module and returns its functions in Cretonne IL format. -The translation is dependent on the runtime chosen. -The default is a dummy runtime that produces placeholder values. +The translation is dependent on the environment chosen. Usage: - wasmstandalone [-vcop] ... - wasmstandalone -e [-mvcop] ... + wasmstandalone [-mop] ... wasmstandalone --help | --version Options: - -v, --verbose displays info on the different steps - -p, --print displays the module and translated functions - -c, --check checks the corectness of the translated functions -o, --optimize runs optimization passes on the translated functions - -e, --execute enable the standalone runtime and executes the start function of the module -m, --memory interactive memory inspector after execution -h, --help print this help message --version print the Cretonne version @@ -85,12 +51,8 @@ Options: #[derive(Deserialize, Debug, Clone)] struct Args { arg_file: Vec, - flag_verbose: bool, - flag_execute: bool, flag_memory: bool, - flag_check: bool, flag_optimize: bool, - flag_print: bool, } fn read_to_end(path: PathBuf) -> Result, io::Error> { @@ -100,7 +62,6 @@ fn read_to_end(path: PathBuf) -> Result, io::Error> { Ok(buf) } - fn main() { let args: Args = Docopt::new(USAGE) .and_then(|d| { @@ -109,7 +70,6 @@ fn main() { .deserialize() }) .unwrap_or_else(|e| e.exit()); - let mut terminal = term::stdout().unwrap(); let (mut flag_builder, isa_builder) = cton_native::builders().unwrap_or_else(|_| { panic!("host machine is not a supported target"); }); @@ -119,32 +79,26 @@ fn main() { flag_builder.enable("enable_verifier").unwrap(); } + // Enable optimization if requested. + if args.flag_optimize { + flag_builder.set("opt_level", "best").unwrap(); + } + let isa = isa_builder.finish(settings::Flags::new(&flag_builder)); for filename in &args.arg_file { let path = Path::new(&filename); - let name = path.as_os_str().to_string_lossy(); - match handle_module(&args, path.to_path_buf(), &name, &*isa) { + match handle_module(&args, path.to_path_buf(), &*isa) { Ok(()) => {} Err(message) => { - terminal.fg(term::color::RED).unwrap(); - println!("error"); - terminal.reset().unwrap(); - println!("{}", message); + let name = path.as_os_str().to_string_lossy(); + println!("error while processing {}: {}", name, message); exit(1); } } } } -fn handle_module(args: &Args, path: PathBuf, name: &str, isa: &TargetIsa) -> Result<(), String> { - let mut terminal = term::stdout().unwrap(); - terminal.fg(term::color::YELLOW).unwrap(); - vprint!(args.flag_verbose, "Handling: "); - terminal.reset().unwrap(); - vprintln!(args.flag_verbose, "\"{}\"", name); - terminal.fg(term::color::MAGENTA).unwrap(); - vprint!(args.flag_verbose, "Translating..."); - terminal.reset().unwrap(); +fn handle_module(args: &Args, path: PathBuf, isa: &TargetIsa) -> Result<(), String> { let mut data = read_to_end(path.clone()).map_err(|err| { String::from(err.description()) })?; @@ -166,248 +120,53 @@ fn handle_module(args: &Args, path: PathBuf, name: &str, isa: &TargetIsa) -> Res |err| String::from(err.description()), )?; } - let mut runtime = wasmstandalone_runtime::Runtime::with_flags(isa.flags().clone()); - let translation = { - match translate_module(&data, &mut runtime) { - Ok(x) => x, - Err(string) => { - return Err(string); - } + let mut module = Module::new(); + let mut environ = ModuleEnvironment::new(isa.flags(), &mut module); + translate_module(&data, &mut environ)?; + let translation = environ.finish_translation(); + let instance = match compile_module(isa, &translation) { + Ok(compilation) => { + let mut instance = Instance::new(compilation.module); + execute(&compilation, &mut instance)?; + instance + } + Err(s) => { + return Err(s); } }; - terminal.fg(term::color::GREEN).unwrap(); - vprintln!(args.flag_verbose, " ok"); - terminal.reset().unwrap(); - if args.flag_check { - terminal.fg(term::color::MAGENTA).unwrap(); - vprint!(args.flag_verbose, "Checking... "); - terminal.reset().unwrap(); - for func in &translation.functions { - verifier::verify_function(func, isa).map_err(|err| { - pretty_verifier_error(func, Some(isa), &err) - })?; - } - terminal.fg(term::color::GREEN).unwrap(); - vprintln!(args.flag_verbose, " ok"); - terminal.reset().unwrap(); - } - if args.flag_print { - let mut writer1 = stdout(); - let mut writer2 = stdout(); - match pretty_print_translation(name, &data, &translation, &mut writer1, &mut writer2, isa) { - Err(error) => return Err(String::from(error.description())), - Ok(()) => (), - } - } - if args.flag_optimize { - terminal.fg(term::color::MAGENTA).unwrap(); - vprint!(args.flag_verbose, "Optimizing... "); - terminal.reset().unwrap(); - for func in &translation.functions { - let mut loop_analysis = LoopAnalysis::new(); - let mut cfg = ControlFlowGraph::new(); - cfg.compute(func); - let mut domtree = DominatorTree::new(); - domtree.compute(func, &cfg); - loop_analysis.compute(func, &cfg, &domtree); - let mut context = Context::new(); - context.func = func.clone(); // TODO: Avoid this clone. - context.cfg = cfg; - context.domtree = domtree; - context.loop_analysis = loop_analysis; - match verifier::verify_context(&context.func, &context.cfg, &context.domtree, isa) { - Ok(()) => (), - Err(ref err) => { - return Err(pretty_verifier_error(&context.func, Some(isa), err)); - } - }; - match context.licm(isa) { - Ok(())=> (), - Err(error) => { - match error { - CtonError::Verifier(ref err) => { - return Err(pretty_verifier_error(&context.func, Some(isa), err)); - } - CtonError::InvalidInput | - CtonError::ImplLimitExceeded | - CtonError::CodeTooLarge => return Err(String::from(error.description())), + if args.flag_memory { + let mut input = String::new(); + println!("Inspecting memory"); + println!("Type 'quit' to exit."); + loop { + input.clear(); + print!("Memory index, offset, length (e.g. 0,0,4): "); + let _ = stdout().flush(); + match io::stdin().read_line(&mut input) { + Ok(_) => { + input.pop(); + if input == "quit" { + break; } - } - }; - match verifier::verify_context(&context.func, &context.cfg, &context.domtree, isa) { - Ok(()) => (), - Err(ref err) => return Err(pretty_verifier_error(&context.func, Some(isa), err)), - } - } - terminal.fg(term::color::GREEN).unwrap(); - vprintln!(args.flag_verbose, " ok"); - terminal.reset().unwrap(); - } - if args.flag_execute { - terminal.fg(term::color::MAGENTA).unwrap(); - vprint!(args.flag_verbose, "Compiling... "); - terminal.reset().unwrap(); - match compile_module(&translation, isa, &runtime) { - Ok(ref exec) => { - terminal.fg(term::color::GREEN).unwrap(); - vprintln!(args.flag_verbose, "ok"); - terminal.reset().unwrap(); - terminal.fg(term::color::MAGENTA).unwrap(); - vprint!(args.flag_verbose, "Executing... "); - terminal.reset().unwrap(); - match execute(exec) { - Ok(()) => { - terminal.fg(term::color::GREEN).unwrap(); - vprintln!(args.flag_verbose, "ok"); - terminal.reset().unwrap(); - } - Err(s) => { - return Err(s); + let split: Vec<&str> = input.split(',').collect(); + if split.len() != 3 { + break; } + let memory = instance.inspect_memory( + str::parse(split[0]).unwrap(), + str::parse(split[1]).unwrap(), + str::parse(split[2]).unwrap(), + ); + let mut s = memory.iter().fold(String::from("#"), |mut acc, byte| { + acc.push_str(format!("{:02x}_", byte).as_str()); + acc + }); + s.pop(); + println!("{}", s); } - } - Err(s) => { - return Err(s); - } - }; - if args.flag_memory { - let mut input = String::new(); - terminal.fg(term::color::YELLOW).unwrap(); - println!("Inspecting memory"); - terminal.fg(term::color::MAGENTA).unwrap(); - println!("Type 'quit' to exit."); - terminal.reset().unwrap(); - loop { - input.clear(); - terminal.fg(term::color::YELLOW).unwrap(); - print!("Memory index, offset, length (e.g. 0,0,4): "); - terminal.reset().unwrap(); - let _ = stdout().flush(); - match io::stdin().read_line(&mut input) { - Ok(_) => { - input.pop(); - if input == "quit" { - break; - } - let split: Vec<&str> = input.split(',').collect(); - if split.len() != 3 { - break; - } - let memory = runtime.inspect_memory( - str::parse(split[0]).unwrap(), - str::parse(split[1]).unwrap(), - str::parse(split[2]).unwrap(), - ); - let mut s = memory.iter().fold(String::from("#"), |mut acc, byte| { - acc.push_str(format!("{:02x}_", byte).as_str()); - acc - }); - s.pop(); - println!("{}", s); - } - Err(error) => return Err(String::from(error.description())), - } + Err(error) => return Err(String::from(error.description())), } } } Ok(()) } - -// Prints out a Wasm module, and for each function the corresponding translation in Cretonne IL. -fn pretty_print_translation( - filename: &str, - data: &[u8], - translation: &TranslationResult, - writer_wat: &mut Write, - writer_cretonne: &mut Write, - isa: &TargetIsa, -) -> Result<(), io::Error> { - let mut terminal = term::stdout().unwrap(); - let mut parser = Parser::new(data); - let mut parser_writer = Writer::new(writer_wat); - match parser.read() { - s @ &ParserState::BeginWasm { .. } => parser_writer.write(s)?, - _ => panic!("modules should begin properly"), - } - loop { - match parser.read() { - s @ &ParserState::BeginSection { code: SectionCode::Code, .. } => { - // The code section begins - parser_writer.write(s)?; - break; - } - &ParserState::EndWasm => return Ok(()), - s => parser_writer.write(s)?, - } - } - let mut function_index = 0; - loop { - match parser.read() { - s @ &ParserState::BeginFunctionBody { .. } => { - terminal.fg(term::color::BLUE).unwrap(); - write!( - writer_cretonne, - "====== Function No. {} of module \"{}\" ======\n", - function_index, - filename - )?; - terminal.fg(term::color::CYAN).unwrap(); - write!(writer_cretonne, "Wast ---------->\n")?; - terminal.reset().unwrap(); - parser_writer.write(s)?; - } - s @ &ParserState::EndSection => { - parser_writer.write(s)?; - break; - } - _ => panic!("wrong content in code section"), - } - loop { - match parser.read() { - s @ &ParserState::EndFunctionBody => { - parser_writer.write(s)?; - break; - } - s => { - parser_writer.write(s)?; - } - }; - } - let mut function_string = - format!(" {}", translation.functions[function_index].display(isa)); - function_string.pop(); - let function_str = str::replace(function_string.as_str(), "\n", "\n "); - terminal.fg(term::color::CYAN).unwrap(); - write!(writer_cretonne, "Cretonne IL --->\n")?; - terminal.reset().unwrap(); - write!(writer_cretonne, "{}\n", function_str)?; - function_index += 1; - } - loop { - match parser.read() { - &ParserState::EndWasm => return Ok(()), - s => parser_writer.write(s)?, - } - } -} - -/// 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)) -} diff --git a/src/wasm2obj.rs b/src/wasm2obj.rs index 73bbf0f6ab..1938edfcd8 100644 --- a/src/wasm2obj.rs +++ b/src/wasm2obj.rs @@ -91,33 +91,31 @@ fn handle_module(path: PathBuf, output: &str) -> Result<(), String> { }); let isa = isa_builder.finish(settings::Flags::new(&flag_builder)); - let mut runtime = wasmstandalone_runtime::Runtime::with_flags(isa.flags().clone()); - - let translation = { - match translate_module(&data, &mut runtime) { - Ok(x) => x, - Err(string) => { - return Err(string); - } - } - }; + let mut module = wasmstandalone_runtime::Module::new(); + let mut environ = wasmstandalone_runtime::ModuleEnvironment::new(isa.flags(), &mut module); + translate_module(&data, &mut environ)?; let mut obj = Artifact::new(faerie_target(&*isa)?, Some(String::from(output))); - emit_module(&translation, &mut obj, &*isa, &runtime)?; + let translation = environ.finish_translation(); - if !runtime.tables.is_empty() { - if runtime.tables.len() > 1 { + let (compilation, relocations) = translation.compile(&*isa)?; + + emit_module(&mut obj, &compilation, &relocations)?; + + if !compilation.module.tables.is_empty() { + if compilation.module.tables.len() > 1 { return Err(String::from("multiple tables not supported yet")); } - obj.add_data("table", runtime.tables[0].data.clone()); + return Err(String::from("FIXME: implement tables")); } - if !runtime.memories.is_empty() { - if runtime.memories.len() > 1 { + if !compilation.module.memories.is_empty() { + if compilation.module.memories.len() > 1 { return Err(String::from("multiple memories not supported yet")); } - obj.add_data("memory", runtime.memories[0].data.clone()); + //obj.add_data("memory", initializer); + return Err(String::from("FIXME: implement tables")); } // FIXME: Make the format a parameter.