diff --git a/Cargo.toml b/Cargo.toml index c5b07aa..cc225fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,10 +26,6 @@ path = "ext/src/main.rs" name = "wasm-gas" path = "gas/src/main.rs" -[[bin]] -name = "wasm-pack" -path = "pack/src/main.rs" - [[bin]] name = "wasm-build" path = "build/src/main.rs" diff --git a/build/src/main.rs b/build/src/main.rs index 71b23b9..3b0535f 100644 --- a/build/src/main.rs +++ b/build/src/main.rs @@ -6,9 +6,13 @@ extern crate clap; extern crate parity_wasm; use std::{fs, io}; +use std::io::Write; use std::path::PathBuf; use clap::{App, Arg}; +use parity_wasm::elements; + +use wasm_utils::{CREATE_SYMBOL, CALL_SYMBOL, SET_TEMP_RET_SYMBOL}; #[derive(Debug)] pub enum Error { @@ -58,6 +62,14 @@ pub fn process_output(target_dir: &str, bin_name: &str) -> Result<(), Error> { Ok(()) } +fn has_ctor(module: &elements::Module) -> bool { + if let Some(ref section) = module.export_section() { + section.entries().iter().any(|e| CREATE_SYMBOL == e.field()) + } else { + false + } +} + fn main() { wasm_utils::init_log(); @@ -102,10 +114,6 @@ fn main() { ); } - if !matches.is_present("skip_optimization") { - wasm_utils::optimize(&mut module, vec!["_call", "setTempRet0"]).expect("Optimizer to finish without errors"); - } - if let Some(runtime_type) = matches.value_of("runtime_type") { let runtime_type: &[u8] = runtime_type.as_bytes(); if runtime_type.len() != 4 { @@ -116,5 +124,26 @@ fn main() { module = wasm_utils::inject_runtime_type(module, &runtime_type, runtime_version); } - parity_wasm::serialize_to_file(&path, module).unwrap(); + let mut ctor_module = module.clone(); + + if !matches.is_present("skip_optimization") { + wasm_utils::optimize(&mut module, vec![CALL_SYMBOL, SET_TEMP_RET_SYMBOL]).expect("Optimizer to finish without errors"); + } + + let raw_module = parity_wasm::serialize(module).expect("Failed to serialize module"); + + // If module has an exported function with name=CREATE_SYMBOL + // build will pack the module (raw_module) into this funciton and export as CALL_SYMBOL. + // Otherwise it will just save an optimised raw_module + if has_ctor(&ctor_module) { + if !matches.is_present("skip_optimization") { + wasm_utils::optimize(&mut ctor_module, vec![CREATE_SYMBOL, SET_TEMP_RET_SYMBOL]).expect("Optimizer to finish without errors"); + } + wasm_utils::pack_instance(raw_module, &mut ctor_module); + parity_wasm::serialize_to_file(&path, ctor_module).expect("Failed to serialize to file"); + } else { + let mut file = fs::File::create(&path).expect("Failed to create file"); + file.write_all(&raw_module).expect("Failed to write module to file"); + } + } diff --git a/pack/.gitignore b/pack/.gitignore deleted file mode 100644 index f2f9e58..0000000 --- a/pack/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock \ No newline at end of file diff --git a/pack/Cargo.toml b/pack/Cargo.toml deleted file mode 100644 index 71b99c0..0000000 --- a/pack/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "wasm-pack" -version = "0.1.0" -authors = ["NikVolf "] - -[dependencies] -parity-wasm = "0.12" -wasm-utils = { path = "../" } -clap = "2.24" diff --git a/pack/src/main.rs b/pack/src/main.rs deleted file mode 100644 index b8b1e09..0000000 --- a/pack/src/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -extern crate parity_wasm; -extern crate wasm_utils; -extern crate clap; - -use clap::{App, Arg}; - -fn main() { - wasm_utils::init_log(); - - let matches = App::new("wasm-opt") - .arg(Arg::with_name("input") - .index(1) - .required(true) - .help("Input WASM file")) - .arg(Arg::with_name("output") - .index(2) - .required(true) - .help("Output WASM file")) - .get_matches(); - - let input = matches.value_of("input").expect("is required; qed"); - let output = matches.value_of("output").expect("is required; qed"); - - // doing serialization roundtrip to make sure the input is a valid wasm module - let module = parity_wasm::deserialize_file(&input).expect("Failed to load wasm module from file"); - let bytes = parity_wasm::serialize(module).expect("Failed to serialize wasm module"); - - // Wrap contract code into the wasm module that returns it - let packed_module = wasm_utils::pack_instance(bytes); - - parity_wasm::serialize_to_file(&output, packed_module).unwrap(); -} diff --git a/src/lib.rs b/src/lib.rs index 12d0a9f..96ab101 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,10 @@ extern crate byteorder; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static; +pub static CREATE_SYMBOL: &'static str = "_create"; +pub static CALL_SYMBOL: &'static str = "_call"; +pub static SET_TEMP_RET_SYMBOL: &'static str = "setTempRet0"; + pub mod rules; mod optimizer; @@ -22,3 +26,4 @@ pub use ext::externalize; pub use pack::pack_instance; pub use nondeterminism_check::is_deterministic; pub use runtime_type::inject_runtime_type; + diff --git a/src/pack.rs b/src/pack.rs index fd2062a..4f8073d 100644 --- a/src/pack.rs +++ b/src/pack.rs @@ -1,35 +1,182 @@ -use parity_wasm::{elements, builder}; +use parity_wasm::{elements}; +use self::elements::{ External, Section, Opcode, DataSegment, InitExpr, Internal }; -pub fn pack_instance(raw_module: Vec) -> elements::Module { +use super::{CREATE_SYMBOL, CALL_SYMBOL}; - let raw_len = raw_module.len(); - let mem_required = (raw_len / (64 * 1024) + 1) as u32; +/// If module has an exported "_create" function we want to pack it into "constructor". +/// `raw_module` is the actual contract code +/// `ctor_module` is the constructor which should return `raw_module` +pub fn pack_instance(raw_module: Vec, ctor_module: &mut elements::Module) { - let module = builder::module() - .import() - .module("env") - .field("memory") - .external() - .memory(mem_required as u32, Some(mem_required as u32)) + // We need to find an internal ID of function witch is exported as "_create" + // in order to find it in the Code section of the module + let create_func_id = { + let found_entry = ctor_module.export_section().expect("No export section found").entries().iter() + .find(|entry| CREATE_SYMBOL == entry.field()).expect("No export with name _create found"); + + let function_index: usize = match found_entry.internal() { + &Internal::Function(index) => index as usize, + _ => panic!("export is not a function"), + }; + + let import_section_len: usize = match ctor_module.import_section() { + Some(import) => + import.entries().iter().filter(|entry| match entry.external() { + &External::Function(_) => true, + _ => false, + }).count(), + None => 0, + }; + + // Calculates a function index within module's function section + function_index - import_section_len + }; + + // Code data address is an address where we put the contract's code (raw_module) + let mut code_data_address = 0i32; + + for section in ctor_module.sections_mut() { + match section { + // TODO: add data section is there no one + &mut Section::Data(ref mut data_section) => { + let (index, offset) = if let Some(ref entry) = data_section.entries().iter().last() { + if let Opcode::I32Const(offst) = entry.offset().code()[0] { + let len = entry.value().len() as i32; + let offst = offst as i32; + (entry.index(), offst + (len + 4) - len % 4) + } else { + (0, 0) + } + } else { + (0, 0) + }; + let code_data = DataSegment::new( + index, + InitExpr::new(vec![Opcode::I32Const(offset), Opcode::End]), + raw_module.clone() + ); + data_section.entries_mut().push(code_data); + code_data_address = offset; + }, + _ => {;} + } + } + + for section in ctor_module.sections_mut() { + match section { + &mut Section::Export(ref mut export_section) => { + for entry in export_section.entries_mut().iter_mut() { + if CREATE_SYMBOL == entry.field() { + // change _create export name into default _call + *entry.field_mut() = CALL_SYMBOL.to_owned(); + } + } + } + + &mut Section::Code(ref mut code_section) => { + let code = code_section.bodies_mut()[create_func_id].code_mut().elements_mut(); + code.pop(); + code.extend_from_slice(&[ + Opcode::GetLocal(0), + Opcode::I32Const(code_data_address), + Opcode::I32Store(0, 8), + Opcode::GetLocal(0), + Opcode::I32Const(raw_module.len() as i32), + Opcode::I32Store(0, 12), + Opcode::End]); + }, + + _ => {;}, + } + }; +} + +#[cfg(test)] +mod test { + extern crate parity_wasm; + extern crate byteorder; + + use parity_wasm::builder; + use parity_wasm::interpreter; + use parity_wasm::interpreter::RuntimeValue; + use parity_wasm::ModuleInstanceInterface; + use super::*; + use super::super::optimize; + use super::super::SET_TEMP_RET_SYMBOL; + use byteorder::{ByteOrder, LittleEndian}; + + #[test] + fn call_returns_code() { + let mut module = builder::module() + .import() + .module("env") + .field("memory") + .external() + .memory(1 as u32, Some(1 as u32)) .build() - .data() - .offset(elements::Opcode::I32Const(0)) - .value(raw_module) + .data() + .offset(elements::Opcode::I32Const(16)) + .value(vec![0u8]) .build() - .function() - .signature().param().i32().build() - .body().with_opcodes(elements::Opcodes::new(vec![ - elements::Opcode::GetLocal(0), - elements::Opcode::I32Const(raw_len as i32), - elements::Opcode::I32Store(0, 12), - elements::Opcode::End, - ])).build() + .function() + .signature().build() + .body() + .with_opcodes(elements::Opcodes::new( + vec![ + elements::Opcode::End + ] + )) + .build() .build() - .export() - .field("_call") - .internal().func(0) + .function() + .signature().param().i32().build() + .body() + .with_opcodes(elements::Opcodes::new( + vec![ + elements::Opcode::End + ] + )) + .build() + .build() + .export() + .field("_call") + .internal().func(0) + .build() + .export() + .field("_create") + .internal().func(1) .build() .build(); - module -} \ No newline at end of file + let mut ctor_module = module.clone(); + optimize(&mut module, vec![CALL_SYMBOL, SET_TEMP_RET_SYMBOL]).expect("Optimizer to finish without errors"); + optimize(&mut ctor_module, vec![CREATE_SYMBOL, SET_TEMP_RET_SYMBOL]).expect("Optimizer to finish without errors"); + + let raw_module = parity_wasm::serialize(module).unwrap(); + pack_instance(raw_module.clone(), &mut ctor_module); + + let program = parity_wasm::DefaultProgramInstance::new().expect("Program instance failed to load"); + let env_instance = program.module("env").expect("Wasm program to contain env module"); + let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0)).expect("Linear memory to exist in wasm runtime"); + + let execution_params = interpreter::ExecutionParams::default(); + let constructor_module = program.add_module("contract", ctor_module, None).expect("Failed to initialize module"); + + let _ = constructor_module.execute_export(CALL_SYMBOL, execution_params.add_argument(RuntimeValue::I32(1024))); + + let pointer = LittleEndian::read_u32(&env_memory.get(1024 + 8, 4).unwrap()); + let len = LittleEndian::read_u32(&env_memory.get(1024 + 12, 4).unwrap()); + + let contract_code = env_memory.get(pointer, len as usize).expect("Failed to get code"); + + assert_eq!(raw_module, contract_code); + + let contract_module: elements::Module = parity_wasm::deserialize_buffer(contract_code).expect("Constructed contract module is not valid"); + + let program = parity_wasm::DefaultProgramInstance::new().expect("Program2 instance failed to load"); + let contract_module_instance = program.add_module("contract", contract_module, None).expect("Failed to initialize constructed contract module"); + let execution_params = interpreter::ExecutionParams::default(); + + contract_module_instance.execute_export(CALL_SYMBOL, execution_params).expect("Constructed contract failed to execute"); + } +}